Merge branch 'mkoller/nodeprovider' into sskoczylas/nodeprovider-select
Change-Id: I606867f02a49538101bfec2cd81d13ec56f3a226
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/model/ContextType.java b/api/base/src/main/java/org/eclipse/mdm/api/base/model/ContextType.java
index 29a273f..25dca45 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/model/ContextType.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/model/ContextType.java
@@ -31,23 +31,43 @@
/**
* A {@link ContextRoot} of this type unites meta data of the unit under test.
*/
- UNITUNDERTEST,
+ UNITUNDERTEST("UnitUnderTest"),
/**
* A {@link ContextRoot} of this type unites meta data of the test conditions.
*/
- TESTSEQUENCE,
+ TESTSEQUENCE("TestSequence"),
/**
* A {@link ContextRoot} of this type unites meta data of the used equipment
* (hardware and sensors).
*/
- TESTEQUIPMENT;
+ TESTEQUIPMENT("TestEquipment");
// ======================================================================
// Public methods
// ======================================================================
+ private String typeName;
+
+ /**
+ * Default constructor for this enumeration.
+ *
+ * @param typeName the correct type name
+ */
+ ContextType(String typeName) {
+ this.typeName = typeName;
+ }
+
+ /**
+ * Get the correct type name for the {@link ContextType}.
+ *
+ * @return the type name
+ */
+ public String typeName() {
+ return typeName;
+ }
+
/**
* Returns true if this context type is {@link #UNITUNDERTEST}.
*
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchQuery.java b/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchQuery.java
index dc05bf8..5238c8b 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchQuery.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchQuery.java
@@ -94,7 +94,20 @@
*/
List<Value> getFilterValues(Attribute attribute, Filter filter) throws DataAccessException;
- List<Result> getFilterResults(List<Attribute> attributes, Filter filter) throws DataAccessException;
+ /**
+ * Returns the distinct {@link Result} for the given {@link Attribute}, {@link Filter} and {@link ContextState}.
+ *
+ * @param attributes The {@code Attribute} whose distinct values will be queried
+ * @param filter The criteria sequence
+ * @param contextState The context state to take into account
+ *
+ * @return A distinct {@link List} of {@link Result}s is returned.
+ *
+ * @throws DataAccessException Thrown in case of errors while executing the
+ * query or generating the distinct {@code Value}
+ * sequence.
+ */
+ List<Result> getFilterResults(List<Attribute> attributes, Filter filter, ContextState contextState) throws DataAccessException;
/**
* Executes this search query with given {@link EntityType}s. The {@code
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchService.java b/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchService.java
index 5223f54..f238b78 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchService.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/search/SearchService.java
@@ -126,7 +126,22 @@
List<Value> getFilterValues(Class<? extends Entity> entityClass, Attribute attribute, Filter filter)
throws DataAccessException;
- List<Result> getFilterResults(Class<? extends Entity> entityClass, List<Attribute> attributes, Filter filter) throws DataAccessException;
+ /**
+ * Returns the distinct {@link Result} sequence for given {@link Attribute},
+ * {@link Filter} and {@link ContextState}.
+ *
+ * @param entityClass Used as the {@code SearchQuery} identifier.
+ * @param attributes The {@code Attribute} whose distinct values will be
+ * queried.
+ * @param filter The criteria sequence.
+ * @param contextState The context state.
+ *
+ * @return A distinct {@link List} of {@link Result}s is returned.
+ *
+ * @throws IllegalArgumentException Thrown if given type is not associated with
+ * a predefined {@code SearchQuery}.
+ */
+ List<Result> getFilterResults(Class<? extends Entity> entityClass, List<Attribute> attributes, Filter filter, ContextState contextState) throws DataAccessException;
/**
* Executes the associated {@link SearchQuery} with given {@link EntityType}s.
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/BaseEntitySearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/BaseEntitySearchQuery.java
index d143f95..6240ffa 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/BaseEntitySearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/BaseEntitySearchQuery.java
@@ -158,7 +158,7 @@
* {@inheritDoc}
*/
@Override
- public List<Result> getFilterResults(List<Attribute> attributes, Filter filter) throws DataAccessException {
+ public List<Result> getFilterResults(List<Attribute> attributes, Filter filter, ContextState contextState) throws DataAccessException {
Query query = queryService.createQuery();
for (Attribute attribute : attributes) {
addJoins(query, attribute.getEntityType());
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQuery.java
index 3b11123..7d998be 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQuery.java
@@ -17,10 +17,8 @@
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.reducing;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -31,10 +29,10 @@
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.ContextType;
import org.eclipse.mdm.api.base.model.Value;
import org.eclipse.mdm.api.base.query.Aggregation;
-import org.eclipse.mdm.api.base.query.ComparisonOperator;
-import org.eclipse.mdm.api.base.query.Condition;
import org.eclipse.mdm.api.base.query.DataAccessException;
import org.eclipse.mdm.api.base.query.Filter;
import org.eclipse.mdm.api.base.query.Record;
@@ -111,65 +109,25 @@
* {@inheritDoc}
*/
@Override
- public List<Result> getFilterResults(List<Attribute> attributes, Filter filter)
+ public List<Result> getFilterResults(List<Attribute> attributes, Filter filter, ContextState contextState)
throws DataAccessException {
- ContextState contextState = filter.getContext();
- List<Result> firstResults;
- List<Result> secondResults;
+ ContextState filterContext = filter.getContext() != null ? filter.getContext() : contextState;
+ List<Result> results;
- if (ContextState.ORDERED.equals(contextState)) {
- firstResults = byOrder.getFilterResults(attributes, filter);
- } else {
- firstResults = byResult.getFilterResults(attributes, filter);
- }
-
- if (firstResults.isEmpty()) {
- return Collections.emptyList();
- }
-
- Filter followUpFilter = Filter.or();
- for (Result result : firstResults) {
- Filter attributeValueFilter = Filter.or();
- for (Attribute attr : attributes) {
- Value value = result.getValue(attr, Aggregation.DISTINCT);
- attributeValueFilter.add(ComparisonOperator.EQUAL.create(contextState, attr, value.extract(contextState)));
- }
- followUpFilter.merge(attributeValueFilter);
- }
-
- if (ContextState.ORDERED.equals(contextState)) {
- secondResults = byResult.getFilterResults(attributes, followUpFilter);
- } else {
- secondResults = byOrder.getFilterResults(attributes, followUpFilter);
- }
-
- Set<EntityType> firstTypes = getTypesFromResults(firstResults);
-
- Set<EntityType> secondTypes = getTypesFromResults(secondResults);
-
-
- Set<EntityType> types = new HashSet<>(firstTypes);
- types.addAll(secondTypes);
-
- if (types.size() > 1) {
+ Set<EntityType> entityTypesFromAttributes = getTypesFromAttributes(attributes);
+ if (entityTypesFromAttributes.size() > 1) {
throw new DataAccessException("Cannot handle multiple types here");
}
- return Stream.concat(firstResults.stream(), secondResults.stream())
- .collect(groupingBy(r -> getAttributeValues(attributes, r, Aggregation.DISTINCT), reducing(Result::merge)))
- .values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
- }
+ if (ContextState.ORDERED.equals(filterContext)) {
+ results = byOrder.getFilterResults(attributes, filter, contextState);
+ swapValues(attributes, results, Aggregation.DISTINCT);
+ } else {
+ results = byResult.getFilterResults(attributes, filter, contextState);
+ }
- private Set<EntityType> getTypesFromResults(List<Result> results) {
- return results.stream()
- .map(result -> result.stream().collect(Collectors.toList()))
- .flatMap(Collection::stream)
- .map(Record::getEntityType)
- .collect(Collectors.toSet());
- }
+ return results;
- private String getAttributeValues(List<Attribute> attributes, Result result, Aggregation aggregation) {
- return attributes.stream().map(attribute -> result.getValue(attribute, aggregation).extract()).map(String::valueOf).collect(Collectors.joining("_"));
}
/**
@@ -187,9 +145,9 @@
public List<Result> fetch(List<Attribute> attributes, Filter filter) throws DataAccessException {
ContextState contextState = filter.getContext();
if (ContextState.ORDERED.equals(contextState)) {
- return fetch(contextState, byOrder, byResult, attributes, filter, this::createIdFilter);
+ return fetch(contextState, byOrder, byResult, attributes, filter, results -> createIdFilter(results, entityType, Aggregation.NONE, contextState));
} else if (ContextState.MEASURED.equals(contextState)) {
- return fetch(contextState, byResult, byOrder, attributes, filter, this::createIdFilter);
+ return fetch(contextState, byResult, byOrder, attributes, filter, results -> createIdFilter(results, entityType, Aggregation.NONE, contextState));
}
return fetch(contextState, byResult, byOrder, attributes, filter, list -> filter);
}
@@ -225,9 +183,9 @@
Filter additionalFilter = filterFunction.apply(primaryResults);
List<Result> secondaryResults = secondary.fetch(attributes, additionalFilter);
if (ContextState.ORDERED.equals(contextState)) {
- swapValues(attributes, primaryResults);
+ swapValues(attributes, primaryResults, Aggregation.NONE);
} else if (ContextState.MEASURED.equals(contextState)) {
- swapValues(attributes, secondaryResults);
+ swapValues(attributes, secondaryResults, Aggregation.NONE);
}
return mergeResults(primaryResults, secondaryResults);
}
@@ -239,10 +197,13 @@
* @param attributes selected attributes as list of {@link Attribute}
* @param results the list of results where the values should be swapped
*/
- private void swapValues(List<Attribute> attributes, List<Result> results) {
+ private void swapValues(List<Attribute> attributes, List<Result> results, Aggregation aggregation) {
Set<String> relevantNames = attributes.stream()
+ .filter(this::filterContextAttribute)
.map(Attribute::getName)
+ .map(name -> getAggregatedName(name, aggregation))
.collect(Collectors.toSet());
+
results.stream()
.flatMap(Result::stream)
.map(Record::getValues)
@@ -253,19 +214,34 @@
}
/**
+ * Check whether an attribute which belong to entities from context.
+ * e.g entities created as "UnitUnderTestPart", which then have a parent relation to "UnitUnderTest"
+ *
+ * @param attribute the attribute to check
+ * @return <code>true<</code> if the attribute belongs to a context, <code>false</code> otherwise
+ */
+ private boolean filterContextAttribute(Attribute attribute) {
+ return attribute.getEntityType().getParentRelations().stream()
+ .map(Relation::getName)
+ .anyMatch(name -> ContextType.UNITUNDERTEST.typeName().equals(name) ||
+ ContextType.TESTEQUIPMENT.typeName().equals(name) ||
+ ContextType.TESTSEQUENCE.typeName().equals(name));
+ }
+
+ /**
* Creates a {@link Filter} based on the IDs of the given results.
*
* @param results the results where the IDs should be extracted from
*
* @return a new filter with the IDs
*/
- private Filter createIdFilter(List<Result> results) {
+ private Filter createIdFilter(List<Result> results, EntityType currentEntityType, Aggregation aggregation, ContextState contextState) {
Filter idFilter = Filter.or();
results.stream()
.flatMap(Result::stream)
- .filter(this::hasID)
+ .filter(record -> hasID(record, currentEntityType, aggregation))
.collect(Collectors.groupingBy(Record::getEntityType,
- Collectors.mapping(Record::getID, Collectors.toList())))
+ Collectors.mapping(record -> getID(record, currentEntityType, aggregation, contextState), Collectors.toList())))
.forEach(idFilter::ids);
return idFilter;
}
@@ -278,10 +254,12 @@
* @return <code>true</code> if the record has a valid ID, <code>false</code>
* otherwise
*/
- private boolean hasID(Record record) {
+ private boolean hasID(Record record, EntityType entityType, Aggregation aggregation) {
if (record != null) {
try {
- return record.getID() != null;
+ String idKey = getAggregatedName(entityType.getIDAttribute().getName(), aggregation);
+ Value idValue = record.getValues().get(idKey);
+ return idValue != null;
} catch (IllegalStateException exception) {
return false;
}
@@ -291,6 +269,48 @@
}
/**
+ * Returns the ID of a {@ink Record} and takes {@link EntityType}, {@link Aggregation} and {@link ContextState}
+ * into account.
+ *
+ * @param record the record to get the ID from
+ * @param entityType the related entity type
+ * @param aggregation the aggregation which was used to create the record
+ * @param contextState the context state
+ *
+ * @return the ID
+ */
+ private String getID(Record record, EntityType entityType, Aggregation aggregation, ContextState contextState) {
+ if (record != null) {
+ String idKey = getAggregatedName(entityType.getIDAttribute().getName(), aggregation);
+ Value idValue = record.getValues().get(idKey);
+ if (idValue == null) {
+ throw new IllegalStateException("ID attribute was not selected.");
+ }
+ return idValue.extract(contextState);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the name and takes the given {@link Aggregation} into account.
+ *
+ * @param name the name to use the aggregation with
+ * @param aggregation the aggregation
+ *
+ * @return the updated name
+ */
+ private String getAggregatedName(String name, Aggregation aggregation) {
+ String key;
+ if (Aggregation.NONE == aggregation) {
+ key = name;
+ } else {
+ key = String.format("%s(%s)", aggregation.name(), name);
+ }
+ return key;
+ }
+
+ /**
* Merges given {@link Result}s to one using the root entity type of this search
* query.
*
@@ -338,4 +358,17 @@
}
}
+
+ /**
+ * Returns a set of {@link EntityType} used in the given attributes.
+ *
+ * @param attributes the attributes to collect the entity types from
+ *
+ * @return a set ot entity types
+ */
+ private Set<EntityType> getTypesFromAttributes(List<Attribute> attributes) {
+ return attributes.stream()
+ .map(Attribute::getEntityType)
+ .collect(Collectors.toSet());
+ }
}
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchService.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchService.java
index 8757ed8..a16d882 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchService.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchService.java
@@ -127,8 +127,9 @@
@Override
public List<Result> getFilterResults(final Class<? extends Entity> entityClass,
final List<Attribute> attributes,
- final Filter filter) throws DataAccessException {
- return findSearchQuery(entityClass).getFilterResults(attributes, filter);
+ final Filter filter,
+ final ContextState contextState) throws DataAccessException {
+ return findSearchQuery(entityClass).getFilterResults(attributes, filter, contextState);
}
/**
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
index 7b1d566..8d55b15 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
@@ -65,6 +65,7 @@
import org.eclipse.mdm.protobuf.Mdm.Node;
import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
/**
* Configurable implementation of a {@link NodeProvider}. A
@@ -185,7 +186,7 @@
if (childNodeLevel.isVirtual()) {
// We use the TestStep entity as query root to request the values for the label
List<Result> results = getSearchService(context).getFilterResults(TestStep.class,
- childNodeLevel.getLabelAttributes(), filter);
+ childNodeLevel.getLabelAttributes(), filter, childNodeLevel.getContextState());
// Filter values are always in measured
return results.stream()
@@ -468,7 +469,7 @@
/**
* Creates a basic label for {@link NodeLevel} of label attributes, while
* *ignoring* the NodeLevels {@link ValueExpression}.
- *
+ *
* @param contextState
* @param attributeContainers the label attributes
* @return