blob: effa0dcc5c676d708776a0eab346244f29ee4d15 [file] [log] [blame]
/********************************************************************************
* 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);
}
}