Bug 561943 - Nodeprovider: Features Prio 1

implemented getTreePath
refactoring & documentation
removed environment filter

Change-Id: I6a037474cb709d4b160c8258aa4a3203a75110fa
Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
index 3c51891..c0cab0d 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSQuery.java
@@ -258,8 +258,9 @@
 	private void logQuery() {
 		LOGGER.debug("Executing query with selects: {}, joins: {}.",
 				anuSeq.stream().map(a -> getName(a.attr.aid) + "." + a.attr.aaName).collect(Collectors.joining(",")),
-				joinSeq.stream().map(j -> joinType(j.joiningType) + ": " + getName(j.fromAID) + "-" + j.refName + "->"
-						+ getName(j.toAID)).collect(Collectors.joining(",")));
+				joinSeq.stream().map(
+						j -> getName(j.fromAID) + "-[" + joinType(j.joiningType) + j.refName + "]->" + getName(j.toAID))
+						.collect(Collectors.joining(", ")));
 	}
 
 	private String getName(T_LONGLONG aid) {
@@ -271,12 +272,8 @@
 		}
 	}
 
-	private EntityType lookupEntityType(T_LONGLONG aid) {
-		return entityTypesByID.get("" + ODSConverter.fromODSLong(aid));
-	}
-
 	private String joinType(org.asam.ods.JoinType joinType) {
-		return (joinType == org.asam.ods.JoinType.JTDEFAULT ? "default" : "outer");
+		return (joinType == org.asam.ods.JoinType.JTDEFAULT ? "" : "outer: ");
 	}
 
 	/**
diff --git a/nucleus/businessobjects/build.gradle b/nucleus/businessobjects/build.gradle
index 35415a3..48c2414 100644
--- a/nucleus/businessobjects/build.gradle
+++ b/nucleus/businessobjects/build.gradle
@@ -36,6 +36,7 @@
 	testCompile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.23.2'
 
 	testImplementation project(":api:odsadapter")
+	testImplementation project(":api:atfxadapter")
 	testImplementation "com.google.code.gson:gson:2.7"
 
 	antlr "org.antlr:antlr4:4.5.3"
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
index 5640e07..ebc2098 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
@@ -36,6 +36,13 @@
 import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;

 

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.Parameter;

+import io.swagger.v3.oas.annotations.media.ArraySchema;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.parameters.RequestBody;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

 import io.swagger.v3.oas.annotations.tags.Tag;

 

 /**

@@ -61,13 +68,21 @@
 	}

 

 	@GET

+	@Operation(summary = "Load nodeprovider IDs", description = "Get list of available nodeprovider IDs", responses = {

+			@ApiResponse(description = "The IDs of the available nodeproviders", content = {

+					@Content(array = @ArraySchema(schema = @Schema(implementation = String.class))) }),

+			@ApiResponse(responseCode = "400", description = "Error") })

 	public Response getNodeProviders() {

-		return Response.ok(this.nodeProviderService.getNodeProviderNames()).build();

+		return Response.ok(this.nodeProviderService.getNodeProviderIDs()).build();

 	}

 

 	@GET

 	@Path("{NODEPROVIDER}")

-	public Response getRoots(@PathParam("NODEPROVIDER") String nodeProviderId) {

+	@Operation(summary = "Load root nodes", description = "Get list of root nodes of a nodeprovider", responses = {

+			@ApiResponse(description = "The nodes at root level", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response getRoots(

+			@Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId) {

 		try {

 			java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId).getRoots();

 			return Response.status(Status.OK).entity(NodeProviderResponse.newBuilder().addAllData(nodes).build())

@@ -81,8 +96,12 @@
 

 	@GET

 	@Path("{NODEPROVIDER}/{PARENT_ID}")

-	public Response getChildren(@PathParam("NODEPROVIDER") String nodeProviderId,

-			@PathParam("PARENT_ID") String parentId) {

+	@Operation(summary = "Load children", description = "Get children of given node for a nodeprovider", responses = {

+			@ApiResponse(description = "The requested child nodes", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response getChildren(

+			@Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId,

+			@Parameter(description = "serial of the parent node", required = true) @PathParam("PARENT_ID") String parentId) {

 		try {

 			java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId)

 					.getChildren(SerializationUtil.deserializeNode(parentId));

@@ -98,7 +117,12 @@
 	@POST

 	@Consumes(MediaType.APPLICATION_JSON)

 	@Path("{NODEPROVIDER}")

-	public Response getTreePath(@PathParam("NODEPROVIDER") String nodeProviderId, Node node) {

+	@Operation(summary = "Load tree path", description = "Get a list all parent nodes including the current node", responses = {

+			@ApiResponse(description = "List of all parent nodes including the given node", content = @Content(schema = @Schema(implementation = NodeProviderResponse.class))),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response getTreePath(

+			@Parameter(description = "ID of the nodeprovider", required = true) @PathParam("NODEPROVIDER") String nodeProviderId,

+			@RequestBody(description = "Node that is the child of the returned TreePath.") Node node) {

 		try {

 			java.util.List<Node> nodes = this.nodeProviderService.getNodeProvider(nodeProviderId).getTreePath(node);

 			return Response.status(Status.OK).entity(NodeProviderResponse.newBuilder().addAllData(nodes).build())

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
index 8fe65c8..ccc91a2 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
@@ -47,16 +47,24 @@
 		this.nodeProviders = nodeProviders;

 	}

 

-	public List<String> getNodeProviderNames() {

+	/**

+	 * @return list of nodeprovider IDs

+	 */

+	public List<String> getNodeProviderIDs() {

 		List<String> names = new ArrayList<>(nodeProviders.getNodeProviders().keySet());

 		Collections.sort(names);

 		return names;

 	}

 

-	public NodeProvider getNodeProvider(String name) {

-		NodeProvider nodeProvider = nodeProviders.getNodeProviders().get(name);

+	/**

+	 * @param nodeproviderId ID of the requested {@link NodeProvider}

+	 * @return NodeProvider with requested ID

+	 * @throws NodeProviderException if the requested ID does not exist.

+	 */

+	public NodeProvider getNodeProvider(String nodeproviderId) {

+		NodeProvider nodeProvider = nodeProviders.getNodeProviders().get(nodeproviderId);

 		if (nodeProvider == null) {

-			throw new NodeProviderException("NodeProvider with name " + name + " does not exist!");

+			throw new NodeProviderException("NodeProvider with name " + nodeproviderId + " does not exist!");

 		}

 		return nodeProvider;

 	}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
index 9b88702..7062bfd 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
@@ -160,7 +160,7 @@
 			nodes.add(convertNode(current.get()));
 			current = getParent(current.get());
 		}
-
+		Collections.reverse(nodes);
 		return nodes;
 	}
 
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 71050a5..75ad230 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
@@ -5,8 +5,7 @@
 import java.util.Collections;

 import java.util.Comparator;

 import java.util.List;

-import java.util.Map;

-import java.util.Optional;

+import java.util.stream.Collectors;

 

 import org.eclipse.mdm.api.base.ServiceNotProvidedException;

 import org.eclipse.mdm.api.base.adapter.Attribute;

@@ -32,7 +31,6 @@
 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.EntityManager;

 import org.eclipse.mdm.api.dflt.model.Pool;

 import org.eclipse.mdm.api.dflt.model.Project;

 import org.eclipse.mdm.businessobjects.control.FilterParser;

@@ -46,6 +44,8 @@
 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

@@ -94,9 +94,12 @@
 		List<Node> nodes = new ArrayList<>();

 

 		for (ApplicationContext context : this.connectorService.getContexts()) {

-			getChildNodeLevel(context, parent).map(nl -> getNodes(context, parent, nl)).ifPresent(nodes::addAll);

+			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;

@@ -107,66 +110,191 @@
 	 */

 	@Override

 	public List<Node> getTreePath(Node node) {

-		// TODO Auto-generated method stub

-		return null;

-	}

+		ApplicationContext context = this.connectorService.getContextByName(node.getSource());

 

-	private List<Node> getNodes(ApplicationContext context, Node parent, NodeLevel children) {

-		String sourceName = getSourceName(context);

-

-		Filter filter = getFilter(context, parent);

-

-		if (Environment.class.getSimpleName().equals(children.getEntityType().getName())) {

-

-			QueryService queryService = context.getQueryService().get();

-			Query query = queryService.createQuery().select(children.getIdAttribute(), children.getLabelAttribute())

-					.group(children.getIdAttribute(), children.getLabelAttribute());

-

-			children.getOrderAttributes().stream()

-					.forEach(oa -> query.order(oa.getAttribute(), oa.getOrder() == Order.ASCENDING));

-

-			List<Node> nodes = new ArrayList<>();

-

-			for (Result r : query.fetch()) {

-				nodes.add(convertNode(r, sourceName, children, filter));

-			}

-

-			return nodes;

-		}

-

-		if (!sourceName.equalsIgnoreCase(parent.getSource())) {

-			return Collections.emptyList();

-		}

-

-		if (!sourceNameMatches(filter, sourceName)) {

-			return Collections.emptyList();

-		}

-

-		SearchService search = context.getSearchService().get();

+		NodeLevel nodeLevel = root.getNodeLevel(context, node.getType(), node.getIdAttribute());

 

 		List<Node> nodes = new ArrayList<>();

 

-		if (children.isVirtual()) {

-			// We use the TestStep entity as query root to request the values for the label

-			List<Value> values = search.getFilterValues(TestStep.class, children.getLabelAttribute());

+		// reload node to get correct label

 

-			// Filter values are always in measured

-			for (Value v : values) {

-				nodes.add(convertNode(v, sourceName, children, filter));

-			}

-		} else {

-			Class<? extends Entity> entityClass = getEntityClass(children.getIdAttribute().getEntityType());

+		Query query = createQueryForNodeLevel(context, nodeLevel);

 

-			List<Result> results = search.fetchResults(entityClass,

-					Arrays.asList(children.getIdAttribute(), children.getLabelAttribute()), filter, "");

-

-			for (Result r : results) {

-				nodes.add(convertNode(r, sourceName, children, filter));

-			}

+		for (Result r : query.fetch(createFilterFromIdOrLabel(node, nodeLevel))) {

+			nodes.add(convertNode(r, node.getSource(), nodeLevel, Filter.and()));

 		}

-		return nodes;

+

+		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);

 

@@ -208,7 +336,7 @@
 		for (FilterItem filterItem : filter) {

 			if (filterItem.isCondition()) {

 				Attribute a = filterItem.getCondition().getAttribute();

-				if (Environment.class.getSimpleName().equals(a.getEntityType().getName()) && "Id".equals(a.getName())) {

+				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) {

@@ -225,7 +353,7 @@
 	 * 

 	 * @param context application context

 	 * @param node

-	 * @return the filter of the node

+	 * @return the filter of the node or an empty Filter

 	 */

 	private Filter getFilter(ApplicationContext context, Node node) {

 

@@ -242,52 +370,28 @@
 		return filter;

 	}

 

-	private Optional<NodeLevel> getChildNodeLevel(ApplicationContext context, Node parent) {

-		NodeLevel nodeLevel = null;

-

-		if (parent == null) {

-			nodeLevel = getNodeLevel(context, Environment.class.getSimpleName(), null);

-		} else {

-			nodeLevel = getNodeLevel(context, parent.getType(), parent.getIdAttribute());

-			if (nodeLevel == null) {

-				return Optional.empty();

-			}

-			nodeLevel = nodeLevel.getChild();

-		}

-

-		return Optional.ofNullable(nodeLevel);

-	}

-

-	private NodeLevel getNodeLevel(ApplicationContext context, String type, String idAttribute) {

-		NodeLevel n = root.getNodeLevel(getSourceName(context));

-

-		while (n != null && !nodeLevelMatches(n, type, idAttribute)) {

-			n = n.getChild();

-		}

-

-		return n;

-	}

-

-	private boolean nodeLevelMatches(NodeLevel nodeLevel, String type, String idAttribute) {

-		return ServiceUtils.workaroundForTypeMapping(type)

-				.equals(ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()))

-				&& (idAttribute == null || idAttribute.equals(nodeLevel.getIdAttribute().getName()));

-	}

-

+	/**

+	 * 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) {

-		Map<String, Value> values = r.getRecord(nodeLevel.getEntityType()).getValues();

-

-		String label = values.get(nodeLevel.getLabelAttribute().getName()).extract(nodeLevel.getContextState());

-		String id = values.get(nodeLevel.getIdAttribute().getName()).extract(nodeLevel.getContextState());

+		String label = r.getValue(nodeLevel.getLabelAttribute()).extract(nodeLevel.getContextState());

+		String id = r.getValue(nodeLevel.getIdAttribute()).extract(nodeLevel.getContextState());

 

 		Filter newFilter = null;

 

-		if (Environment.class.getSimpleName().equals(nodeLevel.getEntityType().getName())) {

-			// only 1 environment per context -> no parent filter needed

-			newFilter = Filter.and();

-		} else if (parentFilter != null) {

-			newFilter = parentFilter.copy()

-					.add(ComparisonOperator.EQUAL.create(nodeLevel.getContextState(), nodeLevel.getIdAttribute(), id));

+		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,

@@ -295,6 +399,15 @@
 				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

@@ -304,7 +417,7 @@
 

 		Filter newFilter = null;

 

-		if (Environment.class.getSimpleName().equals(nodeLevel.getEntityType().getName())) {

+		if (isEnvironment(nodeLevel.getEntityType())) {

 			// only 1 environment per context -> no parent filter needed

 			newFilter = Filter.and();

 		} else if (parentFilter != null) {

@@ -313,15 +426,48 @@
 		}

 

 		return SerializationUtil.createNode(sourceName,

-				ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), null,

+				ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()), label,

 				nodeLevel.getIdAttribute().getName(), newFilter, label);

 	}

 

-	private String getSourceName(ApplicationContext context) {

-		String sourceName = context.getEntityManager()

-				.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class)).loadEnvironment()

-				.getSourceName();

-		return sourceName;

+	/**

+	 * 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());

 	}

 

 	/**

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
index 7acce02..4fbd8ff 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
@@ -15,6 +15,7 @@
 package org.eclipse.mdm.nodeprovider.control;

 

 import java.io.Serializable;

+import java.security.Principal;

 import java.util.HashMap;

 import java.util.List;

 import java.util.Map;

@@ -23,12 +24,15 @@
 import javax.enterprise.context.SessionScoped;

 import javax.inject.Inject;

 

+import org.eclipse.mdm.businessobjects.boundary.ChannelResource;

 import org.eclipse.mdm.connector.boundary.ConnectorService;

 import org.eclipse.mdm.nodeprovider.entity.NodeProvider;

 import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;

 import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;

 import org.eclipse.mdm.preferences.controller.PreferenceService;

 import org.eclipse.mdm.preferences.entity.PreferenceMessage;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

 

 /**

  * Repository for NodeProviders in a specific session.

@@ -39,6 +43,8 @@
 

 	private static final long serialVersionUID = -3081666937067348673L;

 

+	private static final Logger LOG = LoggerFactory.getLogger(ChannelResource.class);

+

 	private Map<String, NodeProvider> nodeProviders = new HashMap<>();

 

 	@Inject

@@ -47,44 +53,75 @@
 	@Inject

 	private PreferenceService preferenceService;

 

+	@Inject

+	Principal principal;

+

+	/**

+	 * Default constructor

+	 */

 	public NodeProviderRepository() {

 	}

 

+	/**

+	 * Constructor for unit tests.

+	 * 

+	 * @param connectorService

+	 * @param preferenceService

+	 */

 	public NodeProviderRepository(ConnectorService connectorService, PreferenceService preferenceService) {

 		this.connectorService = connectorService;

 		this.preferenceService = preferenceService;

 		init();

 	}

 

+	/**

+	 * Initializes the NodeProviderRepository

+	 */

 	@PostConstruct

 	public void init() {

+		LOG.debug("Initializing NodeProviderRepository for user ", principal.getName());

 

-		System.out.println("Init NodeProviderRepository");

 		DefaultNodeProvider defaultNP = new DefaultNodeProvider(connectorService);

 

 		putNodeProvider("default", defaultNP);

+		LOG.trace("Registered default nodeprovider.");

 

 		List<PreferenceMessage> msgs = preferenceService.getPreferences("system", "nodeprovider.");

 		for (PreferenceMessage msg : msgs) {

-

 			try {

 				NodeProviderRoot root = parsePreference(msg.getValue());

 				putNodeProvider(root.getId(), new GenericNodeProvider(connectorService, root));

-

+				LOG.trace("Registered generic nodeprovider '{}'.", root.getId());

 			} catch (RuntimeException e) {

 				e.printStackTrace();

+				LOG.warn("Could not deserialize nodeprovider configuration: {}", msg.getValue(), e);

 			}

 		}

 	}

 

+	/**

+	 * Add a nodeprovider to the repository.

+	 * 

+	 * @param id           ID of the nodeprovider

+	 * @param nodeProvider {@link NodeProvider}

+	 */

 	public void putNodeProvider(String id, NodeProvider nodeProvider) {

 		nodeProviders.put(id, nodeProvider);

 	}

 

+	/**

+	 * @return a map of all registered nodeproviders indexed by ID

+	 */

 	public Map<String, NodeProvider> getNodeProviders() {

 		return nodeProviders;

 	}

 

+	/**

+	 * Parse a JSON string representing a {@link NodeProviderRoot}

+	 * 

+	 * @param json JSON string

+	 * @return parsed {@link NodeProviderRoot}

+	 */

 	private NodeProviderRoot parsePreference(String json) {

 		return SerializationUtil.deserializeNodeProviderRoot(connectorService, json);

 	}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
index f0065bf..3d89c19 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
@@ -2,8 +2,15 @@
 

 import java.util.HashMap;

 import java.util.Map;

+import java.util.Optional;

 

+import org.eclipse.mdm.api.base.model.Environment;

+import org.eclipse.mdm.api.dflt.ApplicationContext;

+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;

 import org.eclipse.mdm.nodeprovider.control.GenericNodeProvider;

+import org.eclipse.mdm.protobuf.Mdm.Node;

+

+import com.google.common.base.Strings;

 

 /**

  * A {@link NodeProviderRoot} defines the configuration of a

@@ -65,6 +72,13 @@
 	}

 

 	/**

+	 * @param contexts the contexts to set

+	 */

+	public void setContexts(Map<String, NodeLevel> contexts) {

+		this.contexts = contexts;

+	}

+

+	/**

 	 * Get the {@link NodeLevel} for the given sourceName. If the given sourceName

 	 * has no entry, the {@link NodeLevel} for the {@link NodeProviderRoot#WILDCARD}

 	 * is returned.

@@ -84,10 +98,74 @@
 	}

 

 	/**

-	 * @param contexts the contexts to set

+	 * Find the parent NodeLevel for a given nodeLevel.

+	 * 

+	 * @param context

+	 * @param nodeLevel

+	 * @return parent NodeLevel

 	 */

-	public void setContexts(Map<String, NodeLevel> contexts) {

-		this.contexts = contexts;

+	public NodeLevel getParentNodeLevel(ApplicationContext context, NodeLevel nodeLevel) {

+		NodeLevel n = getNodeLevel(context.getSourceName());

+

+		if (n.equals(nodeLevel)) {

+			return null;

+		}

+		while (n.getChild() != null && !nodeLevel.equals(n.getChild())) {

+			n = n.getChild();

+		}

+

+		return n;

+	}

+

+	/**

+	 * Find the child NodeLevel for a given nodeLevel.

+	 * 

+	 * @param context

+	 * @param nodeLevel

+	 * @return child NodeLevel

+	 */

+	public Optional<NodeLevel> getChildNodeLevel(ApplicationContext context, Node nodeLevel) {

+		NodeLevel childNodeLevel = null;

+

+		if (nodeLevel == null) {

+			childNodeLevel = getNodeLevel(context, Environment.class.getSimpleName(), null);

+		} else {

+			childNodeLevel = getNodeLevel(context, nodeLevel.getType(), nodeLevel.getIdAttribute());

+			if (childNodeLevel == null) {

+				return Optional.empty();

+			}

+			childNodeLevel = childNodeLevel.getChild();

+		}

+

+		return Optional.ofNullable(childNodeLevel);

+	}

+

+	/**

+	 * @param context

+	 * @param type

+	 * @param idAttribute

+	 * @return NodeLevel for given type and idAttribute

+	 */

+	public NodeLevel getNodeLevel(ApplicationContext context, String type, String idAttribute) {

+		NodeLevel n = getNodeLevel(context.getSourceName());

+

+		while (n != null && !nodeLevelMatches(n, type, idAttribute)) {

+			n = n.getChild();

+		}

+

+		return n;

+	}

+

+	/**

+	 * @param nodeLevel

+	 * @param type

+	 * @param idAttribute

+	 * @return true, if the given type and idAttribute matches the nodeLevel

+	 */

+	private boolean nodeLevelMatches(NodeLevel nodeLevel, String type, String idAttribute) {

+		return ServiceUtils.workaroundForTypeMapping(type)

+				.equals(ServiceUtils.workaroundForTypeMapping(nodeLevel.getEntityType()))

+				&& (Strings.isNullOrEmpty(idAttribute) || idAttribute.equals(nodeLevel.getIdAttribute().getName()));

 	}

 

 }

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
index 85b3220..b85e2fa 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
@@ -3,9 +3,9 @@
 /**

  * Enum for defining an order.

  *

+ * @see SortAttribute

  */

 public enum Order {

 

-	ASCENDING,

-	DESCENDING;

+	ASCENDING, DESCENDING;

 }

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
index 3fc96af..b877cae 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
@@ -46,11 +46,33 @@
 

 	private static ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);

 

+	/**

+	 * Create a node with the specified parameters and sets the serial.

+	 * 

+	 * @param source      source name

+	 * @param type        source type

+	 * @param id          ID of the Node

+	 * @param idAttribute name of the ID attribute

+	 * @param filter      filter

+	 * @param label       Label of the Node

+	 * @return {@link Node}

+	 */

 	public static Node createNode(String source, String type, String id, String idAttribute, Filter filter,

 			String label) {

 		return createNode(source, type, id, idAttribute, FilterParser.toString(filter), label);

 	}

 

+	/**

+	 * Create a node with the specified parameters and sets the serial.

+	 * 

+	 * @param source      source name

+	 * @param type        source type

+	 * @param id          ID of the Node

+	 * @param idAttribute name of the ID attribute

+	 * @param filter      filter

+	 * @param label       Label of the Node

+	 * @return {@link Node}

+	 */

 	public static Node createNode(String source, String type, String id, String idAttribute, String filter,

 			String label) {

 		Node node = Node.newBuilder().setSource(source).setType(type).setId(id == null ? "" : id)

@@ -61,33 +83,26 @@
 	}

 

 	/**

-	 * Serializes a node to a base64 encoded JSON string

+	 * Serializes a node to a base64 encoded protobuf {@link Node} object

 	 * 

 	 * @param node

-	 * @return base64 encoded JSON string

+	 * @return base64 encoded protobuf Node

 	 */

 	public static String serializeNode(Node node) {

 		return Base64.getUrlEncoder().encodeToString(node.toByteArray());

-//		Mdm.Node.newBuilder().setSource(node.getSource()).setType(node.getType())

-//						.setId(node.getId()).setLabel(node.getLabel()).setIdAttribute(node.getIdAttribute())

-//						.setFilter(node.getFilter()).setLeaf(node.isLeaf()).build().toByteArray());

 	}

 

 	/**

-	 * Deserialize a base64 encoded JSON string into a {@link Node}.

+	 * Deserialize a base64 encoded protobuf object into a {@link Node}.

 	 * 

-	 * @param base64Json

+	 * @param base64Protobuf

 	 * @return {@link Node}

 	 */

-	public static Node deserializeNode(String base64Json) {

+	public static Node deserializeNode(String base64Protobuf) {

 		try {

-			Mdm.Node node = Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Json.getBytes("UTF-8")));

-			return node;

-//			System.out.println(JsonFormat.printer().print(node));

-//			return new Node(node.getSource(), node.getType(), node.getId(), node.getIdAttribute(), node.getFilter(),

-//					node.getLabel());

+			return Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Protobuf.getBytes("UTF-8")));

 		} catch (InvalidProtocolBufferException | UnsupportedEncodingException e) {

-			throw new NodeProviderException("Cannot deserialize Node: " + base64Json, e);

+			throw new NodeProviderException("Cannot deserialize Node: " + base64Protobuf, e);

 		}

 	}

 

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
index 2131ae3..feeabb9 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
@@ -98,7 +98,7 @@
 

 	@Before

 	public void init() {

-		when(nodeProviderService.getNodeProviderNames()).thenReturn(Collections.emptyList());

+		when(nodeProviderService.getNodeProviderIDs()).thenReturn(Collections.emptyList());

 		when(nodeProviderService.getNodeProvider("default")).thenReturn(nodeProvider);

 

 		rootNode = SerializationUtil.createNode("MDM", "Project", "1", "Id", Filter.and(), "Project1");

@@ -111,7 +111,7 @@
 

 	@Test

 	public void testGetNodeProviders() {

-		when(nodeProviderService.getNodeProviderNames()).thenReturn(Arrays.asList("default", "generic"));

+		when(nodeProviderService.getNodeProviderIDs()).thenReturn(Arrays.asList("default", "generic"));

 		List<String> nodeProviderNames = target("nodeprovider").request().get(new GenericType<List<String>>() {

 		});

 		assertThat(nodeProviderNames).containsExactly("default", "generic");

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
index 3ac7588..dc1254f 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
@@ -162,9 +162,9 @@
 		Attribute model = vehicle.getAttribute("model");

 		EntityType test = mm.getEntityType("Test");

 

-		NodeProviderService repo = mockNodeProviderService();

+		NodeProviderService npService = mockNodeProviderService();

 

-		NodeProvider nodeProvider = repo.getNodeProvider("generic");

+		NodeProvider nodeProvider = npService.getNodeProvider("generic");

 

 		List<Node> roots = nodeProvider.getRoots();

 

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
new file mode 100644
index 0000000..6c50e63
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
@@ -0,0 +1,357 @@
+package org.eclipse.mdm.nodeprovider.control;

+

+import static org.assertj.core.api.Assertions.assertThat;

+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_NAMESERVICE;

+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_PASSWORD;

+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_SERVICENAME;

+import static org.eclipse.mdm.api.odsadapter.ODSContextFactory.PARAM_USER;

+import static org.mockito.ArgumentMatchers.any;

+

+import java.util.Arrays;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+import org.eclipse.mdm.api.base.ConnectionException;

+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.Environment;

+import org.eclipse.mdm.api.base.model.Measurement;

+import org.eclipse.mdm.api.base.model.TestStep;

+import org.eclipse.mdm.api.base.search.ContextState;

+import org.eclipse.mdm.api.dflt.ApplicationContext;

+import org.eclipse.mdm.api.dflt.model.Project;

+import org.eclipse.mdm.api.odsadapter.ODSContextFactory;

+import org.eclipse.mdm.connector.boundary.ConnectorService;

+import org.eclipse.mdm.nodeprovider.entity.NodeLevel;

+import org.eclipse.mdm.nodeprovider.entity.NodeProviderRoot;

+import org.eclipse.mdm.nodeprovider.entity.SortAttribute;

+import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;

+import org.eclipse.mdm.protobuf.Mdm.Node;

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+import org.junit.Ignore;

+import org.junit.Test;

+import org.mockito.Mockito;

+

+import com.google.common.collect.ImmutableMap;

+

+import junit.framework.AssertionFailedError;

+

+@Ignore

+//FIXME 25.11.2020: this test needs a running ODS Server, that is not suitable for continous build in Jenkins.

+//Comment this in for local tests only.

+public class GenericNodeProviderTest {

+	/*

+	 * ATTENTION: ==========

+	 *

+	 * To run this test make sure the target service is running a MDM default model

+	 * and any database constraint which enforces a relation of Test to a parent

+	 * entity is deactivated!

+	 */

+

+	private static final String NAME_SERVICE = "corbaloc::1.2@%s:%s/NameService";

+

+	private static final String USER = "sa";

+	private static final String PASSWORD = "sa";

+

+	private static ApplicationContext context;

+

+	@BeforeClass

+	public static void setUpBeforeClass() throws ConnectionException {

+		String nameServiceHost = System.getProperty("host");

+		String nameServicePort = System.getProperty("port");

+		String serviceName = System.getProperty("service");

+

+		if (nameServiceHost == null || nameServiceHost.isEmpty()) {

+			throw new IllegalArgumentException("name service host is unknown: define system property 'host'");

+		}

+

+		nameServicePort = nameServicePort == null || nameServicePort.isEmpty() ? String.valueOf(2809) : nameServicePort;

+		if (nameServicePort == null || nameServicePort.isEmpty()) {

+			throw new IllegalArgumentException("name service port is unknown: define system property 'port'");

+		}

+

+		if (serviceName == null || serviceName.isEmpty()) {

+			throw new IllegalArgumentException("service name is unknown: define system property 'service'");

+		}

+

+		Map<String, String> connectionParameters = new HashMap<>();

+		connectionParameters.put(PARAM_NAMESERVICE, String.format(NAME_SERVICE, nameServiceHost, nameServicePort));

+		connectionParameters.put(PARAM_SERVICENAME, serviceName + ".ASAM-ODS");

+		connectionParameters.put(PARAM_USER, USER);

+		connectionParameters.put(PARAM_PASSWORD, PASSWORD);

+

+		context = new ODSContextFactory().connect(connectionParameters);

+	}

+

+	@AfterClass

+	public static void tearDownAfterClass() throws ConnectionException {

+		if (context != null) {

+			context.close();

+		}

+

+	}

+

+	private NodeProviderRoot getNodeProviderRoot() {

+		NodeLevel nl = createStructure(context, ContextState.MEASURED);

+		NodeProviderRoot npr = new NodeProviderRoot();

+		npr.setId("generic_measured");

+		npr.setName("Measured");

+		npr.setContexts(ImmutableMap.of("*", nl));

+

+		return npr;

+	}

+

+	private GenericNodeProvider getGenericNodeProvider(NodeProviderRoot npr) {

+		ConnectorService connectorService = Mockito.mock(ConnectorService.class);

+		Mockito.when(connectorService.getContexts()).thenReturn(Arrays.asList(context));

+		Mockito.when(connectorService.getContextByName(any())).thenReturn(context);

+

+		return new GenericNodeProvider(connectorService, npr);

+	}

+

+	@Test

+	public void testGetRoots() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+

+		assertThat(np.getRoots())

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"));

+	}

+

+	@Test

+	public void testGetChildren() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+

+		List<Node> roots = np.getRoots();

+

+		List<Node> projects = np.getChildren(roots.get(0));

+		assertThat(projects).containsExactly(

+				SerializationUtil.createNode("NVHDEMO", "Project", "5", "Id", "Project.Id eq \"5\"", "PMV 2PV"),

+				SerializationUtil.createNode("NVHDEMO", "Project", "4", "Id", "Project.Id eq \"4\"", "PMV Model P"),

+				SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"", "PMV Summit"));

+

+		List<Node> vehicleTypes = np.getChildren(findNodeWithLabel(projects, "PMV Summit"));

+		assertThat(vehicleTypes).containsExactly(

+				SerializationUtil.createNode("NVHDEMO", "vehicle", "Model P", "model",

+						"Project.Id eq \"3\" and vehicle.model eq \"Model P\"", "Model P"),

+				SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"));

+

+		List<Node> tests = np.getChildren(findNodeWithLabel(vehicleTypes, "Summit"));

+		assertThat(tests).containsExactly(SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",

+				"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",

+				"PBN_UNECE_R51_20140213130501"));

+

+		List<Node> testSteps = np.getChildren(findNodeWithLabel(tests, "PBN_UNECE_R51_20140213130501"));

+		assertThat(testSteps).containsExactly(SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id",

+				"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\"",

+				"PBN_UNECE_R51_Left_Acc_50"),

+				SerializationUtil.createNode("NVHDEMO", "TestStep", "10", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"10\"",

+						"PBN_UNECE_R51_Left_Steady_50"),

+				SerializationUtil.createNode("NVHDEMO", "TestStep", "13", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"13\"",

+						"PBN_UNECE_R51_Right_Acc_50"),

+				SerializationUtil.createNode("NVHDEMO", "TestStep", "12", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"12\"",

+						"PBN_UNECE_R51_Right_Steady_50"));

+

+		List<Node> measurements = np.getChildren(findNodeWithLabel(testSteps, "PBN_UNECE_R51_Left_Acc_50"));

+		assertThat(measurements).containsExactly(SerializationUtil.createNode("NVHDEMO", "Measurement", "175", "Id",

+				"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\"",

+				"Channel"),

+				SerializationUtil.createNode("NVHDEMO", "Measurement", "176", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"176\"",

+						"Document"),

+				SerializationUtil.createNode("NVHDEMO", "Measurement", "177", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"177\"",

+						"Photo"));

+

+		List<Node> channelGroups = np.getChildren(findNodeWithLabel(measurements, "Channel"));

+		assertThat(channelGroups).containsExactly(SerializationUtil.createNode("NVHDEMO", "ChannelGroup", "2011", "Id",

+				"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\" and ChannelGroup.Id eq \"2011\"",

+				"Channel"));

+

+		List<Node> channels = np.getChildren(findNodeWithLabel(channelGroups, "Channel"));

+		assertThat(channels).hasSize(11).startsWith(SerializationUtil.createNode("NVHDEMO", "Channel", "8498", "Id",

+				"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\" and Measurement.Id eq \"175\" and ChannelGroup.Id eq \"2011\" and Channel.Id eq \"8498\"",

+				"CHANNEL01"));

+	}

+

+	private Node findNodeWithLabel(List<Node> nodes, String label) {

+		return nodes.stream().filter(n -> n.getLabel().equals(label)).findFirst().orElseThrow(

+				() -> new AssertionFailedError("Node node with label " + label + " found in " + nodes + "."));

+	}

+

+	@Test

+	public void testGetParentNodeEnvironment() {

+		NodeProviderRoot npr = getNodeProviderRoot();

+		GenericNodeProvider np = getGenericNodeProvider(npr);

+

+		NodeLevel nlEnvironment = npr.getNodeLevel(context, "Environment", null);

+

+		assertThat(np.loadParentNode(context,

+				SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"), nlEnvironment))

+						.isNull();

+	}

+

+	@Test

+	public void testGetParentNodeProject() {

+		NodeProviderRoot npr = getNodeProviderRoot();

+		GenericNodeProvider np = getGenericNodeProvider(npr);

+

+		NodeLevel nlProject = npr.getNodeLevel(context, "Project", null);

+

+		assertThat(

+				np.loadParentNode(context,

+						SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",

+								"PMV Summit"),

+						nlProject))

+								.isEqualTo(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id",

+										"Project.Id eq \"3\"", "MDM-NVH"));

+	}

+

+	@Test

+	public void testGetParentNodeVehicle() {

+		NodeProviderRoot npr = getNodeProviderRoot();

+		GenericNodeProvider np = getGenericNodeProvider(npr);

+

+		NodeLevel nlVehicle = npr.getNodeLevel(context, "vehicle", "model");

+

+		assertThat(np.loadParentNode(context,

+				SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model", "vehicle.model eq \"Summit\"",

+						"Summit"),

+				nlVehicle))

+						.isEqualTo(SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id",

+								"vehicle.model eq \"Summit\" and Project.Id eq \"3\"", "PMV Summit"));

+	}

+

+	@Test

+	public void testGetParentNodeTest() {

+		NodeProviderRoot npr = getNodeProviderRoot();

+		GenericNodeProvider np = getGenericNodeProvider(npr);

+

+		NodeLevel nlTest = npr.getNodeLevel(context, "Test", null);

+

+		assertThat(np.loadParentNode(context,

+				SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id", "Test.Id eq \"4\"", ""), nlTest))

+						.isEqualTo(SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",

+								"Test.Id eq \"4\" and vehicle.model eq \"Summit\"", "Summit"));

+	}

+

+	@Test

+	public void testGetParentNodeTestStep() {

+		NodeProviderRoot npr = getNodeProviderRoot();

+		GenericNodeProvider np = getGenericNodeProvider(npr);

+

+		NodeLevel nlTestStep = npr.getNodeLevel(context, "TestStep", null);

+

+		assertThat(np.loadParentNode(context,

+				SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id", "TestStep.Id eq \"11\"", ""),

+				nlTestStep))

+						.isEqualTo(SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",

+								"TestStep.Id eq \"11\" and Test.Id eq \"4\"", "PBN_UNECE_R51_20140213130501"));

+	}

+

+	@Test

+	public void testGetTreePathEnvironment() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+		assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH")))

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"));

+	}

+

+	@Test

+	public void testGetTreePathProject() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+		assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "", "PMV Summit")))

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),

+						SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",

+								"PMV Summit"));

+	}

+

+	@Test

+	public void testGetTreePathVehicle() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+		assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model", "", "Summit")))

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),

+						SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",

+								"PMV Summit"),

+						SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",

+								"Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"));

+	}

+

+	@Test

+	public void testGetTreePathTest() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+		assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "Test", "4", "", "", ""))).containsExactly(

+				SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),

+				SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"", "PMV Summit"),

+				SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"),

+				SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",

+						"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",

+						"PBN_UNECE_R51_20140213130501"));

+	}

+

+	@Test

+	public void testGetTreePathTestStep() {

+		GenericNodeProvider np = getGenericNodeProvider(getNodeProviderRoot());

+		assertThat(np.getTreePath(SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id", "", "Name")))

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "MDM-NVH"),

+						SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", "Project.Id eq \"3\"",

+								"PMV Summit"),

+						SerializationUtil.createNode("NVHDEMO", "vehicle", "Summit", "model",

+								"Project.Id eq \"3\" and vehicle.model eq \"Summit\"", "Summit"),

+						SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id",

+								"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\"",

+								"PBN_UNECE_R51_20140213130501"),

+						SerializationUtil.createNode("NVHDEMO", "TestStep", "11", "Id",

+								"Project.Id eq \"3\" and vehicle.model eq \"Summit\" and Test.Id eq \"4\" and TestStep.Id eq \"11\"",

+								"PBN_UNECE_R51_Left_Acc_50"));

+	}

+

+	private static NodeLevel createStructure(ApplicationContext context, ContextState contextState) {

+		ModelManager modelManager = context.getModelManager().get();

+

+		EntityType etEnv = modelManager.getEntityType(Environment.class);

+		NodeLevel env = new NodeLevel(etEnv);

+		env.getOrderAttributes().add(new SortAttribute(env.getLabelAttribute()));

+

+		NodeLevel project = new NodeLevel(modelManager.getEntityType(Project.class));

+		project.getOrderAttributes().add(new SortAttribute(project.getLabelAttribute()));

+

+		EntityType vehicle = modelManager.getEntityType("vehicle");

+		NodeLevel vehicleModel = new NodeLevel(vehicle, vehicle.getAttribute("model"), vehicle.getAttribute("model"));

+		vehicleModel.setVirtual(true);

+		vehicleModel.setContextState(contextState);

+		vehicleModel.getOrderAttributes().add(new SortAttribute(vehicleModel.getLabelAttribute()));

+

+		NodeLevel test = new NodeLevel(modelManager.getEntityType(org.eclipse.mdm.api.base.model.Test.class));

+		test.getOrderAttributes().add(new SortAttribute(test.getLabelAttribute()));

+

+		NodeLevel testStep = new NodeLevel(modelManager.getEntityType(TestStep.class));

+		testStep.getOrderAttributes().add(new SortAttribute(testStep.getLabelAttribute()));

+

+		NodeLevel measurement = new NodeLevel(modelManager.getEntityType(Measurement.class));

+		measurement.getOrderAttributes().add(new SortAttribute(measurement.getLabelAttribute()));

+

+		NodeLevel channelGroup = new NodeLevel(modelManager.getEntityType(ChannelGroup.class));

+		channelGroup.getOrderAttributes().add(new SortAttribute(channelGroup.getLabelAttribute()));

+

+		NodeLevel channel = new NodeLevel(modelManager.getEntityType(Channel.class));

+		channel.getOrderAttributes().add(new SortAttribute(channel.getLabelAttribute()));

+

+		env.setChild(project);

+		project.setChild(vehicleModel);

+		vehicleModel.setChild(test);

+		test.setChild(testStep);

+		testStep.setChild(measurement);

+		measurement.setChild(channelGroup);

+		channelGroup.setChild(channel);

+

+		return env;

+	}

+}

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java
new file mode 100644
index 0000000..b7ce35a
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java
@@ -0,0 +1,176 @@
+package org.eclipse.mdm.nodeprovider.entity;

+

+import static org.assertj.core.api.Assertions.assertThat;

+

+import java.util.Map;

+

+import org.eclipse.mdm.api.atfxadapter.ATFXContextFactory;

+import org.eclipse.mdm.api.base.ConnectionException;

+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.Environment;

+import org.eclipse.mdm.api.base.model.Measurement;

+import org.eclipse.mdm.api.base.model.TestStep;

+import org.eclipse.mdm.api.base.search.ContextState;

+import org.eclipse.mdm.api.dflt.ApplicationContext;

+import org.eclipse.mdm.api.dflt.model.Project;

+import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;

+import org.junit.After;

+import org.junit.Before;

+import org.junit.Test;

+

+import com.google.common.collect.ImmutableMap;

+

+public class NodeProviderRootTest {

+

+	private ApplicationContext context;

+

+	@Before

+	public void init() throws ConnectionException {

+		Map<String, String> map = ImmutableMap.of("atfxfile", "../../api/atfxadapter/src/test/resources/Right_Acc.atfx",

+				"freetext.active", "false");

+

+		context = new ATFXContextFactory().connect(map);

+	}

+

+	@After

+	public void close() throws ConnectionException {

+		context.close();

+	}

+

+	private NodeProviderRoot getNodeProviderRoot(ApplicationContext context) {

+		NodeLevel nl = createStructure(context, ContextState.MEASURED);

+		NodeProviderRoot npr = new NodeProviderRoot();

+		npr.setId("generic_measured");

+		npr.setName("Measured");

+		npr.setContexts(ImmutableMap.of("*", nl));

+

+		return npr;

+	}

+

+	@Test

+	public void testGetNodeLevel() throws ConnectionException {

+		NodeProviderRoot np = getNodeProviderRoot(context);

+

+		NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);

+

+		assertThat(nl0.getEntityType().getName()).isEqualTo("Environment");

+		assertThat(nl0.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl0.getContextState()).isNull();

+		assertThat(nl0.isVirtual()).isFalse();

+

+		NodeLevel nl1 = np.getNodeLevel(context, "Project", null);

+		assertThat(nl1.getEntityType().getName()).isEqualTo("Project");

+		assertThat(nl1.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl1.getContextState()).isNull();

+		assertThat(nl1.isVirtual()).isFalse();

+

+		NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");

+		assertThat(nl2.getEntityType().getName()).isEqualTo("vehicle");

+		assertThat(nl2.getIdAttribute().getName()).isEqualTo("model");

+		assertThat(nl2.getContextState()).isEqualTo(ContextState.MEASURED);

+		assertThat(nl2.isVirtual()).isTrue();

+

+		NodeLevel nl3 = np.getNodeLevel(context, "Test", null);

+		assertThat(nl3.getEntityType().getName()).isEqualTo("Test");

+		assertThat(nl3.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl3.getContextState()).isNull();

+		assertThat(nl3.isVirtual()).isFalse();

+

+		NodeLevel nl4 = np.getNodeLevel(context, "TestStep", null);

+		assertThat(nl4.getEntityType().getName()).isEqualTo("TestStep");

+		assertThat(nl4.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl4.getContextState()).isNull();

+		assertThat(nl4.isVirtual()).isFalse();

+

+		NodeLevel nl5 = np.getNodeLevel(context, "Measurement", null);

+		assertThat(nl5.getEntityType().getName()).isEqualTo("MeaResult");

+		assertThat(nl5.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl5.getContextState()).isNull();

+		assertThat(nl5.isVirtual()).isFalse();

+

+		NodeLevel nl6 = np.getNodeLevel(context, "ChannelGroup", null);

+		assertThat(nl6.getEntityType().getName()).isEqualTo("SubMatrix");

+		assertThat(nl6.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl6.getContextState()).isNull();

+		assertThat(nl6.isVirtual()).isFalse();

+

+		NodeLevel nl7 = np.getNodeLevel(context, "Channel", null);

+		assertThat(nl7.getEntityType().getName()).isEqualTo("MeaQuantity");

+		assertThat(nl7.getIdAttribute().getName()).isEqualTo("Id");

+		assertThat(nl7.getContextState()).isNull();

+		assertThat(nl7.isVirtual()).isFalse();

+	}

+

+	@Test

+	public void testGetNodeLevelInvalid() throws ConnectionException {

+		NodeProviderRoot np = getNodeProviderRoot(context);

+

+		assertThat(np.getNodeLevel(context, "InvalidType", null)).isNull();

+	}

+

+	@Test

+	public void testGetParentNodeLevel() throws ConnectionException {

+		NodeProviderRoot np = getNodeProviderRoot(context);

+

+		NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);

+		NodeLevel nl1 = np.getNodeLevel(context, "Project", null);

+		NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");

+		NodeLevel nl3 = np.getNodeLevel(context, "Test", null);

+

+		assertThat(np.getParentNodeLevel(context, nl0)).isNull();

+		assertThat(np.getParentNodeLevel(context, nl1)).isEqualTo(nl0);

+		assertThat(np.getParentNodeLevel(context, nl2)).isEqualTo(nl1);

+		assertThat(np.getParentNodeLevel(context, nl3)).isEqualTo(nl2);

+	}

+

+	@Test

+	public void testGetChildNodeLevel() throws ConnectionException {

+		NodeProviderRoot np = getNodeProviderRoot(context);

+

+		NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);

+		NodeLevel nl1 = np.getNodeLevel(context, "Project", null);

+		NodeLevel nl2 = np.getNodeLevel(context, "vehicle", "model");

+		NodeLevel nl3 = np.getNodeLevel(context, "Test", null);

+

+		assertThat(np.getChildNodeLevel(context, null)).contains(nl0);

+		assertThat(np.getChildNodeLevel(context,

+				SerializationUtil.createNode("NVHDEMO", "Environment", "1", "Id", "", "Name"))).contains(nl1);

+		assertThat(

+				np.getChildNodeLevel(context, SerializationUtil.createNode("NVHDEMO", "Project", "", "Id", "", "Name")))

+						.contains(nl2);

+		assertThat(np.getChildNodeLevel(context,

+				SerializationUtil.createNode("NVHDEMO", "vehicle", "", "model", "", "model"))).contains(nl3);

+	}

+

+	private static NodeLevel createStructure(ApplicationContext context, ContextState contextState) {

+		ModelManager modelManager = context.getModelManager().get();

+

+		NodeLevel env = new NodeLevel(modelManager.getEntityType(Environment.class));

+		NodeLevel project = new NodeLevel(modelManager.getEntityType(Project.class));

+		EntityType vehicle = modelManager.getEntityType("vehicle");

+

+		NodeLevel vehicleModel = new NodeLevel(vehicle, vehicle.getAttribute("model"), vehicle.getAttribute("model"));

+		vehicleModel.setVirtual(true);

+		vehicleModel.setContextState(contextState);

+		NodeLevel test = new NodeLevel(modelManager.getEntityType(org.eclipse.mdm.api.base.model.Test.class));

+

+		env.setChild(project);

+		project.setChild(vehicleModel);

+		vehicleModel.setChild(test);

+

+		NodeLevel testStep = new NodeLevel(modelManager.getEntityType(TestStep.class));

+		NodeLevel measurement = new NodeLevel(modelManager.getEntityType(Measurement.class));

+		NodeLevel channelGroup = new NodeLevel(modelManager.getEntityType(ChannelGroup.class));

+		NodeLevel channel = new NodeLevel(modelManager.getEntityType(Channel.class));

+

+		test.setChild(testStep);

+		testStep.setChild(measurement);

+		measurement.setChild(channelGroup);

+		channelGroup.setChild(channel);

+

+		return env;

+	}

+}

diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
index 5732364..ec8bf46 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
@@ -119,7 +119,7 @@
 
   subscribeToNodeProviderChanges() {
     this.nodeProviderSubscription = this.nodeproviderService.nodeProviderChanged.subscribe(
-      np => this.reloadTree(),
+      np =>  this.reloadTree(),
       error => this.notificationService.notifyError(
         this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
     );
@@ -385,20 +385,29 @@
   }
 
   expandTreePath(treePath: MDMTreeNode[]) {
-    console.log("expand", treePath);
-    let current = this.nodes;
-    for (let mdmTreeNode of treePath.reverse()) {
-      console.log("current", current, "mdmTreeNode", mdmTreeNode);
-      current = this.expandChild(current, mdmTreeNode);
-    }
-    console.log("current", current);
-    this.onNodeSelect(current);
+    this.expandChild(this.nodes, treePath);
   }
 
-  expandChild(children: TreeNode[], treePath: MDMTreeNode) {
-    let s = children.filter(node => node.data.source === treePath.source && node.data.id === treePath.id)[0];
-    s.expanded = true;
-    return s.children;
+  expandChild(children: TreeNode[], treePath: MDMTreeNode[]) {
+    const n = treePath.shift();
+    let nodeToExpand = children.filter(node => node.data.source === n.source && node.data.id === n.id)[0];
+
+    if (nodeToExpand) {
+      return this.getChildren(nodeToExpand.data).subscribe(
+        nodes => {
+          nodeToExpand.children = nodes;
+          nodeToExpand.expanded = true;
+          if (treePath.length > 0) {
+            this.expandChild(nodeToExpand.children, treePath);
+          } else {
+            this.selectedNodes = [ nodeToExpand ];
+            this.navigatorService.setSelectedItem(this.toMDMItem(nodeToExpand));
+          }
+        },
+        error => this.notificationService.notifyError(
+          this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
+      );
+    }
   }
 
   /**