| /******************************************************************************** |
| * Copyright (c) 2015-2020 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.api.odsadapter.lookup; |
| |
| import static java.util.stream.Collectors.toList; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Stream; |
| |
| import org.eclipse.mdm.api.base.adapter.EntityType; |
| import org.eclipse.mdm.api.base.adapter.Relation; |
| import org.eclipse.mdm.api.base.model.ContextComponent; |
| import org.eclipse.mdm.api.base.model.ContextSensor; |
| import org.eclipse.mdm.api.base.model.ContextType; |
| import org.eclipse.mdm.api.base.model.Deletable; |
| import org.eclipse.mdm.api.base.model.Entity; |
| import org.eclipse.mdm.api.base.query.ComparisonOperator; |
| import org.eclipse.mdm.api.base.query.DataAccessException; |
| import org.eclipse.mdm.api.base.query.Filter; |
| import org.eclipse.mdm.api.base.query.Query; |
| import org.eclipse.mdm.api.base.query.QueryService; |
| import org.eclipse.mdm.api.base.query.Record; |
| import org.eclipse.mdm.api.base.query.Result; |
| import org.eclipse.mdm.api.dflt.model.TemplateAttribute; |
| import org.eclipse.mdm.api.dflt.model.TemplateComponent; |
| import org.eclipse.mdm.api.dflt.model.TemplateSensor; |
| import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig; |
| import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key; |
| import org.eclipse.mdm.api.odsadapter.query.ODSModelManager; |
| |
| /** |
| * Recursively loads entities for a given {@link EntityConfig} with all resolved |
| * dependencies (optional, mandatory children). |
| * |
| * @param <T> The entity type. |
| * @since 1.0.0 |
| * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH |
| */ |
| public class EntityRequest<T extends Entity> { |
| |
| // ====================================================================== |
| // Instance variables |
| // ====================================================================== |
| |
| final ODSModelManager odsModelManager; |
| final QueryService queryService; |
| final EntityConfig<T> entityConfig; |
| final EntityResult<T> entityResult = new EntityResult<>(this); |
| |
| final Cache cache; |
| |
| boolean filtered; |
| |
| // ====================================================================== |
| // Constructors |
| // ====================================================================== |
| |
| /** |
| * Constructor. |
| * |
| * @param modelManager The {@link ODSModelManager}. |
| * @param config The {@link EntityConfig}. |
| */ |
| public EntityRequest(ODSModelManager modelManager, QueryService queryService, Key<T> key) { |
| this.odsModelManager = modelManager; |
| this.queryService = queryService; |
| this.entityConfig = modelManager.getEntityConfig(key); |
| cache = new Cache(); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param parentRequest The parent {@link EntityRequest}. |
| * @param entityConfig The {@link EntityConfig}. |
| */ |
| protected EntityRequest(EntityRequest<?> parentRequest, EntityConfig<T> entityConfig) { |
| odsModelManager = parentRequest.odsModelManager; |
| queryService = parentRequest.queryService; |
| cache = parentRequest.cache; |
| this.entityConfig = entityConfig; |
| } |
| |
| // ====================================================================== |
| // Public methods |
| // ====================================================================== |
| |
| /** |
| * Loads all entities matching given name pattern. |
| * |
| * @param pattern Is always case sensitive and may contain wildcard characters |
| * as follows: "?" for one matching character and "*" for a |
| * sequence of matching characters. |
| * @return A sorted {@link List} with queried entities is returned. |
| * @throws DataAccessException Thrown if unable to load entities. |
| */ |
| public List<T> loadAll(String pattern) throws DataAccessException { |
| return load(Filter.nameOnly(entityConfig.getEntityType(), pattern)).getSortedEntities(); |
| } |
| |
| /** |
| * Loads all entities matching given instance IDs. |
| * |
| * @param instanceIDs The instance IDs. |
| * @return A sorted {@link List} with queried entities is returned. |
| * @throws DataAccessException Thrown if unable to load entities. |
| */ |
| public List<T> loadAll(Collection<String> instanceIDs) throws DataAccessException { |
| if (instanceIDs.isEmpty()) { |
| // just to be sure... |
| return Collections.emptyList(); |
| } |
| |
| return load(Filter.idsOnly(entityConfig.getEntityType(), instanceIDs)).getSortedEntities(); |
| } |
| |
| public ODSModelManager getODSModelManager() { |
| return odsModelManager; |
| } |
| |
| // ====================================================================== |
| // Protected methods |
| // ====================================================================== |
| |
| /** |
| * Adds foreign key select statements to given {@link Query} for each given |
| * {@link EntityConfig}. |
| * |
| * @param query The {@link Query}. |
| * @param relatedConfigs The {@code EntityConfig}s. |
| * @param mandatory Flag indicates whether given {@code EntityConfig}s are |
| * mandatory or not. |
| * @return For each {@code EntityConfig} a corresponding {@code |
| * RelationConfig} is returned in a {@code List}. |
| */ |
| protected List<RelationConfig> selectRelations(Query query, List<EntityConfig<?>> relatedConfigs, |
| boolean mandatory) { |
| List<RelationConfig> relationConfigs = new ArrayList<>(); |
| EntityType entityType = entityConfig.getEntityType(); |
| for (EntityConfig<?> relatedEntityConfig : relatedConfigs) { |
| RelationConfig relationConfig = new RelationConfig(entityType, relatedEntityConfig, mandatory); |
| query.select(relationConfig.relation.getAttribute()); |
| relationConfigs.add(relationConfig); |
| } |
| |
| return relationConfigs; |
| } |
| |
| /** |
| * Convenience method collects the queried {@link Record} from each |
| * {@link Result}. |
| * |
| * @param results The {@code Result}s. |
| * @return The queried {@link Record}s are returned. |
| */ |
| protected List<Record> collectRecords(List<Result> results) { |
| return results.stream().map(r -> r.getRecord(entityConfig.getEntityType())).collect(toList()); |
| } |
| |
| /** |
| * Loads and maps related entities for each given {@link RelationConfig}. |
| * |
| * @param relationConfigs The {@code RelationConfig}s. |
| * @throws DataAccessException Thrown if unable to load related entities. |
| */ |
| protected void loadRelatedEntities(List<RelationConfig> relationConfigs) throws DataAccessException { |
| for (RelationConfig relationConfig : relationConfigs) { |
| EntityConfig<?> relatedConfig = relationConfig.entityConfig; |
| |
| boolean isContextTypeDefined = entityConfig.getContextType().isPresent(); |
| for (Entity relatedEntity : new EntityRequest<>(this, relatedConfig) |
| .loadAll(relationConfig.dependants.keySet())) { |
| boolean setByContextType = !isContextTypeDefined && relatedConfig.getContextType().isPresent(); |
| for (EntityRecord<?> entityRecord : relationConfig.dependants.remove(relatedEntity.getID())) { |
| setRelatedEntity(entityRecord, relatedEntity, |
| setByContextType ? relatedConfig.getContextType().get() : null); |
| } |
| } |
| |
| if (!relationConfig.dependants.isEmpty()) { |
| // this may occur if the instance id of the related entity |
| // is defined, but the entity itself does not exist |
| throw new IllegalStateException( |
| "Unable to load related entities: " + relationConfig.dependants.toString()); |
| } |
| } |
| } |
| |
| /** |
| * Assigns given related {@link Entity} to given {@link EntityRecord}. |
| * |
| * @param entityRecord The {@code EntityRecord} which references given |
| * {@code Entity}. |
| * @param relatedEntity The related {@code Entity}. |
| * @param contextType Used as qualifier for relation assignment. |
| */ |
| protected void setRelatedEntity(EntityRecord<?> entityRecord, Entity relatedEntity, ContextType contextType) { |
| if (contextType == null) { |
| entityRecord.core.getMutableStore().set(relatedEntity); |
| } else { |
| entityRecord.core.getMutableStore().set(relatedEntity, contextType); |
| } |
| |
| List<TemplateAttribute> templateAttributes = new ArrayList<>(); |
| if (entityRecord.entity instanceof ContextComponent && relatedEntity instanceof TemplateComponent) { |
| templateAttributes.addAll(((TemplateComponent) relatedEntity).getTemplateAttributes()); |
| } else if (entityRecord.entity instanceof ContextSensor && relatedEntity instanceof TemplateSensor) { |
| templateAttributes.addAll(((TemplateSensor) relatedEntity).getTemplateAttributes()); |
| } |
| |
| if (!templateAttributes.isEmpty()) { |
| // hide Value containers that are missing in the template |
| Set<String> names = new HashSet<>(entityRecord.core.getValues().keySet()); |
| names.remove(Entity.ATTR_NAME); |
| names.remove(Entity.ATTR_MIMETYPE); |
| templateAttributes.stream().map(Entity::getName).forEach(names::remove); |
| entityRecord.core.hideValues(names); |
| } |
| } |
| |
| // ====================================================================== |
| // Private methods |
| // ====================================================================== |
| |
| /** |
| * Loads all entities matching given {@link Filter} including all of related |
| * entities (optional, mandatory and children). |
| * |
| * @param filter The {@link Filter}. |
| * @return Returns the queried {@code EntityResult}. |
| * @throws DataAccessException Thrown if unable to load entities. |
| */ |
| private EntityResult<T> load(Filter filter) throws DataAccessException { |
| filtered = !filter.isEmtpty() || entityConfig.isReflexive(); |
| |
| EntityType entityType = entityConfig.getEntityType(); |
| Relation reflexiveRelation = entityConfig.isReflexive() ? entityType.getRelation(entityType) : null; |
| |
| Query query = queryService.createQuery().selectAll(entityConfig.getEntityType()); |
| |
| if (entityConfig.isReflexive()) { |
| query.select(reflexiveRelation.getAttribute()); |
| // entities with children have to be processed before their |
| // children! |
| query.order(entityType.getIDAttribute()); |
| } |
| |
| // prepare relations select statements |
| List<RelationConfig> optionalRelations = selectRelations(query, entityConfig.getOptionalConfigs(), false); |
| List<RelationConfig> mandatoryRelations = selectRelations(query, entityConfig.getMandatoryConfigs(), true); |
| |
| // configure filter |
| Filter adjustedFilter = Filter.or(); |
| if (filtered) { |
| // preserve current conditions |
| adjustedFilter.merge(filter); |
| if (entityConfig.isReflexive()) { |
| // extend to retrieve all reflexive child candidates |
| adjustedFilter.add(ComparisonOperator.IS_NOT_NULL.create(reflexiveRelation.getAttribute(), 0L)); |
| } |
| } |
| |
| // load entities and prepare mappings for required related entities |
| List<EntityRecord<?>> parentRecords = new ArrayList<>(); |
| for (Record record : collectRecords(query.fetch(adjustedFilter))) { |
| Optional<String> reflexiveParentID = Optional.empty(); |
| if (entityConfig.isReflexive()) { |
| reflexiveParentID = record.getID(reflexiveRelation); |
| } |
| EntityRecord<T> entityRecord; |
| |
| if (entityConfig.isReflexive() && reflexiveParentID.isPresent()) { |
| Optional<EntityRecord<T>> parentRecord = entityResult.get(reflexiveParentID.get()); |
| if (!parentRecord.isPresent()) { |
| // this entity's parent was not loaded -> skip |
| continue; |
| } |
| |
| entityRecord = entityResult.add(parentRecord.get(), record); |
| parentRecords.add(parentRecord.get()); |
| } else { |
| entityRecord = entityResult.add(record); |
| } |
| |
| // collect related instance IDs |
| Stream.concat(optionalRelations.stream(), mandatoryRelations.stream()) |
| .forEach(rc -> rc.add(entityRecord, record)); |
| } |
| |
| if (entityResult.isEmpty()) { |
| // no entities found -> neither related nor child entities required |
| return entityResult; |
| } |
| |
| // load and map related entities |
| loadRelatedEntities(optionalRelations); |
| loadRelatedEntities(mandatoryRelations); |
| |
| // sort children of parent |
| if (entityConfig.isReflexive()) { |
| @SuppressWarnings("unchecked") |
| EntityConfig<Deletable> childConfig = (EntityConfig<Deletable>) entityConfig; |
| for (EntityRecord<?> entityRecord : parentRecords) { |
| entityRecord.core.getChildrenStore().sort(childConfig.getEntityClass(), childConfig.getComparator()); |
| } |
| } |
| |
| // load children |
| for (EntityConfig<? extends Deletable> childConfig : entityConfig.getChildConfigs()) { |
| cache.add(new ChildRequest<>(this, childConfig).load()); |
| } |
| |
| return entityResult; |
| } |
| |
| } |