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