| /********************************************************************************
|
| * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
|
| *
|
| * See the NOTICE file(s) distributed with this work for additional
|
| * information regarding copyright ownership.
|
| *
|
| * This program and the accompanying materials are made available under the
|
| * terms of the Eclipse Public License v. 2.0 which is available at
|
| * http://www.eclipse.org/legal/epl-2.0.
|
| *
|
| * SPDX-License-Identifier: EPL-2.0
|
| *
|
| ********************************************************************************/
|
| |
| package org.eclipse.mdm.businessobjects.service; |
| |
| import static io.vavr.API.Tuple; |
| |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| |
| import javax.ejb.EJB; |
| import javax.ejb.Stateless;
|
| import javax.inject.Inject;
|
|
|
| import org.apache.commons.lang3.NotImplementedException; |
| import org.eclipse.mdm.api.base.adapter.Attribute; |
| import org.eclipse.mdm.api.base.adapter.Core; |
| import org.eclipse.mdm.api.base.adapter.EntityStore; |
| import org.eclipse.mdm.api.base.adapter.EntityType; |
| import org.eclipse.mdm.api.base.model.BaseEntity; |
| import org.eclipse.mdm.api.base.model.ContextComponent; |
| import org.eclipse.mdm.api.base.model.ContextRoot; |
| import org.eclipse.mdm.api.base.model.ContextType; |
| import org.eclipse.mdm.api.base.model.Entity; |
| import org.eclipse.mdm.api.base.model.EnumRegistry; |
| import org.eclipse.mdm.api.base.model.EnumerationValue; |
| import org.eclipse.mdm.api.dflt.EntityManager; |
| import org.eclipse.mdm.api.dflt.model.CatalogAttribute; |
| import org.eclipse.mdm.api.dflt.model.CatalogComponent; |
| import org.eclipse.mdm.api.dflt.model.CatalogSensor; |
| import org.eclipse.mdm.api.dflt.model.EntityFactory; |
| import org.eclipse.mdm.api.dflt.model.TemplateAttribute; |
| import org.eclipse.mdm.api.dflt.model.TemplateComponent; |
| import org.eclipse.mdm.api.dflt.model.TemplateRoot; |
| import org.eclipse.mdm.api.dflt.model.TemplateSensor; |
| import org.eclipse.mdm.api.dflt.model.TemplateTest; |
| import org.eclipse.mdm.api.dflt.model.TemplateTestStepUsage; |
| import org.eclipse.mdm.api.dflt.model.ValueList; |
| import org.eclipse.mdm.api.dflt.model.ValueListValue; |
| import org.eclipse.mdm.businessobjects.control.I18NActivity; |
| import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException; |
| import org.eclipse.mdm.businessobjects.control.NavigationActivity; |
| import org.eclipse.mdm.businessobjects.control.SearchActivity; |
| import org.eclipse.mdm.businessobjects.entity.SearchAttribute; |
| import org.eclipse.mdm.businessobjects.utils.EntityNotFoundException; |
| import org.eclipse.mdm.connector.boundary.ConnectorService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import io.vavr.CheckedFunction0; |
| import io.vavr.Function0; |
| import io.vavr.Tuple; |
| import io.vavr.Tuple2; |
| import io.vavr.Value; |
| import io.vavr.collection.HashMap; |
| import io.vavr.collection.List; |
| import io.vavr.collection.Map; |
| import io.vavr.collection.Seq; |
| import io.vavr.collection.Set; |
| import io.vavr.collection.Stream; |
| import io.vavr.control.Option; |
| import io.vavr.control.Try; |
| |
| /** |
| * Class providing basic data access methods to {@link Entity}s. |
| * |
| * @author Alexander Nehmer, science+computing AG Tuebingen (Atos SE) |
| * |
| */ |
| @Stateless |
| public class EntityService { |
| |
| @Inject |
| private ConnectorService connectorService; |
| |
| @EJB |
| private SearchActivity searchActivity; |
| |
| @EJB |
| private NavigationActivity navigationActivity; |
| |
| @EJB |
| private I18NActivity i18nActivity; |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(EntityService.class); |
| |
| /** |
| * Converts a {@code value} into a {@link Value}. If {@code value} is |
| * {@code null}, {@code Value.isEmpty() == true}. |
| * |
| * @param value |
| * the value to wrap in a {@link Value} |
| * @return the created {@link Value} |
| */ |
| // TODO anehmer on 2017-11-26: rename to toValue()? |
| public static <T> Value<T> V(T value) { |
| return Option.of(value); |
| } |
| |
| /** |
| * Converts the given string values into a {@link Seq} of {@link Value}s |
| * wrapping the value. |
| * |
| * @param value |
| * the {@link Value}s to put in a {@link Seq} |
| * @return the created {@link Seq} of {@link Value}s |
| */ |
| public static Seq<Value<String>> SL(String... values) { |
| return List.of(values) |
| .map(Option::of); |
| } |
| |
| /** |
| * Converts the given string {@link Value}s into a {@link Seq} of string |
| * {@link Value}s. |
| * |
| * @param value |
| * the {@link Value}s to put in a {@link Seq} |
| * @return the created {@link Seq} of {@link Value}s |
| */ |
| @SafeVarargs |
| public static Seq<Value<String>> SL(Value<String>... values) { |
| return List.of(values); |
| } |
| |
| /** |
| * Converts the given {@link Value}s into a {@link Seq} of {@link Value}s. |
| * |
| * @param value |
| * the {@link Value}s to put in a {@link Seq} |
| * @return the created {@link Seq} of {@link Value}s |
| */ |
| @SafeVarargs |
| public static Seq<Value<?>> L(Value<?>... values) { |
| return List.of(values); |
| } |
| |
| /** |
| * @see #find(Value, Class, Value, Value, Value...) |
| */ |
| public <T extends Entity> Try<T> find(Value<String> sourceNameSupplier, Class<T> entityClass, |
| Value<String> idSupplier) { |
| return find(sourceNameSupplier, entityClass, idSupplier, (Value<ContextType>) null, null); |
| } |
| |
| /** |
| * @see #find(Value, Class, Value, Value, Value...) |
| */ |
| public <T extends Entity> Try<T> find(Value<String> sourceNameSupplier, Class<T> entityClass, |
| Value<String> idSupplier, Seq<Value<String>> parentIdSuppliers) { |
| return find(sourceNameSupplier, entityClass, idSupplier, (Value<ContextType>) null, parentIdSuppliers); |
| } |
| |
| /** |
| * @see #find(Value, Class, Value, Value, Value...) |
| */ |
| public <T extends Entity> Try<T> find(Value<String> sourceNameSupplier, Class<T> entityClass, |
| Value<String> idSupplier, Value<ContextType> contextTypeSupplier) { |
| return find(sourceNameSupplier, entityClass, idSupplier, contextTypeSupplier, null); |
| } |
| |
| /** |
| * Returns the specified entity by given {@code entityClass} and given |
| * {@code id}. If the {@code entityClass} is either {@link CatalogAttribute}, |
| * {@link TemplateCompont}, {@link TemplateAttributeAttribute} or |
| * {@link ContextComponent} the respective root entities |
| * {@link CatalogComponent}, {@link TemplateRoot} or {@link ContextRoot} are |
| * used to get the entity to find. |
| * |
| * @param sourceNameSupplier |
| * {@link Value} with the name of the source (MDM {@link Environment} |
| * name) |
| * @param entityClass |
| * entityType |
| * @param idSupplier |
| * {@link Value} with id of entity to find |
| * @param contextTypeSupplier |
| * {@link Value} with the contextType of entity to find. Can be |
| * {@code null} if {@code EntityType} has no {@code ContextType}. |
| * @param parentIdSuppliers |
| * {@link Value}s with the id(s) of parent(s). For |
| * {@link CatalogAttribute} the parentId must be the id of the |
| * {@link CatalogComponent}, for {@link TemplateComponent} it must be |
| * the id of the {@link TemplateRoot}, for {@link TemplateAttribute} |
| * it must be the id of the {@link TemplateRoot} first and also the |
| * {@link TemplateComponent}, for a nested {@link TemplateComponent} |
| * it must be the id of the {@link TemplateRoot} first and the id of |
| * the parent {@link TemplateComponent} second, for a |
| * {@link TemplateAttribute} within a nested |
| * {@link TemplateComponent} it must be the id of the |
| * {@link TemplateRoot} first, the id of the parent |
| * {@link TemplateComponent} second and the id of the |
| * {@link TemplateComponent} last and for {@link ContextComponent} it |
| * must be the id of the {@link ContextRoot}. |
| * @return {@link Try} with the found entity |
| */ |
| // TODO anehmer on 2017-11-22: complete javadoc for ValueListValue and |
| // TemplateTestStepUsage |
| // TODO anehmer on 2017-11-22: add comment for parentIds for TplSensors and |
| // nested TplSensors as well as for TplSensorAttrs and nested TplSensorAttrs |
| @SuppressWarnings("unchecked") |
| public <T extends Entity> Try<T> find(Value<String> sourceNameSupplier, Class<T> entityClass, |
| Value<String> idSupplier, Value<ContextType> contextTypeSupplier, Seq<Value<String>> parentIdSuppliers) { |
| |
| // validate parentIds count |
| Map<Class<?>, Integer> minParentsForEntity = HashMap.empty(); |
| minParentsForEntity = minParentsForEntity.put(Tuple(CatalogAttribute.class, 1)) |
| .put(Tuple(CatalogSensor.class, 1)) |
| .put(Tuple(TemplateComponent.class, 1)) |
| .put(Tuple(TemplateAttribute.class, 2)) |
| .put(Tuple(TemplateSensor.class, 2)) |
| .put(Tuple(ValueListValue.class, 1)) |
| .put(Tuple(TemplateTestStepUsage.class, 1)); |
| |
| // return failure if number of parentIds do not correspond with the minimu |
| // required by the entity type |
| Option<Integer> minParents = minParentsForEntity.get(entityClass); |
| // TODO anehmer on 2017-11-25: add entity types to message |
| if (minParents.isDefined() && (parentIdSuppliers == null || minParents.get() > parentIdSuppliers.size())) { |
| return Try.failure(new IllegalArgumentException("ParentId(s) of " + entityClass.getSimpleName() |
| + " not set appropriately. Expected minimum: " + minParents.get())); |
| } |
| |
| // if the find is contextType specific |
| if (contextTypeSupplier != null && !contextTypeSupplier.isEmpty()) { |
| if (entityClass.equals(CatalogAttribute.class)) { |
| // get CatalogAttribute from CatalogComponent |
| if (parentIdSuppliers.size() == 1) { |
| return find(sourceNameSupplier, CatalogComponent.class, parentIdSuppliers.get(0), |
| contextTypeSupplier) |
| .map(catComp -> (T) getChild(CatalogAttribute.class, idSupplier, |
| catComp::getCatalogAttributes)); |
| } |
| // get the CatalogAttribute from a CatalogSensor |
| else if (parentIdSuppliers.size() == 2) { |
| return find(sourceNameSupplier, CatalogSensor.class, parentIdSuppliers.get(1), contextTypeSupplier, |
| parentIdSuppliers.dropRight(1)) |
| .map(catComp -> (T) getChild(CatalogAttribute.class, idSupplier, |
| catComp::getCatalogAttributes)); |
| } |
| } |
| |
| // get CatalogSensor from CatalogComponent |
| else if (entityClass.equals(CatalogSensor.class)) { |
| return find(sourceNameSupplier, CatalogComponent.class, parentIdSuppliers.get(0), contextTypeSupplier) |
| .map(catComp -> (T) getChild(CatalogSensor.class, idSupplier, catComp::getCatalogSensors)); |
| } |
| |
| // get TemplateComponent from TemplateRoot or parent TemplateComponent(s) |
| else if (entityClass.equals(TemplateComponent.class)) { |
| // if nested TplComp has to be found |
| if (parentIdSuppliers.size() > 1) { |
| return find(sourceNameSupplier, TemplateComponent.class, |
| parentIdSuppliers.get(parentIdSuppliers.size() - 1), contextTypeSupplier, |
| parentIdSuppliers.dropRight(1)) |
| .map(tplComp -> (T) getChild(TemplateComponent.class, idSupplier, |
| tplComp::getTemplateComponents)); |
| } |
| // if non-nested TplComp has to be found: exit condition of recursive call |
| return find(sourceNameSupplier, TemplateRoot.class, parentIdSuppliers.get(0), contextTypeSupplier).map( |
| tplRoot -> (T) getChild(TemplateComponent.class, idSupplier, tplRoot::getTemplateComponents)); |
| } |
| |
| // get TemplateAttributes from TemplateComponent |
| else if (entityClass.equals(TemplateAttribute.class)) { |
| Try<TemplateComponent> tplCompTry = find(sourceNameSupplier, TemplateComponent.class, |
| parentIdSuppliers.get(parentIdSuppliers.size() - 1), contextTypeSupplier, |
| parentIdSuppliers.dropRight(1)); |
| // if TemplateSensorAttribute has to be found |
| if (!tplCompTry.isFailure()) { |
| return tplCompTry.map(tplComp -> (T) getChild(TemplateAttribute.class, idSupplier, |
| tplComp::getTemplateAttributes)); |
| } else { |
| return find(sourceNameSupplier, TemplateSensor.class, |
| parentIdSuppliers.get(parentIdSuppliers.size() - 1), contextTypeSupplier, |
| parentIdSuppliers.dropRight(1)) |
| .map(tplComp -> (T) getChild(TemplateAttribute.class, idSupplier, |
| tplComp::getTemplateAttributes)); |
| } |
| } |
| |
| // get TemplateSensor from TemplateComponent |
| else if (entityClass.equals(TemplateSensor.class)) { |
| return find(sourceNameSupplier, TemplateComponent.class, |
| parentIdSuppliers.get(parentIdSuppliers.size() - 1), contextTypeSupplier, |
| parentIdSuppliers.dropRight(1)).map( |
| tplComp -> (T) getChild(TemplateSensor.class, idSupplier, tplComp::getTemplateSensors)); |
| } |
| |
| // get ContextComponent from ContextRoot |
| else if (entityClass.equals(ContextComponent.class)) { |
| // TODO anehmer on 2017-11-09: implement (also for nested ContextComponents) |
| throw new NotImplementedException("NOT IMPLEMENTED YET"); |
| } |
| |
| // get root nested entities (CatalogComponent, TemplateRoot, ContextRoot) |
| return getEntityManager(sourceNameSupplier) |
| .mapTry(em -> em.load(entityClass, contextTypeSupplier.get(), idSupplier.get())); |
| } |
| |
| // get ValueListValue from ValueList |
| else if (entityClass.equals(ValueListValue.class)) { |
| return find(sourceNameSupplier, ValueList.class, parentIdSuppliers.get(0)) |
| .map(valueList -> (T) getChild(ValueListValue.class, idSupplier, valueList::getValueListValues)); |
| } |
| |
| // get TemplateTestStepUsage from TemplateTest |
| else if (entityClass.equals(TemplateTestStepUsage.class)) { |
| return find(sourceNameSupplier, TemplateTest.class, parentIdSuppliers.get(0)) |
| .map(tplTest -> (T) getChild(TemplateTestStepUsage.class, idSupplier, |
| tplTest::getTemplateTestStepUsages)); |
| } |
| |
| // for all other cases |
| return getEntityManager(sourceNameSupplier).map(em -> em.load(entityClass, idSupplier.get())); |
| } |
| |
| /** |
| * Gets the child with the given {@code childId} or an EntityNotFoundException |
| * if the child was not found |
| * |
| * @param childClass |
| * class of child to construct exception on failure |
| * @param childIdSupplier |
| * supplier of the id of child to find |
| * @param childSupplier |
| * function that gets all children |
| * @return the found child |
| */ |
| private <T extends Entity> T getChild(Class<T> childClass, Value<String> childIdSupplier, |
| Function0<java.util.List<T>> childSupplier) { |
| return Stream.ofAll(childSupplier.apply()) |
| .find(childEntity -> childEntity.getID() |
| .equals(childIdSupplier.get())) |
| .getOrElseThrow(() -> new EntityNotFoundException(childClass, childIdSupplier.get())); |
| } |
| |
| /** |
| * Returns a {@link Try} of all {@link Entity}s if no filter is available. |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entityClass |
| * class of the {@link Entity} to find |
| * @param filter |
| * filter string to filter the {@link Entity} result. Can be null. |
| * @return a {@link Try} of the list of found {@link Entity}s |
| */ |
| // TODO anehmer on 2017-11-26: make filter Value<String> |
| public <T extends Entity> Try<List<T>> findAll(Value<String> sourceNameSupplier, Class<T> entityClass, |
| String filter) { |
| return findAll(sourceNameSupplier, entityClass, filter, null); |
| } |
| |
| /** |
| * Returns a {@link Try} of the matching {@link Entity}s of the given |
| * contextType using the given filter or all {@link Entity}s of the given |
| * contextType provided by the {@code contextTypeSupplier} if no filter is |
| * available. |
| * |
| * @param sourceNameSupplier |
| * {@link Value} with the name of the source (MDM {@link Environment} |
| * name) |
| * @param entityClass |
| * class of the {@link Entity} to find |
| * @param filter |
| * filter string to filter the {@link Entity} result |
| * @param contextTypeSupplier |
| * a {@link Value} with the contextType of entity to find. Can be |
| * {@code null} if {@code EntityType} has no {@code ContextType}. |
| * @return a {@link Try} of the list of found {@link Entity}s |
| */ |
| // TODO anehmer on 2017-11-26: make filter Value<String> |
| public <T extends Entity> Try<List<T>> findAll(Value<String> sourceNameSupplier, Class<T> entityClass, |
| String filter, Value<ContextType> contextTypeSupplier) { |
| // TODO anehmer on 2017-11-22: do we need to implement the navigationActivity |
| // filter shortcut like in ChannelGroupService.getChannelGroups() |
| if (filter == null || filter.trim() |
| .length() <= 0) { |
| return Try |
| .of(getLoadAllEntitiesMethod(getEntityManager(sourceNameSupplier).get(), entityClass, |
| contextTypeSupplier)) |
| .map(javaList -> List.ofAll(javaList)); |
| } else { |
| // TODO anehmer on 2017-11-15: not tested |
| return Try |
| .of(() -> this.searchActivity.search(connectorService.getContextByName(sourceNameSupplier.get()), |
| entityClass, filter)) |
| .map(javaList -> List.ofAll(javaList)); |
| } |
| } |
| |
| /** |
| * Returns the method to load all entities of type {@code entityClass}. If a |
| * {@code ContextType} is given the appropriate method in |
| * {@link org.eclipse.mdm.api.dflt.EntityManager} is used |
| * |
| * @param entityManager |
| * entityManager to load entities with |
| * @param entityClass |
| * class of entites to load |
| * @param contextType |
| * {@link ContextType} of entities of null if none |
| * @return the appropriate loadAllEntities() method |
| */ |
| private <T extends Entity> CheckedFunction0<java.util.List<T>> getLoadAllEntitiesMethod(EntityManager entityManager, |
| Class<T> entityClass, Value<ContextType> contextType) { |
| // if contextType is specified |
| if (contextType != null && !contextType.isEmpty()) { |
| return (() -> entityManager.loadAll(entityClass, contextType.get())); |
| } |
| return (() -> entityManager.loadAll(entityClass)); |
| } |
| |
| /** |
| * Creates a new {@link Entity} of type entityClass. The method searches the |
| * {@link EntityFactory} for a suitable create() method by matching the return |
| * parameter and the given entity class. If more than one method is found, the |
| * first one is taken. The argument are provided by {@link Try<Object>}s so that |
| * any exceptions thrown throughout retrieval will be wrapped in the returned |
| * {@link Try}. |
| * |
| * @param entityClass |
| * class of the {@link Entity} to create |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param argumentSuppliers |
| * varargs of {@link Try<?>s that supply the create() method |
| * arguments |
| * @return a {@link Try} with the created {@link Entity} |
| */ |
| @SuppressWarnings("unchecked") |
| public <T extends Entity> Try<T> create(Value<String> sourceNameSupplier, Class<T> entityClass, |
| Seq<Value<?>> argumentSuppliers) { |
| |
| // get corresponding create method for entityClass from EntityFactory |
| return Try.of(() -> connectorService.getContextByName(sourceNameSupplier.get()) |
| .getEntityFactory()) |
| .mapTry(factory -> (T) Stream.of(EntityFactory.class.getMethods()) |
| // find method with the return type matching entityClass |
| .filter(m -> m.getReturnType() |
| .equals(entityClass)) |
| .filter(m -> Arrays.asList(m.getParameterTypes()) |
| // compare argument types |
| .equals(argumentSuppliers.map(s -> s.get() |
| .getClass()) |
| .toJavaList())) |
| .getOrElseThrow(() -> new NoSuchMethodException( |
| "No matching create()-method found for EntityType " + entityClass.getSimpleName() |
| + " taking the parameters " + argumentSuppliers.map(s -> s.get() |
| .getClass() |
| .getName()) |
| .collect(Collectors.joining(", ")))) |
| // invoke with given arguments |
| .invoke(factory.get(), argumentSuppliers.map(Value::get) |
| .toJavaArray())) |
| |
| // start transaction to create the entity |
| .map(e -> DataAccessHelper.execute(getEntityManager(sourceNameSupplier).get(), e, |
| DataAccessHelper.CREATE)); |
| } |
| |
| /** |
| * Updates the given {@link Entity} with the values of the given map provided by |
| * the {@code valueMapSupplier}. |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entity |
| * the entity to update |
| * @param valueMapSupplier |
| * {@link Supplier<Map<String, Object>> of a map of values to update |
| * the entity with according to matching attribute values by name case sensitive |
| * @return a {@link Try} of the updated entity |
| */ |
| public <T extends Entity> Try<T> update(Value<String> sourceNameSupplier, Try<T> entity, |
| Value<Map<String, Object>> valueMapSupplier) { |
| // return updated entity |
| return |
| // update entity values |
| entity.map(e -> updateEntityValues(e, valueMapSupplier.get(), sourceNameSupplier)) |
| // persist entity |
| .map(e -> DataAccessHelper.execute(getEntityManager(sourceNameSupplier).get(), e.get(), |
| DataAccessHelper.UPDATE)); |
| } |
| |
| /** |
| * Deletes the given {@link Entity} {@code valueMapSupplier}. |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entity |
| * the entity to delete |
| * @return a {@link Try} of the deleted entity |
| */ |
| public <T extends Entity> Try<T> delete(Value<String> sourceNameSupplier, Try<T> entity) { |
| return entity.map( |
| e -> DataAccessHelper.execute(getEntityManager(sourceNameSupplier).get(), e, DataAccessHelper.DELETE)); |
| } |
| |
| /** |
| * Returns a {@link Try} of the the {@link SearchAttribute}s for the given |
| * entityClass |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entityClass |
| * class of the {@link Entity} to get the {@link SearchAttribute}s |
| * for |
| * |
| * @return a {@link Try} with the {@link SearchAttribute}s |
| */ |
| public <T extends Entity> Try<List<SearchAttribute>> getSearchAttributesSupplier(Value<String> sourceNameSupplier, |
| Class<T> entityClass) { |
| return Try.of(() -> List.ofAll(this.searchActivity |
| .listAvailableAttributes(connectorService.getContextByName(sourceNameSupplier.get()), entityClass))); |
| } |
| |
| /** |
| * Returns a {@link Try} of the localized {@link Entity} type name |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entityClass |
| * class of the {@link Entity} to be localized |
| * |
| * @return a {@link Try} with the localized {@link Entity} type name |
| */ |
| public <T extends Entity> Try<Map<EntityType, String>> getLocalizeTypeSupplier(Value<String> sourceNameSupplier, |
| Class<T> entityClass) { |
| return Try.of(() -> HashMap.ofAll(this.i18nActivity.localizeType(sourceNameSupplier.get(), entityClass))); |
| } |
| |
| /** |
| * Returns a {@link Try} of the localized {@link Entity} attributes |
| * |
| * @param sourceName |
| * name of the source (MDM {@link Environment} name) |
| * @param entityClass |
| * class of the {@link Entity} to be localized |
| * @return a {@link Try} with the the localized {@link Entity} attributes |
| */ |
| public <T extends Entity> Try<Map<Attribute, String>> getLocalizeAttributesSupplier( |
| Value<String> sourceNameSupplier, Class<T> entityClass) { |
| return Try.of(() -> HashMap.ofAll(this.i18nActivity.localizeAttributes(sourceNameSupplier.get(), entityClass))); |
| } |
| |
| /** |
| * Returns a {@link Try} of an {@link EnumerationValue} for the name supplied by |
| * the {@code enumValueNameSupplier} |
| * |
| * @param enumValueNameSupplier |
| * supplies the name of the {@link EnumerationValue} to get |
| * @return a {@link Try} with the resolved {@link EnumerationValue} |
| */ |
| public Try<EnumerationValue> getEnumerationValueSupplier(Try<?> enumValueNameSupplier) { |
| return Try.of(() -> EnumRegistry.getInstance() |
| .get(EnumRegistry.VALUE_TYPE) |
| .valueOf(enumValueNameSupplier.get() |
| .toString())); |
| } |
| |
| /** |
| * Gets the EntityManager from the ConnectorService with the given source name |
| * provided by the {@code sourceNameSupplier}. |
| * |
| * @param sourceNameSupplier |
| * {@link Value} with the name of the datasource to get EntityManager |
| * for |
| * @return the found EntityManager. Throws {@link MDMEntityAccessException} if |
| * not found. |
| */ |
| private Try<EntityManager> getEntityManager(Value<String> sourceNameSupplier) { |
| return Try.of(() -> this.connectorService.getContextByName(sourceNameSupplier.get()) |
| .getEntityManager() |
| .orElseThrow(() -> new MDMEntityAccessException("Entity manager not present"))); |
| } |
| |
| /** |
| * Updates the given {@link Entity} with the values from the given valueMap. All |
| * matching attributes (case sensitive) are updated as well as the referenced |
| * relations by the id of the given |
| * {@link org.eclipse.mdm.api.base.model.Entity} and the simple class name as |
| * the key (the data model attribute name is the reference, case sensitive). |
| * |
| * @param entity |
| * the entity to update |
| * @param valueMap |
| * values to update the entity with according to matching attribute |
| * names. The keys are compared case sensitive. |
| * @return a {@link Try} with the the updated entity |
| */ |
| @SuppressWarnings("unchecked") |
| public <T extends Entity> Try<T> updateEntityValues(T entity, Map<String, Object> valueMap, |
| Value<String> sourceNameSupplier) { |
| |
| HashMap<String, org.eclipse.mdm.api.base.model.Value> entityValues = HashMap.ofAll(entity.getValues()); |
| |
| // update primitive values where the key from the valueMap has a matching entity |
| // value and collect the updated keys |
| Set<String> updatedPrimitiveValues = valueMap |
| .filter((valueMapEntryKey, valueMapEntryValue) -> entityValues.containsKey(valueMapEntryKey) |
| && !(valueMapEntryValue instanceof java.util.Map)) |
| .map((entityValueEntryKey, entityValueEntryValue) -> { |
| entityValues.get(entityValueEntryKey) |
| .forEach(value -> value.set(entityValueEntryValue)); |
| return new Tuple2<>(entityValueEntryKey, entityValueEntryValue); |
| }) |
| .keySet(); |
| |
| // update enumeration values |
| Set<String> updatedEnumerationValues = valueMap |
| .filter((valueMapEntryKey, valueMapEntryValue) -> entityValues.containsKey(valueMapEntryKey) |
| && (valueMapEntryValue instanceof java.util.Map)) |
| .map((entityValueEntryKey, entityValueEntryValue) -> { |
| entityValues.get(entityValueEntryKey) |
| .forEach(value -> { |
| // get key-value-pairs that identify the enum und enumValue |
| String enumName = ((java.util.Map<String, String>) entityValueEntryValue) |
| .get("Enumeration"); |
| String enumValueName = ((java.util.Map<String, String>) entityValueEntryValue) |
| .get("EnumerationValue"); |
| |
| if (enumName == null || enumValueName == null) { |
| throw new IllegalArgumentException("EnumerationValue is set by providing a map " |
| + "containing the keys 'Enumeration' and 'EnumerationValue' " |
| + "and the respective names as the values"); |
| } |
| |
| // find enumeration and the enumeration value |
| Option.of(EnumRegistry.getInstance() |
| // get enum |
| .get(enumName)) |
| .onEmpty(() -> { |
| throw new IllegalArgumentException( |
| "Enumeration [" + enumName + "] not found"); |
| }) |
| // get enumValue |
| .map(enumeration -> enumeration.valueOf(enumValueName)) |
| // if enumValue is not found, null is returned |
| .filter(Objects::nonNull) |
| .onEmpty(() -> { |
| throw new IllegalArgumentException("EnumerationValue [" + enumValueName |
| + "] not found in Enumeration [" + enumName + "]"); |
| }) |
| // set enumValue |
| .map(enumValue -> { |
| value.set(enumValue); |
| return enumValue; |
| }); |
| |
| }); |
| return new Tuple2<>(entityValueEntryKey, entityValueEntryValue); |
| }) |
| .keySet(); |
| |
| // update the relations and gather the updated keys |
| // use only those keys that have not been updated yet and can be resolved as |
| // class names. If so, try to update accordingly named relation with the entity |
| // found by its id given as the value |
| Set<String> updatedRelations = valueMap |
| .filter((valueMapEntryKey, valueMapEntryValue) -> !updatedPrimitiveValues.contains(valueMapEntryKey) |
| && !updatedEnumerationValues.contains(valueMapEntryKey)) |
| .filter((relatedEntityClassName, relatedEntityId) -> { |
| EntityStore store = getMutableStore(entity); |
| |
| ContextType contextType = null; |
| // determine if class has a context type |
| for (ContextType ct : ContextType.values()) { |
| int index = relatedEntityClassName.toUpperCase() |
| .indexOf(ct.name()); |
| if (index > 0) { |
| contextType = ct; |
| // cut out ContextType |
| relatedEntityClassName = relatedEntityClassName.substring(0, index) |
| + relatedEntityClassName.substring(index + ct.name() |
| .length()); |
| } |
| } |
| |
| // to have final variables for Try |
| final String processedRelatedEntityClassName = relatedEntityClassName; |
| final ContextType contextTypeIfPresent = contextType; |
| |
| // load class from model packages |
| Try<Class<Entity>> updateTry = Try |
| .of(() -> (Class<Entity>) Class |
| .forName("org.eclipse.mdm.api.base.model." + processedRelatedEntityClassName)) |
| .orElse(Try.of(() -> (Class<Entity>) Class |
| .forName("org.eclipse.mdm.api.dflt.model." + processedRelatedEntityClassName))) |
| // update related entity by first finding the related entity by its id |
| // use find and store.set() with ContextType if needed |
| .andThenTry(entityClass -> { |
| if (contextTypeIfPresent == null) { |
| store.set(find(sourceNameSupplier, entityClass, V(relatedEntityId.toString())) |
| .onFailure(e -> LOGGER.error(e.getMessage())) |
| .get()); |
| } else { |
| store.set(find(sourceNameSupplier, entityClass, V(relatedEntityId.toString()), |
| V(contextTypeIfPresent)).onFailure(e -> LOGGER.error(e.getMessage())) |
| .get(), |
| contextTypeIfPresent); |
| } |
| }) |
| .onFailure(e -> LOGGER.error("Entity of type [" + processedRelatedEntityClassName |
| + "] and ID " + relatedEntityId + " not found", e)); |
| |
| return updateTry.isSuccess() ? true : false; |
| }) |
| .keySet(); |
| |
| // return Try.Failure if there are keys that are not present in the entity and |
| // thus are not updated |
| String unmappedKeys = valueMap |
| .filterKeys(key -> !updatedPrimitiveValues.contains(key) && !updatedEnumerationValues.contains(key) |
| && !updatedRelations.contains(key)) |
| .map(Tuple::toString) |
| .collect(Collectors.joining(", ")); |
| |
| if (unmappedKeys != null && !unmappedKeys.isEmpty()) { |
| return Try.failure( |
| new IllegalArgumentException("ValueMap to update entity contains the following keys that either " |
| + "have no match in the entity values or relations to update " |
| + "or an error occurred while finding the related entity: " + unmappedKeys)); |
| } else { |
| return Try.of(() -> entity); |
| } |
| } |
| |
| /** |
| * Get the mutableStore from {@link org.eclipse.mdm.api.base.adapter.Core} of |
| * given {@link org.eclipse.mdm.api.base.model.Entity} |
| * |
| * @param e |
| * Entity to get Core of |
| * @return Core of given Entity |
| */ |
| private static EntityStore getMutableStore(Entity e) { |
| return Try.of(() -> { |
| Method getMetod; |
| try { |
| getMetod = BaseEntity.class.getDeclaredMethod("getCore"); |
| getMetod.setAccessible(true); |
| } catch (NoSuchMethodException | SecurityException x) { |
| throw new IllegalStateException( |
| "Unable to load 'getCore()' in class '" + BaseEntity.class.getSimpleName() + "'.", x); |
| } |
| Core core = (Core) getMetod.invoke(e); |
| return core.getMutableStore(); |
| }) |
| .get(); |
| } |
| |
| } |