blob: 75ad2301422b1f69385516a907b7b705777b2f71 [file] [log] [blame]
package org.eclipse.mdm.nodeprovider.control;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.mdm.api.base.ServiceNotProvidedException;
import org.eclipse.mdm.api.base.adapter.Attribute;
import org.eclipse.mdm.api.base.adapter.EntityType;
import org.eclipse.mdm.api.base.adapter.ModelManager;
import org.eclipse.mdm.api.base.model.Channel;
import org.eclipse.mdm.api.base.model.ChannelGroup;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.Environment;
import org.eclipse.mdm.api.base.model.Measurement;
import org.eclipse.mdm.api.base.model.Quantity;
import org.eclipse.mdm.api.base.model.Test;
import org.eclipse.mdm.api.base.model.TestStep;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.model.ValueType;
import org.eclipse.mdm.api.base.query.BooleanOperator;
import org.eclipse.mdm.api.base.query.ComparisonOperator;
import org.eclipse.mdm.api.base.query.Filter;
import org.eclipse.mdm.api.base.query.FilterItem;
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.ContextState;
import org.eclipse.mdm.api.base.search.SearchService;
import org.eclipse.mdm.api.dflt.ApplicationContext;
import org.eclipse.mdm.api.dflt.model.Pool;
import org.eclipse.mdm.api.dflt.model.Project;
import org.eclipse.mdm.businessobjects.control.FilterParser;
import org.eclipse.mdm.businessobjects.utils.Serializer;
import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
import org.eclipse.mdm.connector.boundary.ConnectorService;
import org.eclipse.mdm.nodeprovider.entity.NodeLevel;
import org.eclipse.mdm.nodeprovider.entity.NodeProvider;
import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;
import org.eclipse.mdm.nodeprovider.entity.Order;
import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
import org.eclipse.mdm.protobuf.Mdm.Node;
import com.google.common.base.Joiner;
/**
* Configurable implementation of a {@link NodeProvider}. A
* {@link NodeProviderRoot} must be provided, which defines the structure of the
* tree defined by the returned {@link Node}s.
*
*/
public class GenericNodeProvider implements NodeProvider {
private ConnectorService connectorService;
private NodeProviderRoot root;
/**
* Construct a new {@link GenericNodeProvider} using the given
* {@link ConnectorService} and {@link NodeProviderRoot}.
*
* @param connectorService
* @param root
*/
public GenericNodeProvider(ConnectorService connectorService, NodeProviderRoot root) {
this.connectorService = connectorService;
this.root = root;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return root.getName();
}
/**
* {@inheritDoc}
*/
@Override
public List<Node> getRoots() {
return getChildren(null);
}
/**
* {@inheritDoc}
*/
@Override
public List<Node> getChildren(Node parent) {
List<Node> nodes = new ArrayList<>();
for (ApplicationContext context : this.connectorService.getContexts()) {
root.getChildNodeLevel(context, parent).map(nl -> loadChildNodes(context, parent, nl))
.ifPresent(nodes::addAll);
}
// Sort the complete list again, as only Nodes from each context are sorted.
// TODO 30.11.2020: sort by defined OrderAttributes
nodes.sort(compareByLabel);
return nodes;
}
/**
* {@inheritDoc}
*/
@Override
public List<Node> getTreePath(Node node) {
ApplicationContext context = this.connectorService.getContextByName(node.getSource());
NodeLevel nodeLevel = root.getNodeLevel(context, node.getType(), node.getIdAttribute());
List<Node> nodes = new ArrayList<>();
// reload node to get correct label
Query query = createQueryForNodeLevel(context, nodeLevel);
for (Result r : query.fetch(createFilterFromIdOrLabel(node, nodeLevel))) {
nodes.add(convertNode(r, node.getSource(), nodeLevel, Filter.and()));
}
if (nodes.size() != 1) {
throw new NodeProviderException("Expected exactly one node, but got " + nodes.size() + "!");
}
Node parent = nodes.get(0);
while ((parent = loadParentNode(context, parent, nodeLevel)) != null) {
nodes.add(parent);
nodeLevel = root.getNodeLevel(context, parent.getType(), parent.getIdAttribute());
}
return reverseListAndFilters(nodes);
}
/**
* Loads the child node of the given parent Node and NodeLevel.
*
* @param context ApplicationContext
* @param parent parent node
* @param childNodeLevel nodeLevel of the child nodes
* @return the children of the given node
*/
private List<Node> loadChildNodes(ApplicationContext context, Node parent, NodeLevel childNodeLevel) {
String sourceName = context.getSourceName();
Filter filter = getFilter(context, parent);
if (isEnvironment(childNodeLevel.getEntityType())) {
return createQueryForNodeLevel(context, childNodeLevel).fetch().stream()
.map(r -> convertNode(r, sourceName, childNodeLevel, filter)).collect(Collectors.toList());
}
if (!sourceName.equalsIgnoreCase(parent.getSource()) || !sourceNameMatches(filter, sourceName)) {
return Collections.emptyList();
}
if (childNodeLevel.isVirtual()) {
// We use the TestStep entity as query root to request the values for the label
List<Value> values = getSearchService(context).getFilterValues(TestStep.class,
childNodeLevel.getLabelAttribute());
// Filter values are always in measured
List<Node> nodes = new ArrayList<>();
for (Value v : values) {
nodes.add(convertNode(v, sourceName, childNodeLevel, filter));
}
return nodes;
} else {
return fetchNodes(context, childNodeLevel, filter);
}
}
/**
* Loads the parent node of the given Node and NodeLevel.
*
* @param context
* @param node
* @param nodeLevel
* @return the parent node of the given node, or null if the given node is a
* root node.
*/
protected Node loadParentNode(ApplicationContext context, Node node, NodeLevel nodeLevel) {
NodeLevel parentLevel = root.getParentNodeLevel(context, nodeLevel);
if (parentLevel == null) {
return null;
}
Filter childFilter = getFilter(context, node);
if (childFilter.isEmtpty()) {
throw new NodeProviderException("Filter cannot be empty! Received node '" + node + "' with empty filter.");
}
if (isEnvironment(parentLevel.getEntityType())) {
Query query = createQueryForNodeLevel(context, parentLevel);
for (Result r : query.fetch()) {
return convertNode(r, context.getSourceName(), parentLevel, childFilter);
}
return null;
}
List<Node> nodes = new ArrayList<>();
if (parentLevel.isVirtual()) {
// We use the TestStep entity as query root to request the values for the label
List<Result> results = getSearchService(context).fetchResults(TestStep.class,
Arrays.asList(parentLevel.getLabelAttribute()), childFilter, "");
for (Result r : results) {
Value value = r.getValue(parentLevel.getLabelAttribute());
nodes.add(convertNode(value, node.getSource(), parentLevel, childFilter));
break;
}
} else {
nodes.addAll(fetchNodes(context, parentLevel, childFilter));
}
if (nodes.size() != 1) {
throw new NodeProviderException("Expected exactly one node but got " + nodes.size() + "!");
}
return nodes.get(0);
}
/**
* Creates an ID filter for non virtual nodes or a filter based on the label for
* virtual nodes.
*
* @param node Node to build the Filter for
* @param nodeLevel NodeLevel of the Node
* @return created Filter
*/
private Filter createFilterFromIdOrLabel(Node node, NodeLevel nodeLevel) {
if (nodeLevel.isVirtual()) {
return Filter.and().add(ComparisonOperator.EQUAL.create(nodeLevel.getContextState(),
nodeLevel.getLabelAttribute(), node.getLabel()));
} else {
return Filter.and().id(nodeLevel.getEntityType(), node.getId());
}
}
/**
* Reverse the Nodes and build up the filters of the individual Node from root
* to leaf.
*
* @param treePath tree path build from leaf to root.
* @return list of Nodes build from root to leaf, e.g. the root will be the
* first Node in the returned list and the leaf will be the last Node.
*/
private List<Node> reverseListAndFilters(List<Node> treePath) {
List<Node> finalList = new ArrayList<>();
List<String> filters = new ArrayList<>();
for (int i = treePath.size() - 1; i >= 0; i--) {
String current = treePath.get(i).getFilter();
if (i - 1 >= 0) {
String child = treePath.get(i - 1).getFilter();
if (!current.equals(child)) {
filters.add(current.replace(child + " and ", ""));
}
} else {
filters.add(current);
}
Node node = treePath.get(i);
finalList.add(SerializationUtil.createNode(node.getSource(), node.getType(), node.getId(),
node.getIdAttribute(), Joiner.on(" and ").join(filters), node.getLabel()));
}
return finalList;
}
/**
* @param context ApplicationContext
* @param nodeLevel nodeLevel to search for nodes
* @param filter filter used for the search
* @return list with the loaded nodes
*/
private List<Node> fetchNodes(ApplicationContext context, NodeLevel nodeLevel, Filter filter) {
Class<? extends Entity> entityClass = getEntityClass(nodeLevel.getIdAttribute().getEntityType());
List<Result> results = getSearchService(context).fetchResults(entityClass,
Arrays.asList(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute()), filter, "");
return results.stream().map(r -> convertNode(r, context.getSourceName(), nodeLevel, filter))
.collect(Collectors.toList());
}
/**
* @param entityType
* @return entity class of the given entityType
* @throws NodeProviderException if entity class is not found
*/
private Class<? extends Entity> getEntityClass(EntityType entityType) {
String entityName = ServiceUtils.workaroundForTypeMapping(entityType);
switch (entityName) {
case "Environment":
return Environment.class;
case "Project":
return Project.class;
case "Pool":
return Pool.class;
case "Test":
return Test.class;
case "TestStep":
return TestStep.class;
case "Measurement":
return Measurement.class;
case "ChannelGroup":
return ChannelGroup.class;
case "Channel":
return Channel.class;
case "Quantity":
return Quantity.class;
default:
throw new RuntimeException("Could not find entity class for entity with name '" + entityName + "'.");
}
}
/**
* Checks if the given filter matches the given sourceName
*
* @param filter
* @param sourceName
* @return true, if the sourceName could match the filter.
*/
private boolean sourceNameMatches(Filter filter, String sourceName) {
if (filter.isEmtpty()) {
return true;
}
for (FilterItem filterItem : filter) {
if (filterItem.isCondition()) {
Attribute a = filterItem.getCondition().getAttribute();
if (isEnvironment(a.getEntityType()) && "Id".equals(a.getName())) {
return sourceName.equals(filterItem.getCondition().getValue().extract(ValueType.STRING));
}
} else if (filterItem.isBooleanOperator() && filterItem.getBooleanOperator() == BooleanOperator.AND) {
continue;
} else {
throw new NodeProviderException("Filter not supported yet: " + filter);
}
}
return true;
}
/**
* Returns the filter of the given node.
*
* @param context application context
* @param node
* @return the filter of the node or an empty Filter
*/
private Filter getFilter(ApplicationContext context, Node node) {
Filter filter = Filter.and();
if (node != null) {
String filterStr = node.getFilter();
if (!filterStr.isEmpty()) {
ModelManager mm = context.getModelManager()
.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));
filter = FilterParser.parseFilterString(mm.listEntityTypes(), filterStr);
}
}
return filter;
}
/**
* Converts a Result to a Node
*
* @param r Result to convert
* @param sourceName name of the source
* @param nodeLevel NodeLevel
* @param parentFilter Filter of the parent Node
* @return Result converted to a Node
*/
private Node convertNode(Result r, String sourceName, NodeLevel nodeLevel, Filter parentFilter) {
String label = r.getValue(nodeLevel.getLabelAttribute()).extract(nodeLevel.getContextState());
String id = r.getValue(nodeLevel.getIdAttribute()).extract(nodeLevel.getContextState());
Filter newFilter = null;
if (parentFilter != null) {
if (isEnvironment(nodeLevel.getEntityType())) {
newFilter = parentFilter.copy();
} else {
newFilter = parentFilter.copy().add(
ComparisonOperator.EQUAL.create(nodeLevel.getContextState(), nodeLevel.getIdAttribute(), id));
}
}
return SerializationUtil.createNode(sourceName,
ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), id,
nodeLevel.getIdAttribute().getName(), newFilter, label);
}
/**
* Converts a Value to a Node.
*
* @param v value to convert
* @param sourceName name of the source
* @param nodeLevel NodeLevel
* @param parentFilter Filter of the parent Node
* @return Value converted to a Node
*/
private Node convertNode(Value v, String sourceName, NodeLevel nodeLevel, Filter parentFilter) {
ContextState contextState = nodeLevel.getContextState() == null ? ContextState.MEASURED
: nodeLevel.getContextState();
String label = Serializer.serializeValue(v).toString();
Filter newFilter = null;
if (isEnvironment(nodeLevel.getEntityType())) {
// only 1 environment per context -> no parent filter needed
newFilter = Filter.and();
} else if (parentFilter != null) {
newFilter = parentFilter.copy()
.add(ComparisonOperator.EQUAL.create(contextState, nodeLevel.getLabelAttribute(), v.extract()));
}
return SerializationUtil.createNode(sourceName,
ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), label,
nodeLevel.getIdAttribute().getName(), newFilter, label);
}
/**
* Creates a Query to load nodes for the given NodeLevel
*
* @param context ApplicationContext in which the query is created
* @param nodeLevel the NodeLevel of the requested nodes.
* @return Query for the given NodeLevel
*/
private Query createQueryForNodeLevel(ApplicationContext context, NodeLevel nodeLevel) {
Query query = context.getQueryService().orElseThrow(() -> new ServiceNotProvidedException(QueryService.class))
.createQuery().select(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute())
.group(nodeLevel.getIdAttribute(), nodeLevel.getLabelAttribute());
nodeLevel.getOrderAttributes().stream()
.forEach(oa -> query.order(oa.getAttribute(), oa.getOrder() == Order.ASCENDING));
return query;
}
/**
* Returns the SearchService
*
* @param context ApplicationContext
* @return the SearchService
* @throws ServiceNotProvidedException if no SearchService is available from the
* context.
*/
private SearchService getSearchService(ApplicationContext context) {
return context.getSearchService().orElseThrow(() -> new ServiceNotProvidedException(SearchService.class));
}
/**
* Checks if entity type is an Environment
*
* @param entityType entityType to check
* @return true, if entity type is an Environment
*/
private boolean isEnvironment(EntityType entityType) {
return Environment.class.getSimpleName().equals(entityType.getName());
}
/**
* Compares {@link Node}s be {@link Node#getLabel()}.
*/
public static final Comparator<Node> compareByLabel = new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return o1.getLabel().compareTo(o2.getLabel());
}
};
}