| /******************************************************************************** |
| * Copyright (c) 2015-2019 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.search; |
| |
| import static java.util.stream.Collectors.groupingBy; |
| import static java.util.stream.Collectors.reducing; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.stream.Collectors; |
| |
| import org.eclipse.mdm.api.base.adapter.Attribute; |
| import org.eclipse.mdm.api.base.adapter.EntityType; |
| import org.eclipse.mdm.api.base.adapter.Relation; |
| 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.Measurement; |
| import org.eclipse.mdm.api.base.model.TestStep; |
| import org.eclipse.mdm.api.base.model.Value; |
| import org.eclipse.mdm.api.base.query.Aggregation; |
| import org.eclipse.mdm.api.base.query.DataAccessException; |
| import org.eclipse.mdm.api.base.query.Filter; |
| import org.eclipse.mdm.api.base.query.FilterItem; |
| import org.eclipse.mdm.api.base.query.JoinType; |
| import org.eclipse.mdm.api.base.query.Query; |
| import org.eclipse.mdm.api.base.query.QueryService; |
| import org.eclipse.mdm.api.base.query.Result; |
| import org.eclipse.mdm.api.base.search.SearchQuery; |
| import org.eclipse.mdm.api.base.search.Searchable; |
| 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; |
| import org.eclipse.mdm.api.odsadapter.search.JoinTree.JoinConfig; |
| import org.eclipse.mdm.api.odsadapter.search.JoinTree.JoinNode; |
| |
| /** |
| * Base implementation for entity {@link SearchQuery}. |
| * |
| * @since 1.0.0 |
| * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH |
| */ |
| abstract class BaseEntitySearchQuery implements SearchQuery { |
| |
| // ====================================================================== |
| // Instance variables |
| // ====================================================================== |
| |
| private final Class<? extends Entity> rootEntityClass; |
| private final JoinTree joinTree = new JoinTree(); |
| private final Class<? extends Entity> entityClass; |
| |
| private final ODSModelManager modelManager; |
| private final QueryService queryService; |
| // ====================================================================== |
| // Constructors |
| // ====================================================================== |
| |
| /** |
| * Constructor. |
| * |
| * @param modelManager Used to load {@link EntityType}s. |
| * @param entityClass The source entity class of this search query. |
| * @param rootEntityClass The root entity class of this search query. |
| */ |
| protected BaseEntitySearchQuery(ODSModelManager modelManager, QueryService queryService, |
| Class<? extends Entity> entityClass, Class<? extends Entity> rootEntityClass) { |
| this.modelManager = modelManager; |
| this.queryService = queryService; |
| this.entityClass = entityClass; |
| this.rootEntityClass = rootEntityClass; |
| |
| EntityConfig<?> entityConfig = modelManager.getEntityConfig(new Key<>(entityClass)); |
| EntityType source = entityConfig.getEntityType(); |
| |
| entityConfig.getOptionalConfigs().stream().map(EntityConfig::getEntityType) |
| .filter(et -> !"Classification".equals(et.getName())).forEach(entityType -> { |
| joinTree.addNode(source, entityType, true, JoinType.OUTER); |
| }); |
| |
| entityConfig.getMandatoryConfigs().stream().map(EntityConfig::getEntityType).forEach(entityType -> { |
| joinTree.addNode(source, entityType, true, JoinType.INNER); |
| }); |
| } |
| |
| // ====================================================================== |
| // Public methods |
| // ====================================================================== |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final List<EntityType> listEntityTypes() { |
| return joinTree.getNodeNames().stream().map(modelManager::getEntityType).collect(Collectors.toList()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final Searchable getSearchableRoot() { |
| Function<String, SearchableNode> factory = k -> { |
| return new SearchableNode(modelManager.getEntityType(k)); |
| }; |
| |
| Map<String, SearchableNode> nodes = new HashMap<>(); |
| for (Entry<String, List<String>> entry : joinTree.getTree().entrySet()) { |
| SearchableNode parent = nodes.computeIfAbsent(entry.getKey(), factory); |
| |
| for (String childName : entry.getValue()) { |
| parent.addRelated(nodes.computeIfAbsent(childName, factory)); |
| } |
| } |
| |
| return nodes.get(modelManager.getEntityType(rootEntityClass).getName()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final List<Value> getFilterValues(Attribute attribute, Filter filter) throws DataAccessException { |
| Query query = queryService.createQuery().select(attribute, Aggregation.DISTINCT).group(attribute); |
| |
| // add required joins |
| filter.stream().filter(FilterItem::isCondition).map(FilterItem::getCondition).forEach(c -> { |
| addJoins(query, c.getAttribute().getEntityType()); |
| }); |
| |
| return query.fetch(filter).stream().map(r -> r.getValue(attribute)).collect(Collectors.toList()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final List<Result> fetchComplete(List<EntityType> entityTypes, Filter filter) throws DataAccessException { |
| Query query = queryService.createQuery().selectID(modelManager.getEntityType(entityClass)); |
| |
| // add required joins |
| entityTypes.stream().forEach(entityType -> { |
| addJoins(query, entityType); |
| query.selectAll(entityType); |
| }); |
| |
| return fetch(query, filter); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public final List<Result> fetch(List<Attribute> attributes, Filter filter) throws DataAccessException { |
| Query query = queryService.createQuery().selectID(modelManager.getEntityType(entityClass)); |
| |
| // add required joins |
| attributes.stream().forEach(attribute -> { |
| addJoins(query, attribute.getEntityType()); |
| query.select(attribute); |
| }); |
| |
| return fetch(query, filter); |
| } |
| |
| // ====================================================================== |
| // Protected methods |
| // ====================================================================== |
| |
| /** |
| * Adds given {@link JoinConfig} to the internally managed {@link JoinTree}. |
| * |
| * @param joinConfig Will be added. |
| */ |
| protected final void addJoinConfig(JoinConfig joinConfig) { |
| EntityConfig<?> targetEntityConfig = modelManager.getEntityConfig(new Key<>(joinConfig.target)); |
| EntityType target = targetEntityConfig.getEntityType(); |
| |
| // add dependency source to target |
| joinTree.addNode(modelManager.getEntityType(joinConfig.source), target, joinConfig.viaParent, JoinType.INNER); |
| |
| // add target's optional dependencies |
| targetEntityConfig.getOptionalConfigs().stream().map(EntityConfig::getEntityType) |
| .filter(et -> !"Classification".equals(et.getName())).forEach(entityType -> { |
| joinTree.addNode(target, entityType, true, JoinType.OUTER); |
| }); |
| |
| // add target's mandatory dependencies |
| targetEntityConfig.getMandatoryConfigs().stream().map(EntityConfig::getEntityType).forEach(entityType -> { |
| joinTree.addNode(target, entityType, true, JoinType.INNER); |
| }); |
| } |
| |
| /** |
| * Adds joins to context data according to the given {@link ContextState}. |
| * |
| * @param contextState The {@code ContextState}. |
| */ |
| protected final void addJoinConfig(ContextState contextState) { |
| if (contextState == null) { |
| // nothing to do |
| return; |
| } |
| |
| Class<? extends Entity> source = contextState.isOrdered() ? TestStep.class : Measurement.class; |
| for (ContextType contextType : ContextType.values()) { |
| EntityType rootEntityType = modelManager.getEntityType(ContextRoot.class, contextType); |
| for (Relation componentRelation : rootEntityType.getChildRelations()) { |
| joinTree.addNode(componentRelation.getSource(), componentRelation.getTarget(), true, JoinType.OUTER); |
| |
| for (Relation sensorRelation : componentRelation.getTarget().getChildRelations()) { |
| joinTree.addNode(sensorRelation.getSource(), sensorRelation.getTarget(), true, JoinType.OUTER); |
| } |
| } |
| |
| joinTree.addNode(modelManager.getEntityType(source), rootEntityType, true, JoinType.OUTER); |
| } |
| } |
| |
| // ====================================================================== |
| // Private methods |
| // ====================================================================== |
| |
| /** |
| * Executes given {@link Query} using given {@link Filter}. Joins required for |
| * the given {@code Filter} will be implicitly added as needed. |
| * |
| * @param query Will be executed. |
| * @param filter The query filtering sequence. |
| * @return Returns the {@link Result}s in a {@code List}. |
| * @throws DataAccessException Thrown if failed to execute given {@code Query}. |
| */ |
| private List<Result> fetch(Query query, Filter filter) throws DataAccessException { |
| filter.stream().filter(FilterItem::isCondition).map(FilterItem::getCondition) |
| .forEach(c -> addJoins(query, c.getAttribute().getEntityType())); |
| |
| EntityType entityType = modelManager.getEntityType(entityClass); |
| return query.order(entityType.getIDAttribute()).fetch(filter).stream() |
| // group by instance ID and merge grouped results |
| .collect(groupingBy(r -> r.getRecord(entityType).getID(), reducing(Result::merge))) |
| // collect merged results |
| .values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); |
| } |
| |
| /** |
| * Adds join statements to given target {@link EntityType} as needed to be able |
| * to execute given {@code Query}. |
| * |
| * @param query The {@link Query}. |
| * @param entityType The target {@link EntityType}. |
| */ |
| private void addJoins(Query query, EntityType entityType) { |
| if (query.isQueried(entityType)) { |
| return; |
| } |
| |
| JoinNode joinNode = joinTree.getJoinNode(entityType.getName()); |
| EntityType sourceEntityType = modelManager.getEntityType(joinNode.source); |
| addJoins(query, sourceEntityType); |
| query.join(sourceEntityType.getRelation(entityType), joinNode.joinType); |
| } |
| |
| } |