Merge "Documentation transferred to AsciiDoc, initial version." into dev
diff --git a/README.md b/README.md
index 49dc779..53e285c 100644
--- a/README.md
+++ b/README.md
@@ -447,7 +447,11 @@
 ### System scope
 
 System scoped preference are applied globally.
-
+* Restore tree state
+  The  navigator is able to save the state of the tree in the Browsers local storage and reopen the tree in the saved state after page reload or logout.
+  
+  The parameter `navigator.restoreTreeState` with value `true` will save the state of the tree.
+  
 * Node provider  
   The navigation tree structure can be defined via a node provider. The default node provider is set in `nucleus/webclient/src/main/webapp/src/app/navigator/defaultnodeprovider.json`.  
   It is recommended not to change the default node provider. Instead new node providers can be added as preferences.  
diff --git a/WebContent/META-INF/MANIFEST.MF b/WebContent/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..254272e
--- /dev/null
+++ b/WebContent/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+
diff --git a/api/atfxadapter/openatfx/build.gradle b/api/atfxadapter/openatfx/build.gradle
index fd1d925..c0a9e5c 100644
--- a/api/atfxadapter/openatfx/build.gradle
+++ b/api/atfxadapter/openatfx/build.gradle
@@ -33,6 +33,7 @@
 	testCompile 'org.apache.commons:commons-math:2.2'
 	testCompile 'org.mockito:mockito-core:2.13.0'
 	testCompile 'org.assertj:assertj-core:3.6.2'
+
 }
 
 configurations.all {
diff --git a/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContext.java b/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContext.java
index 5808de8..be9a36c 100644
--- a/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContext.java
+++ b/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/ATFXContext.java
@@ -69,6 +69,10 @@
 			List<ExtSystemAttribute> extSystemAttrList, File atfxFile) throws AoException {
 		this.parameters = parameters;
 
+		for (Map.Entry<String, String> entry : parameters.entrySet()) {
+			aoSession.setContextString(entry.getKey(), entry.getValue());
+		}
+
 		boolean includeCatalog = Boolean.valueOf(parameters.getOrDefault(INCLUDE_CATALOG, "false"));
 
 		this.modelManager = new ODSModelManager(orb, aoSession, new ATFXEntityConfigRepositoryLoader(includeCatalog));
diff --git a/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/transaction/WriteRequestHandler.java b/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/transaction/WriteRequestHandler.java
index 65c7cd9..11af7d4 100644
--- a/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/transaction/WriteRequestHandler.java
+++ b/api/atfxadapter/src/main/java/org/eclipse/mdm/api/atfxadapter/transaction/WriteRequestHandler.java
@@ -111,8 +111,9 @@
 		values.get(AE_LC_ATTR_AXISTYPE).set(writeRequest.getAxisType());
 		values.get(AE_LC_ATTR_PARAMETERS).set(writeRequest.getGenerationParameters());
 
+		ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
+
 		if (writeRequest.hasValues()) {
-			ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
 			String unitName = writeRequest.getChannel().getUnit().getName();
 			values.put(AE_LC_ATTR_VALUES,
 					valueType.create(AE_LC_ATTR_VALUES, unitName, true, writeRequest.getValues()));
@@ -132,6 +133,9 @@
 				IntStream.range(0, genParamD.length)
 						.forEach(i -> genParamD[i] = ((Number) Array.get(genParamValues, i)).doubleValue());
 				values.get(AE_LC_ATTR_PARAMETERS).set(genParamD);
+
+				// remove GenerationParameters from Values
+				values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
 			}
 
 			// flags
@@ -142,25 +146,28 @@
 				values.get(AE_LC_ATTR_FLAGS).set(flags);
 			}
 		} else if (writeRequest.hasExternalComponents()) {
-			// No values to write (ext comps are used instead), so remove Values attribute from map:
-			values.remove(AE_LC_ATTR_VALUES); 
-			
+			// No values to write (ext comps are used instead), but we have to set the
+			// Values attribute to empty/invalid, based on the rawDataType
+			values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
+
 			// Set global flag as specified in the WriteRequest:
 			values.get(AE_LC_ATTR_GLOBAL_FLAG).set(writeRequest.getGlobalFlag());
-			
+
 			EntityType externalComponentEntityType = insertStatement.getTransaction().getModelManager()
 					.getEntityType("ExternalComponent");
-			
+
 			List<Attribute> listAttrsExtComp = externalComponentEntityType.getAttributes();
-			boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName())).count() > 0);
-			boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName())).count() > 0);
-			boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName())).count() > 0);
+			boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName()))
+					.count() > 0);
+			boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName()))
+					.count() > 0);
+			boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName()))
+					.count() > 0);
 
 			int ordinalNumber = 1;
 			for (ExternalComponent extComp : writeRequest.getExternalComponents()) {
 				Core extCompCore = new DefaultCore(externalComponentEntityType);
-				ExternalComponentEntity extCompEntity = new ExternalComponentEntity(
-						extCompCore);
+				ExternalComponentEntity extCompEntity = new ExternalComponentEntity(extCompCore);
 				extCompEntity.setName(writeRequest.getChannel().getName());
 				if (hasMimeType) {
 					extCompEntity.setMimeType(new MimeType("application/x-asam.aoexternalcomponent"));
@@ -183,7 +190,8 @@
 				}
 
 				core.getChildrenStore().add(extCompEntity);
-				extCompCore.getPermanentStore().set(new BaseEntity(core) {});
+				extCompCore.getPermanentStore().set(new BaseEntity(core) {
+				});
 			}
 		} else {
 			throw new IllegalStateException("Given write request neither has measured values nor external components");
diff --git a/api/atfxadapter/src/test/java/org/eclipse/mdm/api/atfxadapter/AtfxAdapterTest.java b/api/atfxadapter/src/test/java/org/eclipse/mdm/api/atfxadapter/AtfxAdapterTest.java
index 5599695..1184f7a 100644
--- a/api/atfxadapter/src/test/java/org/eclipse/mdm/api/atfxadapter/AtfxAdapterTest.java
+++ b/api/atfxadapter/src/test/java/org/eclipse/mdm/api/atfxadapter/AtfxAdapterTest.java
@@ -36,6 +36,7 @@
 import org.eclipse.mdm.api.base.query.DataAccessException;
 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.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -127,6 +128,17 @@
 	}
 
 	@org.junit.Test
+	public void testLoadProjectChildren() {
+		EntityManager em = context.getEntityManager().get();
+		List<Project> projects = em.loadAll(Project.class);
+
+		assertThat(projects).extracting(Project::getName).containsExactly("PMV Model P");
+
+		List<Pool> pools = em.loadChildren(projects.get(0), Project.CHILD_TYPE_POOL);
+		assertThat(pools).extracting(Pool::getName).contains("PBN Measurements");
+	}
+
+	@org.junit.Test
 	public void testLoadChildren() {
 
 		EntityManager em = context.getEntityManager().get();
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/Attribute.java b/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/Attribute.java
index 0771091..6d50c8c 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/Attribute.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/Attribute.java
@@ -108,7 +108,7 @@
 	 * @return Created {@code Value} is returned.
 	 */
 	default Value createValue(String unit, Object input) {
-		return createValue(unit, true, input);
+		return createValue(unit, input != null, input);
 	}
 
 	/**
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/ModelManager.java b/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/ModelManager.java
index 4c04a30..336b132 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/ModelManager.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/adapter/ModelManager.java
@@ -94,4 +94,12 @@
 	 *                                  does not exist.
 	 */
 	EntityType getEntityTypeById(String id);
+
+	/**
+	 * Returns the default mimeType for the given {@link EntityType}.
+	 * 
+	 * @param entityType
+	 * @return default mimeType
+	 */
+	String getMimeType(EntityType entityType);
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
index 208edf9..0b0f40d 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
@@ -15,12 +15,9 @@
 package org.eclipse.mdm.api.base.massdata;
 
 import java.time.LocalDateTime;
-import java.util.Arrays;
-import java.util.List;
 
 import org.eclipse.mdm.api.base.model.FileLink;
 import org.eclipse.mdm.api.base.model.ScalarType;
-import org.eclipse.mdm.api.base.model.SequenceRepresentation;
 
 /**
  * This builder adds values to the {@link WriteRequest} of types listed below.
@@ -206,44 +203,4 @@
 		createValues(ScalarType.BLOB, values);
 		throw new UnsupportedOperationException("Not implemented.");
 	}
-	
-	public UnitIndependentBuilder externalComponent(ScalarType scalarType, ExternalComponent externalComponent) {
-		return externalComponents(scalarType, null, externalComponent == null ? null : Arrays.asList(externalComponent));
-	}
-	
-	public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag, ExternalComponent externalComponent) {
-		return externalComponents(scalarType, globalFlag, externalComponent == null ? null : Arrays.asList(externalComponent));
-	}
-	
-	public UnitIndependentBuilder externalComponents(ScalarType scalarType, List<ExternalComponent> externalComponents) {
-		return externalComponents(scalarType, null, externalComponents);
-	}
-
-	public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag, List<ExternalComponent> externalComponents) {
-		SequenceRepresentation seqRep = getWriteRequest().getSequenceRepresentation();
-		if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {
-			seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
-		} else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {
-			seqRep = SequenceRepresentation.RAW_LINEAR_EXTERNAL;
-		} else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {
-			seqRep = SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL;
-		} else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {
-			seqRep = SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL;
-		} else if (null == seqRep || !seqRep.isExternal()) {
-			seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
-		}
-		
-		WriteRequest writeRequest = getWriteRequest();
-		writeRequest.setSequenceRepresentation(seqRep);
-		writeRequest.setRawScalarType(scalarType);
-		writeRequest.setGlobalFlag(globalFlag);
-		if (externalComponents != null) {
-			for (ExternalComponent externalComponent : externalComponents) {
-				writeRequest.addExternalComponent(externalComponent);
-			}
-		}
-		
-		return new UnitIndependentBuilder(writeRequest);
-	}
-
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java
new file mode 100644
index 0000000..1a6d00f
--- /dev/null
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java
@@ -0,0 +1,83 @@
+/********************************************************************************

+ * Copyright (c) 2015-2021 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.api.base.massdata;

+

+import java.util.Arrays;

+import java.util.List;

+

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

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

+

+/**

+ * This builder adds {@link ExternalComponent}s to the {@link WriteRequest} . It

+ * is possible to add a sequence where all of its values are valid.

+ *

+ */

+public class ExternalComponentBuilder extends BaseValuesBuilder {

+

+	/**

+	 * Constructor.

+	 *

+	 * @param writeRequest The {@link WriteRequest} given {@link ExternalComponent}s

+	 *                     will be added to.

+	 */

+	ExternalComponentBuilder(WriteRequest writeRequest) {

+		super(writeRequest);

+	}

+

+	public UnitIndependentBuilder externalComponent(ScalarType scalarType, ExternalComponent externalComponent) {

+		return externalComponents(scalarType, null,

+				externalComponent == null ? null : Arrays.asList(externalComponent));

+	}

+

+	public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag,

+			ExternalComponent externalComponent) {

+		return externalComponents(scalarType, globalFlag,

+				externalComponent == null ? null : Arrays.asList(externalComponent));

+	}

+

+	public UnitIndependentBuilder externalComponents(ScalarType scalarType,

+			List<ExternalComponent> externalComponents) {

+		return externalComponents(scalarType, null, externalComponents);

+	}

+

+	public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag,

+			List<ExternalComponent> externalComponents) {

+		SequenceRepresentation seqRep = getWriteRequest().getSequenceRepresentation();

+		if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {

+			seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;

+		} else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {

+			seqRep = SequenceRepresentation.RAW_LINEAR_EXTERNAL;

+		} else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {

+			seqRep = SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL;

+		} else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {

+			seqRep = SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL;

+		} else if (null == seqRep || !seqRep.isExternal()) {

+			seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;

+		}

+

+		WriteRequest writeRequest = getWriteRequest();

+		writeRequest.setSequenceRepresentation(seqRep);

+		writeRequest.setRawScalarType(scalarType);

+		writeRequest.setGlobalFlag(globalFlag);

+		if (externalComponents != null) {

+			for (ExternalComponent externalComponent : externalComponents) {

+				writeRequest.addExternalComponent(externalComponent);

+			}

+		}

+

+		return new UnitIndependentBuilder(writeRequest);

+	}

+}

diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
index f810efc..6477ee4 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
@@ -56,6 +56,18 @@
 	}
 
 	/**
+	 * Configures the {@link WriteRequest} to create an explicit sequence of
+	 * measured values provided as {@link ExternalComponent}s.
+	 *
+	 * @return An {@link ExternalComponentBuilder} is returned.
+	 * @see SequenceRepresentation#EXPLICIT_EXTERNAL
+	 */
+	public ExternalComponentBuilder explicitExternal() {
+		getWriteRequest().setSequenceRepresentation(SequenceRepresentation.EXPLICIT_EXTERNAL);
+		return new ExternalComponentBuilder(getWriteRequest());
+	}
+
+	/**
 	 * Configures the {@link WriteRequest} to create an implicit constant sequence
 	 * of measured values. An implicit sequence allows only numerical
 	 * {@link ScalarType}s as listed below:
@@ -225,6 +237,21 @@
 	}
 
 	/**
+	 * Configures the {@link WriteRequest} to create a raw linear sequence of
+	 * measured values provided as {@link ExternalComponent}s.
+	 *
+	 * @param offset The offset for each value.
+	 * @param factor The factor for each value.
+	 * @return A {@link ExternalComponentBuilder} is returned.
+	 * @see SequenceRepresentation#RAW_LINEAR_EXTERNAL
+	 */
+	public ExternalComponentBuilder rawLinearExternal(double offset, double factor) {
+		getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_LINEAR_EXTERNAL);
+		getWriteRequest().setGenerationParameters(new double[] { offset, factor });
+		return new ExternalComponentBuilder(getWriteRequest());
+	}
+
+	/**
 	 * Configures the {@link WriteRequest} to create a raw polynomial sequence of
 	 * measured values.
 	 *
@@ -254,6 +281,35 @@
 	}
 
 	/**
+	 * Configures the {@link WriteRequest} to create a raw polynomial sequence of
+	 * measured values provided as {@link ExternalComponent}s.
+	 *
+	 * @param coefficients At least 2 coefficients must be provided.
+	 * @return A {@link ExternalComponentBuilder} is returned.
+	 * @throws IllegalArgumentException Thrown if coefficients are missing or their
+	 *                                  length is less than 2.
+	 * @see SequenceRepresentation#RAW_POLYNOMIAL_EXTERNAL
+	 */
+	public ExternalComponentBuilder rawPolynomialExternal(double... coefficients) {
+		if (coefficients == null || coefficients.length < 2) {
+			throw new IllegalArgumentException(
+					"Coefficients either missing or their length is " + "inconsitent with given grade");
+		}
+
+		getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL);
+
+		double[] generationParameters = new double[coefficients.length + 1];
+		generationParameters[0] = coefficients.length - 1;
+		System.arraycopy(coefficients, 0, generationParameters, 1, coefficients.length);
+		getWriteRequest().setGenerationParameters(generationParameters);
+
+		// TODO: currently it is possible to define such a channel as
+		// independent
+		// should we prevent this?!
+		return new ExternalComponentBuilder(getWriteRequest());
+	}
+
+	/**
 	 * Configures the {@link WriteRequest} to create a raw linear calibrated
 	 * sequence of measured values.
 	 *
@@ -268,4 +324,20 @@
 		getWriteRequest().setGenerationParameters(new double[] { offset, factor, calibration });
 		return new ComplexNumericalValuesBuilder(getWriteRequest());
 	}
+
+	/**
+	 * Configures the {@link WriteRequest} to create a raw linear calibrated
+	 * sequence of measured values provided as {@link ExternalComponent}s.
+	 *
+	 * @param offset      The offset for each value.
+	 * @param factor      The factor for each value.
+	 * @param calibration The calibration factor.
+	 * @return A {@link ExternalComponentBuilder} is returned.
+	 * @see SequenceRepresentation#RAW_LINEAR_CALIBRATED_EXTERNAL
+	 */
+	public ExternalComponentBuilder rawLinearCalibratedExternal(double offset, double factor, double calibration) {
+		getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL);
+		getWriteRequest().setGenerationParameters(new double[] { offset, factor, calibration });
+		return new ExternalComponentBuilder(getWriteRequest());
+	}
 }
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/model/MDMLocalization.java b/api/base/src/main/java/org/eclipse/mdm/api/base/model/MDMLocalization.java
new file mode 100644
index 0000000..a7d2c32
--- /dev/null
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/model/MDMLocalization.java
@@ -0,0 +1,41 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.api.base.model;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.mdm.api.base.adapter.Core;
+
+/**
+ * 
+ * @author jst
+ *
+ */
+public class MDMLocalization extends BaseEntity {
+	
+	private static final String NO_TRANSLATION = "_no_translation";
+
+	public MDMLocalization(Core core) {
+		super(core);
+	}
+	
+	public List<String> getTranslations() {
+		String[] stringSeq = getValue("AliasNames").extract();
+		return Arrays.stream(stringSeq).map(s -> s.equalsIgnoreCase(NO_TRANSLATION) ? null : s).collect(Collectors.toList());
+//		return Arrays.stream(stringSeq).collect(Collectors.joining(";"));
+	}
+}
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/model/Value.java b/api/base/src/main/java/org/eclipse/mdm/api/base/model/Value.java
index 3c134c8..30517ff 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/model/Value.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/model/Value.java
@@ -22,6 +22,8 @@
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import org.eclipse.mdm.api.base.search.ContextState;
+
 /**
  * Generic value container. This value container is tightly coupled with its
  * {@link ValueType}. It allows only to store assignment compatible values.
@@ -30,7 +32,7 @@
  * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
  * @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
  */
-public final class Value {
+public class Value {
 
 	// ======================================================================
 	// Class variables
@@ -46,15 +48,19 @@
 	private final String name;
 	private final String unit;
 
-	private boolean initialValid;
-	private Object initialValue;
+	private boolean initialValidMeasured;
+	private Object initialValueMeasured;
+	private boolean initialValidOrdered;
+	private Object initialValueOrdered;
 
-	private boolean valid;
-	private Object value;
+	private boolean validMeasured;
+	private boolean validOrdered;
+	private Object measured;
+	private Object ordered;
 
 	private final Class<?> valueClass;
 	private final String valueTypeDescr;
-	private final Object defaultValue;
+	private final Object defaultValueMeasured;
 
 	// ======================================================================
 	// Constructors
@@ -63,43 +69,60 @@
 	/**
 	 * Constructor.
 	 *
-	 * @param valueType    The associated {@link ValueType}.
-	 * @param name         The name of this container.
-	 * @param unit         The name of the unit.
-	 * @param valid        The initial validity flag.
-	 * @param value        The initial value.
-	 * @param valueClass   Used for type checking upon assignment.
-	 * @param defaultValue Used as null replacement.
+	 * @param valueType            The associated {@link ValueType}.
+	 * @param name                 The name of this container.
+	 * @param unit                 The name of the unit.
+	 * @param validMeasured        The initial validity flag for the measured value
+	 * @param validOrdered         The initial validity flag for the ordered value
+	 * @param valueMeasured        The initial measured value.
+	 * @param valueOrdered         The initial ordered value.
+	 * @param valueClass           Used for type checking upon assignment.
+	 * @param defaultValueMeasured Used as null replacement.
 	 */
-	Value(ValueType<?> valueType, String name, String unit, boolean valid, Object value, Class<?> valueClass,
-			Object defaultValue, String valueTypeDescr) {
+	Value(ValueType<?> valueType, String name, String unit, boolean validMeasured, boolean validOrdered,
+			Object valueMeasured, Object valueOrdered, Class<?> valueClass, Object defaultValueMeasured,
+			String valueTypeDescr) {
 		this.valueType = valueType;
 		this.valueTypeDescr = valueTypeDescr;
 		this.name = name;
 		this.unit = unit == null ? "" : unit;
 
 		this.valueClass = valueClass;
-		this.defaultValue = defaultValue;
+		this.defaultValueMeasured = defaultValueMeasured;
 
 		// set initial value
-		set(value);
+		set(ContextState.MEASURED, valueMeasured);
+		set(ContextState.ORDERED, valueOrdered);
 		// overwrite initial validity flag
-		setValid(valid);
+		setValid(ContextState.MEASURED, validMeasured);
+		setValid(ContextState.ORDERED, validOrdered);
 
 		// preserve initial values
-		initialValid = isValid();
-		initialValue = copy(extract());
+		initialValidMeasured = isValid(ContextState.MEASURED);
+		initialValueMeasured = copy(extract(ContextState.MEASURED));
+
+		initialValidOrdered = isValid(ContextState.ORDERED);
+		initialValueOrdered = copy(extract(ContextState.ORDERED));
+	}
+
+	Value(ValueType<?> valueType, String name, String unit, boolean valid, Object value, Class<?> valueClass,
+			Object defaultValueMeasured, String valueTypeDescr) {
+		this(valueType, name, unit, valid, false, value, null, valueClass, defaultValueMeasured, valueTypeDescr);
 	}
 
 	/**
-	 * Constructor.
+	 * Private constructor which will be mainly used for the merge.
 	 *
-	 * @param origin The origin {@link Value}.
-	 * @param input  The new value.
+	 * @param origin        the value on which the merge was initiated
+	 * @param inputMeasured merged value of the measured part
+	 * @param validMeasured validity flag for the measured part
+	 * @param inputOrdered  merged value of the ordered part
+	 * @param validOrdered  validity flag for the ordered part
 	 */
-	private Value(Value origin, Object input) {
-		this(origin.valueType, origin.name, origin.unit, origin.valid, input, origin.valueClass, origin.defaultValue,
-				origin.valueTypeDescr);
+	private Value(Value origin, Object inputMeasured, boolean validMeasured, Object inputOrdered,
+			boolean validOrdered) {
+		this(origin.valueType, origin.name, origin.unit, validMeasured, validOrdered, inputMeasured, inputOrdered,
+				origin.valueClass, origin.defaultValueMeasured, origin.valueTypeDescr);
 	}
 
 	// ======================================================================
@@ -134,35 +157,61 @@
 	}
 
 	/**
-	 * Returns the validity flag of this value container.
+	 * Returns the validity flag of this value container. Will use measured context.
 	 *
 	 * @return True, if the stored value is marked to be valid.
 	 */
 	public boolean isValid() {
-		Object v = extract();
+		return isValid(ContextState.MEASURED);
+	}
+
+	/**
+	 * Returns the validity flag of this value container.
+	 *
+	 * @param contextState the related context state.
+	 *
+	 * @return True, if the stored value is marked to be valid.
+	 */
+	public boolean isValid(ContextState contextState) {
+		Object v = extract(contextState);
 
 		if (v != null) {
 			if (v.getClass().isArray()) {
-				return valid && Array.getLength(v) > 0;
+				return getValid(contextState) && Array.getLength(v) > 0;
 			} else if (v instanceof String) {
-				return valid && !((String) v).isEmpty();
+				return getValid(contextState) && !((String) v).isEmpty();
 			}
 		}
 
-		return valid && v != null;
+		return getValid(contextState) && v != null;
+	}
+
+	/**
+	 * Overwrites validity flag with given flag. Will use measured context.
+	 *
+	 * @param valid The new validity flag.
+	 */
+	public void setValid(boolean valid) {
+		setValid(ContextState.MEASURED, valid);
 	}
 
 	/**
 	 * Overwrites validity flag with given flag.
 	 *
-	 * @param valid The new validity flag.
+	 * @param contextState the related context state.
+	 * @param valid        The new validity flag.
 	 */
-	public void setValid(boolean valid) {
-		this.valid = valid;
+	public void setValid(ContextState contextState, boolean valid) {
+		if (ContextState.ORDERED.equals(contextState)) {
+			this.validOrdered = valid;
+		} else {
+			this.validMeasured = valid;
+		}
 	}
 
 	/**
-	 * Returns currently stored value of this value container.
+	 * Returns currently stored value of this value container. Will use measured
+	 * context.
 	 *
 	 * @param <T>  The expected value type.
 	 * @param type The {@link ValueType}.
@@ -170,36 +219,53 @@
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T extract(ValueType<T> type) {
-		return (T) value;
+		return (T) measured;
 	}
 
 	/**
-	 * Returns currently stored value of this value container.
+	 * Returns currently stored value of this value container. Will use measured
+	 * context.
 	 *
 	 * @param <T> The expected value type.
 	 * @return Currently stored value is returned.
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T extract() {
-		return (T) value;
+		return extract(ContextState.MEASURED);
 	}
-	
+
 	/**
-	 * Returns currently stored value of this value container, or, if value is invalid, defaultValue
+	 * Returns currently stored value of this value container.
 	 *
-	 * @param <T> The expected value type.
-	 * @param defaultValue The value to return if the currently stored value is invalid
+	 * @param contextState the related context state.
+	 * @return Currently stored value is returned.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T extract(ContextState contextState) {
+		if (ContextState.ORDERED.equals(contextState)) {
+			return (T) ordered;
+		}
+		return (T) measured;
+	}
+
+	/**
+	 * Returns currently stored value of this value container, or, if value is
+	 * invalid, defaultValue
+	 *
+	 * @param <T>          The expected value type.
+	 * @param defaultValue The value to return if the currently stored value is
+	 *                     invalid
 	 * @return Currently stored value is returned.
 	 */
 	@SuppressWarnings("unchecked")
 	public <T> T extractWithDefault(T defaultValue) {
-		return (isValid() ? (T) value : defaultValue);
+		return (isValid() ? (T) measured : defaultValue);
 	}
 
 	/**
 	 * Replaces currently stored value with the given one. If {@code null} is given,
 	 * then a well defined default value is used instead and the validity flag is
-	 * automatically set to {@code false}.
+	 * automatically set to {@code false}. Will use measured context.
 	 *
 	 * @param input The new value must be an instance of the type defined in
 	 *              {@link ValueType#type} or in case of an enumeration type an
@@ -207,14 +273,39 @@
 	 * @throws IllegalArgumentException Thrown if an incompatible value is given.
 	 */
 	public void set(Object input) {
+		set(ContextState.MEASURED, input);
+	}
+
+	/**
+	 * Replaces currently stored value with the given one. If {@code null} is given,
+	 * then a well defined default value is used instead and the validity flag is
+	 * automatically set to {@code false}.
+	 *
+	 * @param contextState the related context state.
+	 * @param input        The new value must be an instance of the type defined in
+	 *                     {@link ValueType#type} or in case of an enumeration type
+	 *                     an appropriate enumeration constant or array thereof.
+	 * @throws IllegalArgumentException Thrown if an incompatible value is given.
+	 */
+	public void set(ContextState contextState, Object input) {
 		if (input == null) {
-			value = defaultValue;
-			setValid(false);
+			if (ContextState.ORDERED.equals(contextState)) {
+				ordered = null;
+				setValid(ContextState.ORDERED, false);
+			} else {
+				measured = defaultValueMeasured;
+				setValid(ContextState.MEASURED, false);
+			}
 		} else if (valueClass.isInstance(input)) {
-			value = input;
-			setValid(true);
+			if (ContextState.ORDERED.equals(contextState)) {
+				ordered = input;
+				setValid(ContextState.ORDERED, true);
+			} else {
+				measured = input;
+				setValid(ContextState.MEASURED, true);
+			}
 		} else if (input instanceof EnumerationValue) {
-			setForEnumerationValue(input);
+			setForEnumerationValue(contextState, input);
 		} else {
 			throw new IllegalArgumentException("Incompatible value type '" + input.getClass().getSimpleName()
 					+ "' passed, expected '" + valueClass.getSimpleName() + "'.");
@@ -222,6 +313,19 @@
 	}
 
 	/**
+	 * Will swap internal values for the measured and ordered context.
+	 */
+	public void swapContext() {
+		Object obj = measured;
+		boolean valid = validMeasured;
+
+		measured = ordered;
+		validMeasured = validOrdered;
+		ordered = obj;
+		validOrdered = valid;
+	}
+
+	/**
 	 * Merges given value container with this instance. To be able to do so, the
 	 * given value container must be compatible with this one. Value containers are
 	 * compatible if the their name, unit and {@link ValueType} is equal. If the
@@ -234,16 +338,15 @@
 	 *                                  compatible.
 	 */
 	public Value merge(Value value) {
-		boolean nameMissmatch = !getName().equals(value.getName());
-		boolean unitMissmatch = !getUnit().equals(value.getUnit());
-		boolean typeMissmatch = !getValueType().equals(value.getValueType());
-		if (nameMissmatch || unitMissmatch || typeMissmatch) {
+		if (!isValidMerge(value)) {
 			throw new IllegalArgumentException("Unable to merge, incompatible value passed.");
 		}
 
-		boolean equalValue = Objects.deepEquals(extract(), value.extract());
-		boolean bothValid = isValid() && value.isValid();
-		return new Value(this, equalValue && bothValid ? extract() : null);
+		Merged measuredValue = getMergedValue(value, ContextState.MEASURED);
+		Merged orderedValue = getMergedValue(value, ContextState.ORDERED);
+
+		return new Value(this, measuredValue.getValue(), measuredValue.isValid(), orderedValue.getValue(),
+				orderedValue.isValid());
 	}
 
 	/*
@@ -263,11 +366,17 @@
 		Value val = (Value) other;
 
 		return Objects.equals(this.valueType, val.valueType) && Objects.equals(this.name, val.name)
-				&& Objects.equals(this.unit, val.unit) && Objects.equals(this.initialValid, val.initialValid)
-				&& Objects.deepEquals(this.initialValue, val.initialValue) && Objects.equals(this.valid, val.valid)
-				&& Objects.deepEquals(this.value, val.value) && Objects.equals(this.valueClass, val.valueClass)
+				&& Objects.equals(this.unit, val.unit)
+				&& Objects.equals(this.initialValidMeasured, val.initialValidMeasured)
+				&& Objects.deepEquals(this.validMeasured, val.validMeasured)
+				&& Objects.equals(this.validOrdered, val.validOrdered)
+				&& Objects.deepEquals(this.measured, val.measured) && Objects.deepEquals(this.ordered, val.ordered)
+				&& Objects.equals(this.valueClass, val.valueClass)
+				&& Objects.equals(this.initialValueMeasured, val.initialValueMeasured)
+				&& Objects.equals(this.initialValidOrdered, val.initialValidOrdered)
+				&& Objects.equals(this.initialValueOrdered, val.initialValueOrdered)
 				&& Objects.equals(this.valueTypeDescr, val.valueTypeDescr)
-				&& Objects.equals(this.defaultValue, val.defaultValue);
+				&& Objects.equals(this.defaultValueMeasured, val.defaultValueMeasured);
 	}
 
 	/*
@@ -277,8 +386,9 @@
 	 */
 	@Override
 	public int hashCode() {
-		return Objects.hash(valueType, name, unit, initialValid, initialValue, valid, value, valueClass, valueTypeDescr,
-				defaultValue);
+		return Objects.hash(valueType, name, unit, initialValidMeasured, initialValidOrdered, initialValueMeasured,
+				initialValueOrdered, validMeasured, validOrdered, measured, ordered, valueClass, valueTypeDescr,
+				defaultValueMeasured);
 	}
 
 	/**
@@ -292,25 +402,29 @@
 	 */
 	@Override
 	public String toString() {
-		StringBuilder sb = new StringBuilder(name).append(" = ");
+		StringBuilder sb = new StringBuilder();
 
-		if (isValid()) {
+		boolean measuredValid = isValid();
+		boolean orderedValid = isValid(ContextState.ORDERED);
+
+		if (measuredValid && !orderedValid) {
 			Object v = extract();
-			if (v != null && v.getClass().isArray()) {
-				int length = Array.getLength(v);
-				sb.append('[');
+			sb.append(name).append(" = ");
+			sb.append(valueToString(v));
+		} else if (measuredValid && orderedValid) {
+			Object v = extract();
+			sb.append(name).append("(measured)").append(" = ");
+			sb.append(valueToString(v));
+		}
 
-				if (length > 10) {
-					sb.append(range(0, 5).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
-					sb.append(", ..., ");
-					sb.append(range(length - 5, length).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
-				} else {
-					sb.append(range(0, length).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
-				}
-				sb.append(']');
-			} else {
-				sb.append(v);
-			}
+		if (measuredValid && orderedValid) {
+			sb.append(", ");
+		}
+
+		if (orderedValid) {
+			sb.append(name).append("(ordered)").append(" = ");
+			Object v = extract(ContextState.ORDERED);
+			sb.append(valueToString(v));
 		}
 
 		if (!getUnit().isEmpty()) {
@@ -337,7 +451,20 @@
 	 * @return Returns {@code true} if the value was initially marked as valid.
 	 */
 	public boolean wasValid() {
-		return initialValid;
+		return wasValid(ContextState.MEASURED);
+	}
+
+	/**
+	 * Returns the initial validity flag.
+	 *
+	 * @return Returns {@code true} if the value was initially marked as valid.
+	 */
+	public boolean wasValid(ContextState contextState) {
+		if (ContextState.ORDERED.equals(contextState)) {
+			return initialValidOrdered;
+		} else {
+			return initialValidMeasured;
+		}
 	}
 
 	/**
@@ -346,15 +473,30 @@
 	 * @return The initial value is returned.
 	 */
 	public Object extractInitial() {
-		return initialValue;
+		return extractInitial(ContextState.MEASURED);
+	}
+
+	/**
+	 * Returns the initial value.
+	 *
+	 * @return The initial value is returned.
+	 */
+	public Object extractInitial(ContextState contextState) {
+		if (ContextState.ORDERED.equals(contextState)) {
+			return initialValueOrdered;
+		} else {
+			return initialValueMeasured;
+		}
 	}
 
 	/**
 	 * Overwrites the initial validity flag and value with the current ones.
 	 */
 	public void apply() {
-		initialValid = isValid();
-		initialValue = copy(extract());
+		initialValidMeasured = isValid(ContextState.MEASURED);
+		initialValueMeasured = copy(extract(ContextState.MEASURED));
+		initialValidOrdered = isValid(ContextState.ORDERED);
+		initialValueOrdered = copy(extract(ContextState.ORDERED));
 	}
 
 	// ======================================================================
@@ -382,6 +524,48 @@
 	// ======================================================================
 
 	/**
+	 * Creates a string representation of the current value object.
+	 *
+	 * @param v the value object
+	 * @return string representation
+	 */
+	private String valueToString(Object v) {
+		StringBuilder sb = new StringBuilder();
+		if (v != null && v.getClass().isArray()) {
+			int length = Array.getLength(v);
+			sb.append('[');
+
+			if (length > 10) {
+				sb.append(range(0, 5).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
+				sb.append(", ..., ");
+				sb.append(range(length - 5, length).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
+			} else {
+				sb.append(range(0, length).mapToObj(i -> readAt(v, i)).collect(Collectors.joining(", ")));
+			}
+			sb.append(']');
+		} else {
+			sb.append(v);
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * Returns the validity flag for the given context.
+	 *
+	 * @param contextState the context.
+	 * @return <code>true</code> if the valid for context, <code>false</code>
+	 *         otherwise.
+	 */
+	private boolean getValid(ContextState contextState) {
+		if (ContextState.ORDERED.equals(contextState)) {
+			return validOrdered;
+		} else {
+			return validMeasured;
+		}
+	}
+
+	/**
 	 * Returns a copy of given {@code Object}, so modifications in one do not affect
 	 * to other.
 	 *
@@ -411,7 +595,7 @@
 	 *              appropriate enumeration constant.
 	 * @throws IllegalArgumentException Thrown if an incompatible value is given.
 	 */
-	private void setForEnumerationValue(Object input) {
+	private void setForEnumerationValue(ContextState contextState, Object input) {
 		String inpvalueTypeDescr = ((EnumerationValue) input).getOwner().getName();
 		if (inpvalueTypeDescr == null) {
 			throw new IllegalArgumentException(
@@ -424,8 +608,13 @@
 		}
 
 		if (valueTypeDescr.equals(inpvalueTypeDescr)) {
-			value = input;
-			setValid(true);
+			if (ContextState.ORDERED.equals(contextState)) {
+				ordered = input;
+				setValid(ContextState.ORDERED, true);
+			} else {
+				measured = input;
+				setValid(ContextState.MEASURED, true);
+			}
 		} else {
 			throw new IllegalArgumentException("Incompatible value type description'" + inpvalueTypeDescr
 					+ "' passed, expected '" + valueTypeDescr + "'.");
@@ -433,6 +622,51 @@
 	}
 
 	/**
+	 * Check wether the given value can be merged with the current one or not. Name,
+	 * unit and type must match.
+	 *
+	 * @param value the value which should be checked.
+	 * @return <code>true</code> if name, unit and type match, <code>false</code>
+	 *         otherwise.
+	 */
+	private boolean isValidMerge(Value value) {
+		boolean nameMatch = getName().equals(value.getName());
+		boolean unitMatch = getUnit().equals(value.getUnit());
+		boolean typeMatch = getValueType().equals(value.getValueType());
+
+		return nameMatch && unitMatch && typeMatch;
+	}
+
+	/**
+	 * Returns the {@link Merged} representation for a given value and the context.
+	 *
+	 * @param value        value which should be used.
+	 * @param contextState the context.
+	 * @return the merged representation
+	 */
+	private Merged getMergedValue(Value value, ContextState contextState) {
+		Merged result = new Merged(null, false);
+
+		boolean equalValues = Objects.deepEquals(extract(contextState), value.extract(contextState));
+
+		boolean bothValid = isValid(contextState) && value.isValid(contextState);
+		boolean sourceValid = isValid(contextState) && !value.isValid(contextState);
+		boolean targetValid = !isValid(contextState) && value.isValid(contextState);
+
+		if ((equalValues && bothValid) || sourceValid) {
+			Object obj = extract(contextState);
+			boolean valid = isValid(contextState);
+			result = new Merged(obj, valid);
+		} else if (targetValid) {
+			Object obj = value.extract(contextState);
+			boolean valid = value.isValid(contextState);
+			result = new Merged(obj, valid);
+		}
+
+		return result;
+	}
+
+	/**
 	 * Returns a deep copy of given {@code Object}, so modifications in one do not
 	 * affect to other.
 	 *
@@ -449,7 +683,7 @@
 			return copy;
 		} else {
 			if (value instanceof byte[][]) {
-				return Arrays.stream((byte[][]) value).map(v -> v.clone()).toArray(byte[][]::new);
+				return Arrays.stream((byte[][]) value).map(byte[]::clone).toArray(byte[][]::new);
 			} else if (value instanceof FileLink[]) {
 				return Arrays.stream((FileLink[]) value).map(FileLink::new).toArray(FileLink[]::new);
 			} else {
@@ -457,4 +691,26 @@
 			}
 		}
 	}
+
+	/**
+	 * Internal class which allows to transport a value and validity flag during
+	 * {@link Value#merge(Value)}.
+	 */
+	private static class Merged {
+		Object value;
+		boolean valid;
+
+		public Merged(final Object value, final boolean valid) {
+			this.value = value;
+			this.valid = valid;
+		}
+
+		public Object getValue() {
+			return value;
+		}
+
+		public boolean isValid() {
+			return valid;
+		}
+	}
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/model/ValueType.java b/api/base/src/main/java/org/eclipse/mdm/api/base/model/ValueType.java
index 329699f..f17b8fd 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/model/ValueType.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/model/ValueType.java
@@ -318,7 +318,7 @@
 			throw new IllegalStateException("This value type is an enumeration type.");
 		}
 
-		return new Value(this, name, unit, valid, input, type, defaultValue, null);
+		return new Value(this, name, unit, valid, false, input, null, type, defaultValue, null);
 	}
 
 	/**
@@ -368,7 +368,7 @@
 				valueClass = nullReplacement.getClass();
 			}
 
-			return new Value(this, name, unit, valid, input, valueClass, nullReplacement, valueTypeDescr);
+			return new Value(this, name, unit, valid, false, input, null, valueClass, nullReplacement, valueTypeDescr);
 		}
 
 		throw new IllegalStateException("This value type is not an enumeration type.");
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/BooleanOperator.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/BooleanOperator.java
index 2aed570..6b1758f 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/BooleanOperator.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/BooleanOperator.java
@@ -42,6 +42,24 @@
 	/**
 	 * Logical Negation.
 	 */
-	NOT
+	NOT;
 
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see java.lang.Enum#toString()
+	 */
+	@Override
+	public String toString() {
+		switch (this) {
+		case AND:
+			return "and";
+		case OR:
+			return "or";
+		case NOT:
+			return "not";
+		default:
+			return this.toString();
+		}
+	}
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/ComparisonOperator.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/ComparisonOperator.java
index 09e4910..d01417f 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/ComparisonOperator.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/ComparisonOperator.java
@@ -18,6 +18,7 @@
 
 import org.eclipse.mdm.api.base.adapter.Attribute;
 import org.eclipse.mdm.api.base.model.ValueType;
+import org.eclipse.mdm.api.base.search.ContextState;
 
 /**
  * The selection operations define query instructions like "equal", "greater",
@@ -171,6 +172,25 @@
 		return new Condition(attribute, this, "", value);
 	}
 
+	/**
+	 * Creates a new {@link Condition} for given {@link Attribute} and value.
+	 *
+	 * <p>
+	 * Be aware: Right now filter only support one {@link ContextState} per filter.
+	 * {@link Filter#merge} and {@link Filter#add} will throw an exception when you
+	 * try to use different {@link ContextState} within a filter.
+	 * </p>
+	 *
+	 * @param contextState The {@link ContextState} which will be applied to the
+	 *                     condition.
+	 * @param attribute    The {@code Attribute} the condition will be applied to.
+	 * @param value        The value.
+	 * @return The created {@code Condition} is returned.
+	 */
+	public Condition create(ContextState contextState, Attribute attribute, Object value) {
+		return new Condition(contextState, attribute, this, "", value);
+	}
+
 	// ======================================================================
 	// Package methods
 	// ======================================================================
@@ -194,4 +214,63 @@
 				.contains(this);
 	}
 
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see java.lang.Enum#toString()
+	 */
+	@Override
+	public String toString() {
+		switch (this) {
+		case BETWEEN:
+			return "bw";
+		case CASE_INSENSITIVE_EQUAL:
+			return "ci_eq";
+		case CASE_INSENSITIVE_GREATER_THAN:
+			return "ci_ne";
+		case CASE_INSENSITIVE_GREATER_THAN_OR_EQUAL:
+			return "ci_ge";
+		case CASE_INSENSITIVE_IN_SET:
+			return "ci_in";
+		case CASE_INSENSITIVE_LESS_THAN:
+			return "ci_lt";
+		case CASE_INSENSITIVE_LESS_THAN_OR_EQUAL:
+			return "ci_le";
+		case CASE_INSENSITIVE_LIKE:
+			return "ci_lk";
+		case CASE_INSENSITIVE_NOT_EQUAL:
+			return "ci_ne";
+		case CASE_INSENSITIVE_NOT_IN_SET:
+			return "ci_not_in";
+		case CASE_INSENSITIVE_NOT_LIKE:
+			return "ci_not_lk";
+		case EQUAL:
+			return "eq";
+		case GREATER_THAN:
+			return "gt";
+		case GREATER_THAN_OR_EQUAL:
+			return "ge";
+		case IN_SET:
+			return "in";
+		case IS_NOT_NULL:
+			return "is_not_null";
+		case IS_NULL:
+			return "is_null";
+		case LESS_THAN:
+			return "lt";
+		case LESS_THAN_OR_EQUAL:
+			return "le";
+		case LIKE:
+			return "lk";
+		case NOT_EQUAL:
+			return "ne";
+		case NOT_IN_SET:
+			return "not_in";
+		case NOT_LIKE:
+			return "not_lk";
+		default:
+			return this.toString();
+		}
+	}
+
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Condition.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Condition.java
index afe6c7d..dbba766 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Condition.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Condition.java
@@ -18,6 +18,7 @@
 
 import org.eclipse.mdm.api.base.adapter.Attribute;
 import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.base.search.ContextState;
 
 /**
  * Describes a filter condition.
@@ -37,6 +38,7 @@
 	private final Attribute attribute;
 	private final Value value;
 	private final ComparisonOperator comparisonOperator;
+	private final ContextState contextState;
 
 	// ======================================================================
 	// Constructors
@@ -52,6 +54,21 @@
 	 * @param input              The condition value.
 	 */
 	Condition(Attribute attribute, ComparisonOperator comparisonOperator, String unit, Object input) {
+		this(null, attribute, comparisonOperator, unit, input);
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param contextState       The {@link ContextState} which will be applied to this condition.
+	 * @param attribute          The {@link Attribute} this condition will be
+	 *                           applied to.
+	 * @param comparisonOperator The condition {@link ComparisonOperator}.
+	 * @param unit               The unit of the created value.
+	 * @param input              The condition value.
+	 */
+	Condition(ContextState contextState, Attribute attribute, ComparisonOperator comparisonOperator, String unit, Object input) {
+		this.contextState = contextState;
 		this.attribute = attribute;
 		this.comparisonOperator = comparisonOperator;
 		value = comparisonOperator.requiresSequence() ? attribute.createValueSeq(unit, input)
@@ -89,6 +106,15 @@
 		return comparisonOperator;
 	}
 
+	/**
+	 * Returns the {@link ContextState}.
+	 *
+	 * @return the {@link ContextState}.
+	 */
+	public ContextState getContextState() {
+		return contextState;
+	}
+
 	/*
 	 * (non-Javadoc)
 	 * 
@@ -106,7 +132,8 @@
 		Condition condition = (Condition) other;
 
 		return Objects.equals(this.attribute, condition.attribute) && Objects.equals(this.value, condition.value)
-				&& Objects.equals(this.comparisonOperator, condition.comparisonOperator);
+				&& Objects.equals(this.comparisonOperator, condition.comparisonOperator)
+				&& Objects.equals(this.contextState, condition.contextState);
 	}
 
 	/*
@@ -116,7 +143,7 @@
 	 */
 	@Override
 	public int hashCode() {
-		return Objects.hash(attribute, value, comparisonOperator);
+		return Objects.hash(attribute, value, comparisonOperator, contextState);
 	}
 
 	/*
@@ -126,6 +153,7 @@
 	 */
 	@Override
 	public String toString() {
-		return attribute.getEntityType().getName() + "." + attribute.getName() + " " + comparisonOperator + " " + value;
+		String context = contextState != null ? contextState.toString() + "." : "";
+		return context + attribute.getEntityType().getName() + "." + attribute.getName() + " " + comparisonOperator + " " + value;
 	}
 }
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Filter.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Filter.java
index 3b48be4..10f8761 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Filter.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Filter.java
@@ -21,11 +21,13 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.adapter.Relation;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.base.search.SearchService;
 
@@ -40,7 +42,7 @@
  * @see SearchQuery
  * @see SearchService
  */
-public final class Filter implements Iterable<FilterItem> {
+public class Filter implements Iterable<FilterItem> {
 
 	// ======================================================================
 	// Instance variables
@@ -224,6 +226,23 @@
 		return this;
 	}
 
+	// TODO add ContextSteate param to other methods, too?
+	/**
+	 * Adds a new instance ID condition ({@link ComparisonOperator#EQUAL}) for given
+	 * {@link EntityType} and instance ID to this filter.
+	 *
+	 * @param contextState The ContextState which will be applied to the created
+	 *                     condition.
+	 * @param entityType   The {@code EntityType}.
+	 * @param id           The instance ID.
+	 * @return Returns this filter.
+	 * @see #add(Condition)
+	 */
+	public Filter id(ContextState contextState, EntityType entityType, String id) {
+		add(ComparisonOperator.EQUAL.create(contextState, entityType.getIDAttribute(), id));
+		return this;
+	}
+
 	/**
 	 * Adds a new foreign ID condition ({@link ComparisonOperator#EQUAL}) for given
 	 * {@link Relation} and instance ID to this filter.
@@ -311,8 +330,14 @@
 	 *
 	 * @param condition {@code Condition} that will be added.
 	 * @return Returns this filter.
+	 * @throws IllegalStateException when a condition with another
+	 *                               {@link ContextState} should be added.
 	 */
 	public Filter add(Condition condition) {
+		if (condition != null && hasContext() && condition.getContextState() != null
+				&& !getContext().equals(condition.getContextState())) {
+			throw new IllegalStateException("Cannot add another context to a filter which already has context");
+		}
 		insertOperator();
 		filterItems.addLast(new FilterItem(condition));
 		return this;
@@ -365,6 +390,8 @@
 	 *
 	 * @param filter Filter that will be merged.
 	 * @return Returns this filter.
+	 * @throws IllegalStateException when a filter with another {@link ContextState}
+	 *                               should be merged.
 	 */
 	public Filter merge(Filter filter) {
 		boolean addBrackets = filter.hasMultiple() && operatorItem != filter.operatorItem && !filter.isInverted();
@@ -374,7 +401,11 @@
 			filterItems.addLast(FilterItem.OPEN);
 		}
 
-		filter.filterItems.stream().forEach(filterItems::addLast);
+		if (!hasValidContext(filter)) {
+			throw new IllegalStateException("Filter with different contexts cannot be merged");
+		} else {
+			filter.filterItems.forEach(filterItems::addLast);
+		}
 
 		if (addBrackets) {
 			filterItems.addLast(FilterItem.CLOSE);
@@ -424,6 +455,12 @@
 		return this;
 	}
 
+	public Filter copy() {
+		Filter f = new Filter(this.operatorItem);
+		f.filterItems.addAll(this.filterItems);
+		return f;
+	}
+
 	/**
 	 * Checks whether this filter has no conditions at all.
 	 *
@@ -467,6 +504,28 @@
 		};
 	}
 
+	/**
+	 * Returns <code>true</code> if this filter has a {@link ContextState},
+	 * <code>false</code> otherwise.
+	 *
+	 * @return <code>true</code> if this filter has a {@link ContextState},
+	 *         <code>false</code> otherwise.
+	 */
+	public boolean hasContext() {
+		return streamOfContextState().distinct().count() > 0;
+	}
+
+	/**
+	 * Returns the current {@link ContextState} or <code>null</code> if no context
+	 * is used in this filter.
+	 *
+	 * @return the {@link ContextState} or <code>null</code> if no context is used
+	 *         in this filter.
+	 */
+	public ContextState getContext() {
+		return streamOfContextState().findAny().orElse(null);
+	}
+
 	// ======================================================================
 	// Private methods
 	// ======================================================================
@@ -500,6 +559,53 @@
 		return filterItems.size() > 1;
 	}
 
+	/**
+	 * Returns a {@link Stream<ContextState>}.
+	 *
+	 * @return the stream with context.
+	 */
+	private Stream<ContextState> streamOfContextState() {
+		return filterItems.stream().filter(FilterItem::isCondition).map(FilterItem::getCondition)
+				.map(Condition::getContextState).filter(Objects::nonNull);
+	}
+
+	/**
+	 * Checks whether the given {@link Filter} has a valid context regarding this
+	 * filter.
+	 *
+	 * @param filter the filter which should be checked.
+	 * @return <code>true</code> if the given filter has a valid context,
+	 *         <code>false</code> otherwise.
+	 */
+	private boolean hasValidContext(Filter filter) {
+		if (hasContext()) {
+			ContextState contextState = getContext();
+			Predicate<FilterItem> conditionCheck = createConditionCheck(contextState);
+			return filter.filterItems.stream().allMatch(conditionCheck);
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * Creates a {@link Predicate<FilterItem>} with the given {@link ContextState}.
+	 *
+	 * @param contextState the context state which should be used for the predicate.
+	 * @return the created predicate.
+	 */
+	private Predicate<FilterItem> createConditionCheck(ContextState contextState) {
+		return filterItem -> {
+			if (filterItem.isCondition()) {
+				Condition condition = filterItem.getCondition();
+				if (condition.getContextState() != null) {
+					return contextState.equals(condition.getContextState());
+				}
+			}
+
+			return true;
+		};
+	}
+
 	/*
 	 * (non-Javadoc)
 	 * 
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Record.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Record.java
index c4bfa64..4fdb49b 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Record.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Record.java
@@ -32,7 +32,7 @@
  * @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
  * @see Attribute
  */
-public final class Record {
+public class Record {
 
 	// ======================================================================
 	// Instance variables
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Result.java b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Result.java
index 3d98131..d27f15f 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/query/Result.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/query/Result.java
@@ -32,7 +32,7 @@
  * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
  * @author Sebastian Dirsch, Gigatronik Ingolstadt GmbH
  */
-public final class Result implements Iterable<Record> {
+public class Result implements Iterable<Record> {
 
 	private final Map<EntityType, Record> records = new HashMap<>();
 
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ContextState.java b/api/base/src/main/java/org/eclipse/mdm/api/base/search/ContextState.java
similarity index 95%
rename from api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ContextState.java
rename to api/base/src/main/java/org/eclipse/mdm/api/base/search/ContextState.java
index b02fb5f..fb85715 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ContextState.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/search/ContextState.java
@@ -12,7 +12,7 @@
  *
  ********************************************************************************/
 
-package org.eclipse.mdm.api.odsadapter.search;
+package org.eclipse.mdm.api.base.search;
 
 import org.eclipse.mdm.api.base.model.ContextRoot;
 import org.eclipse.mdm.api.base.model.Measurement;
@@ -24,7 +24,7 @@
  * @since 1.0.0
  * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
  */
-enum ContextState {
+public enum ContextState {
 
 	// ======================================================================
 	// Enum constants
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 6be838a..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
@@ -95,6 +95,21 @@
 	List<Value> getFilterValues(Attribute attribute, 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
 	 * EntityType}s must be fully supported by this search query. This method
 	 * selects all {@link Attribute}s of each given {@code EntityType}.
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 4ea388e..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
@@ -127,6 +127,23 @@
 			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.
 	 * The {@code EntityType}s must be fully supported by the {@code SearchQuery}
 	 * associated with given {@link Entity} type. This method selects all
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/dflt/model/EntityFactory.java b/api/base/src/main/java/org/eclipse/mdm/api/dflt/model/EntityFactory.java
index d18d760..dfe9fde 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/dflt/model/EntityFactory.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/dflt/model/EntityFactory.java
@@ -1211,9 +1211,9 @@
 		} else if (!name.matches("^[\\w]+$")) {
 			throw new IllegalArgumentException(
 					"A calatog name may only constists of the " + "following characters: a-z, A-Z, 0-9 or _.");
-		} else if (isAttributeName && Arrays.asList("id", "name", "mimetype").contains(name.toLowerCase(Locale.ROOT))) {
+		} else if (isAttributeName && Arrays.asList("Id", "Name", "Mimetype").contains(name)) {
 			throw new IllegalArgumentException(
-					"A catalog attribute name is not allowed to be " + "'id', 'name' or 'mimetype' (case ignored).");
+					"A catalog attribute name is not allowed to be " + "'Id', 'Name' or 'Mimetype'.");
 		}
 	}
 
diff --git a/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java b/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
index 3e791b8..f80d7c0 100644
--- a/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
+++ b/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
@@ -36,7 +36,7 @@
 

 	@Test

 	public void testOneExternalComponent() {

-		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit()

+		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()

 				.externalComponent(ScalarType.INTEGER, externalComponent1).independent().build();

 

 		assertThat(r1).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

@@ -46,7 +46,7 @@
 

 	@Test

 	public void testMultipleExternalComponents() {

-		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit()

+		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()

 				.externalComponents(ScalarType.INTEGER, Arrays.asList(externalComponent1, externalComponent2))

 				.independent().build();

 

@@ -58,29 +58,26 @@
 	@Test

 	public void testMultipleExternalComponentsCopyExtComp() {

 

-		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit().externalComponents(

-				ScalarType.INTEGER,

-				Arrays.asList(new ExternalComponent[] { (new ExternalComponent())

-				.setTypeSpecification(externalComponent1.getTypeSpecification())

-				.setStartOffset(externalComponent1.getStartOffset())

-				.setBlocksize(externalComponent1.getBlocksize())

-				.setValuesPerBlock(externalComponent1.getValuesPerBlock())

-				.setLength(externalComponent1.getLength())

-				.setFileLink(externalComponent1.getFileLink())

-				.setFlagsFileLink(externalComponent1.getFlagsFileLink())

-				.setFlagsStartOffset(externalComponent1.getFlagsStartOffset())

-				.setBitCount(externalComponent1.getBitCount())

-				.setBitOffset(externalComponent1.getBitOffset()),

-				(new ExternalComponent()).setTypeSpecification(externalComponent2.getTypeSpecification())

-				.setStartOffset(externalComponent2.getStartOffset())

-				.setBlocksize(externalComponent2.getBlocksize())

-				.setValuesPerBlock(externalComponent2.getValuesPerBlock())

-				.setLength(externalComponent2.getLength())

-				.setFileLink(externalComponent2.getFileLink())

-				.setFlagsFileLink(externalComponent2.getFlagsFileLink())

-				.setFlagsStartOffset(externalComponent2.getFlagsStartOffset())

-				.setBitCount(externalComponent2.getBitCount())

-				.setBitOffset(externalComponent2.getBitOffset())}))

+		WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()

+				.externalComponents(ScalarType.INTEGER, Arrays.asList(new ExternalComponent[] {

+						(new ExternalComponent()).setTypeSpecification(externalComponent1.getTypeSpecification())

+								.setStartOffset(externalComponent1.getStartOffset())

+								.setBlocksize(externalComponent1.getBlocksize())

+								.setValuesPerBlock(externalComponent1.getValuesPerBlock())

+								.setLength(externalComponent1.getLength()).setFileLink(externalComponent1.getFileLink())

+								.setFlagsFileLink(externalComponent1.getFlagsFileLink())

+								.setFlagsStartOffset(externalComponent1.getFlagsStartOffset())

+								.setBitCount(externalComponent1.getBitCount())

+								.setBitOffset(externalComponent1.getBitOffset()),

+						(new ExternalComponent()).setTypeSpecification(externalComponent2.getTypeSpecification())

+								.setStartOffset(externalComponent2.getStartOffset())

+								.setBlocksize(externalComponent2.getBlocksize())

+								.setValuesPerBlock(externalComponent2.getValuesPerBlock())

+								.setLength(externalComponent2.getLength()).setFileLink(externalComponent2.getFileLink())

+								.setFlagsFileLink(externalComponent2.getFlagsFileLink())

+								.setFlagsStartOffset(externalComponent2.getFlagsStartOffset())

+								.setBitCount(externalComponent2.getBitCount())

+								.setBitOffset(externalComponent2.getBitOffset()) }))

 				.independent().build();

 

 		assertThat(r1).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

diff --git a/api/base/src/test/java/org/eclipse/mdm/api/base/model/ModelTest.java b/api/base/src/test/java/org/eclipse/mdm/api/base/model/ModelTest.java
index 2700162..ede2991 100755
--- a/api/base/src/test/java/org/eclipse/mdm/api/base/model/ModelTest.java
+++ b/api/base/src/test/java/org/eclipse/mdm/api/base/model/ModelTest.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.HashMap;
@@ -29,6 +30,7 @@
 import org.eclipse.mdm.api.base.massdata.WriteRequest;
 import org.eclipse.mdm.api.base.massdata.WriteRequestBuilder;
 import org.eclipse.mdm.api.base.model.MeasuredValues.ValueIterator;
+import org.eclipse.mdm.api.base.search.ContextState;
 
 /**
  * 
@@ -279,14 +281,14 @@
 		assertEquals(ValueType.ENUMERATION.toSingleType(), ValueType.ENUMERATION);
 		assertEquals(ValueType.ENUMERATION, ValueType.ENUMERATION.valueOf(ValueType.ENUMERATION.toSingleType().name()));
 		assertEquals(Float.class, ValueType.FLOAT.getValueClass());
-		assertEquals(true, ValueType.DOUBLE.isAnyFloatType());
-		assertEquals(true, ValueType.DOUBLE.isDouble());
-		assertEquals(false, ValueType.DOUBLE.isFloat());
-		assertEquals(true, ValueType.FLOAT.isAnyFloatType());
-		assertEquals(false, ValueType.INTEGER.isAnyFloatType());
-		assertEquals(true, ValueType.INTEGER.isInteger());
-		assertEquals(false, ValueType.FLOAT.isInteger());
-		assertEquals(true, ValueType.FLOAT.isFloat());
+		assertTrue(ValueType.DOUBLE.isAnyFloatType());
+		assertTrue(ValueType.DOUBLE.isDouble());
+		assertFalse(ValueType.DOUBLE.isFloat());
+		assertTrue(ValueType.FLOAT.isAnyFloatType());
+		assertFalse(ValueType.INTEGER.isAnyFloatType());
+		assertTrue(ValueType.INTEGER.isInteger());
+		assertFalse(ValueType.FLOAT.isInteger());
+		assertTrue(ValueType.FLOAT.isFloat());
 	}
 
 	/**
@@ -295,8 +297,8 @@
 	@org.junit.Test
 	public void scalarType() {
 		assertEquals(ValueType.BYTE_SEQUENCE, ScalarType.BYTE.toValueType());
-		assertEquals(true, ScalarType.LONG.isLong());
-		assertEquals(false, ScalarType.DOUBLE_COMPLEX.isLong());
+		assertTrue(ScalarType.LONG.isLong());
+		assertFalse(ScalarType.DOUBLE_COMPLEX.isLong());
 	}
 
 	/**
@@ -373,4 +375,132 @@
 		assertTrue(er.get("Interpolation") != null);
 	}
 
+	@org.junit.Test
+	public void measuredValue() {
+		Value measured = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				"abc", null);
+
+		assertEquals("abc", measured.extract());
+		assertTrue(measured.isValid());
+
+		assertEquals("abc", measured.extract(ContextState.MEASURED));
+		assertTrue(measured.isValid(ContextState.MEASURED));
+
+		assertNull(measured.extract(ContextState.ORDERED));
+		assertFalse(measured.isValid(ContextState.ORDERED));
+
+		measured.set("xyz");
+		assertEquals("xyz", measured.extract());
+		assertEquals("xyz", measured.extract(ContextState.MEASURED));
+	}
+
+	@org.junit.Test
+	public void orderedValue() {
+		Value ordered = new Value(ValueType.STRING, "Foo", null, false, true,
+				null, "abc", String.class,
+				null, null);
+
+		assertEquals("abc", ordered.extract(ContextState.ORDERED));
+		assertTrue(ordered.isValid(ContextState.ORDERED));
+
+		assertNull(ordered.extract(ContextState.MEASURED));
+		assertFalse(ordered.isValid(ContextState.MEASURED));
+
+		ordered.set(ContextState.ORDERED, "xyz");
+		assertNull(ordered.extract());
+		assertEquals("xyz", ordered.extract(ContextState.ORDERED));
+	}
+
+	@org.junit.Test
+	public void swapValue() {
+		Value value = new Value(ValueType.STRING, "Foo", null, true, true,
+				"abc", "xyz", String.class,
+				null, null);
+
+		assertEquals("abc", value.extract(ContextState.MEASURED));
+		assertTrue(value.isValid(ContextState.MEASURED));
+
+		assertEquals("xyz", value.extract(ContextState.ORDERED));
+		assertTrue(value.isValid(ContextState.ORDERED));
+
+		value.swapContext();
+
+		assertEquals("abc", value.extract(ContextState.ORDERED));
+		assertTrue(value.isValid(ContextState.ORDERED));
+
+		assertEquals("xyz", value.extract(ContextState.MEASURED));
+		assertTrue(value.isValid(ContextState.MEASURED));
+	}
+
+	@org.junit.Test
+	public void mergeMeasuredValues() {
+		Value measuredFirst = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value measuredSecond = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value merged = measuredFirst.merge(measuredSecond);
+		assertEquals("abc", merged.extract());
+		assertTrue(merged.isValid());
+	}
+
+	@org.junit.Test(expected = IllegalArgumentException.class)
+	public void mergeInvalidNameMeasuredValues() {
+		Value measuredFirst = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value measuredSecond = new Value(ValueType.STRING, "Bar", null, true, "abc", String.class,
+				null, null);
+
+		measuredFirst.merge(measuredSecond);
+	}
+
+	@org.junit.Test(expected = IllegalArgumentException.class)
+	public void mergeInvalidUnitMeasuredValues() {
+		Value measuredFirst = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value measuredSecond = new Value(ValueType.STRING, "Foo", "Unit", true, "abc", String.class,
+				null, null);
+
+		measuredFirst.merge(measuredSecond);
+	}
+
+	@org.junit.Test(expected = IllegalArgumentException.class)
+	public void mergeInvalidTypeMeasuredValues() {
+		Value measuredFirst = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value measuredSecond = new Value(ValueType.FLOAT, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		measuredFirst.merge(measuredSecond);
+	}
+
+	@org.junit.Test
+	public void mergeValidTargetMeasuredValues() {
+		Value measuredFirst = new Value(ValueType.STRING, "Foo", null, true, "abc", String.class,
+				null, null);
+
+		Value measuredSecond = new Value(ValueType.STRING, "Foo", null, false, true,null, "xyz", String.class,
+				null, null);
+
+		assertEquals("abc", measuredFirst.extract());
+		assertTrue(measuredFirst.isValid());
+		assertNull(measuredFirst.extract(ContextState.ORDERED));
+		assertFalse(measuredFirst.isValid(ContextState.ORDERED));
+
+		assertNull(measuredSecond.extract());
+		assertFalse(measuredSecond.isValid());
+		assertEquals("xyz", measuredSecond.extract(ContextState.ORDERED));
+		assertTrue(measuredSecond.isValid(ContextState.ORDERED));
+
+		Value merged = measuredFirst.merge(measuredSecond);
+		assertEquals("abc", merged.extract());
+		assertTrue(merged.isValid());
+		assertEquals("xyz", merged.extract(ContextState.ORDERED));
+		assertTrue(merged.isValid(ContextState.ORDERED));
+	}
+
 }
diff --git a/api/base/src/test/java/org/eclipse/mdm/api/base/query/FilterTest.java b/api/base/src/test/java/org/eclipse/mdm/api/base/query/FilterTest.java
index f55dbd4..5e1f8e5 100755
--- a/api/base/src/test/java/org/eclipse/mdm/api/base/query/FilterTest.java
+++ b/api/base/src/test/java/org/eclipse/mdm/api/base/query/FilterTest.java
@@ -15,14 +15,19 @@
 package org.eclipse.mdm.api.base.query;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.util.Arrays;
 import java.util.Iterator;
 
+import org.eclipse.mdm.api.base.AttributeImpl;
 import org.eclipse.mdm.api.base.EntityTypeImpl;
 import org.eclipse.mdm.api.base.RelationImpl;
 import org.eclipse.mdm.api.base.RelationImpl.AttributeType;
+import org.eclipse.mdm.api.base.adapter.Attribute;
 import org.eclipse.mdm.api.base.model.EnumRegistry;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -110,4 +115,66 @@
 
 		assertEquals(filterCondition, "1");
 	}
+
+	@Test
+	public void testContext() {
+		Attribute first = new AttributeImpl(AttributeImpl.Type.ID);
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, first, "foo"));
+
+		assertNotNull(expected);
+		assertTrue(expected.hasContext());
+		assertEquals(ContextState.MEASURED, expected.getContext());
+	}
+
+	@Test
+	public void testValidMultipleContext() {
+		Attribute first = new AttributeImpl(AttributeImpl.Type.ID);
+		Attribute second = new AttributeImpl(AttributeImpl.Type.ID);
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, first, "foo"))
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, second, "bar"));
+
+		assertNotNull(expected);
+		assertTrue(expected.hasContext());
+		assertEquals(ContextState.MEASURED, expected.getContext());
+	}
+
+	@Test
+	public void testValidMultipleContextMerge() {
+		Attribute first = new AttributeImpl(AttributeImpl.Type.ID);
+		Attribute second = new AttributeImpl(AttributeImpl.Type.ID);
+		Filter firstFilter = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, first, "foo"));
+		Filter secondFilter = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, second, "foo"));
+
+		Filter expected = Filter.and().merge(firstFilter, secondFilter);
+
+		assertNotNull(expected);
+		assertTrue(expected.hasContext());
+		assertEquals(ContextState.MEASURED, expected.getContext());
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testInvalidMultipleContext() {
+		Attribute first = new AttributeImpl(AttributeImpl.Type.ID);
+		Attribute second = new AttributeImpl(AttributeImpl.Type.ID);
+		Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, first, "foo"))
+				.add(ComparisonOperator.EQUAL.create(ContextState.ORDERED, second, "bar"));
+
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void testInvalidMultipleContextMerge() {
+		Attribute first = new AttributeImpl(AttributeImpl.Type.ID);
+		Attribute second = new AttributeImpl(AttributeImpl.Type.ID);
+		Filter firstFilter = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, first, "foo"));
+		Filter secondFilter = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.ORDERED, second, "foo"));
+
+		Filter.and().merge(firstFilter, secondFilter);
+	}
 }
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
index 25e3481..0ba1064 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
@@ -16,6 +16,8 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -42,6 +44,7 @@
 import org.eclipse.mdm.api.base.model.Channel;
 import org.eclipse.mdm.api.base.model.Entity;
 import org.eclipse.mdm.api.base.model.MeasuredValues;
+import org.eclipse.mdm.api.base.model.ScalarType;
 import org.eclipse.mdm.api.base.model.SequenceRepresentation;
 import org.eclipse.mdm.api.base.model.Unit;
 import org.eclipse.mdm.api.base.model.Value;
@@ -54,6 +57,8 @@
 import org.eclipse.mdm.api.odsadapter.query.ODSEntityType;
 import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
 import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.Lists;
 
@@ -64,6 +69,8 @@
  * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
  */
 public final class ReadRequestHandler {
+	private static final Logger LOGGER = LoggerFactory.getLogger(ReadRequestHandler.class);
+
 	public static final class ColumnAttributes {
 		private String name;
 		private String id;
@@ -72,9 +79,15 @@
 		private boolean independent;
 		private AxisType axisType;
 		private Short globalFlag;
+		private ScalarType dataType;
+		private ScalarType rawDataType;
+
+		private String unit;
+		private String meaQuantityId;
 
 		public ColumnAttributes(String name, String columnId, SequenceRepresentation sequenceRepresentation,
-				double[] generationParameters, boolean independent, AxisType axisType, Short globalFlag) {
+				double[] generationParameters, boolean independent, AxisType axisType, Short globalFlag,
+				ScalarType rawDataType, String meaQuantityId) {
 			this.name = name;
 			this.id = columnId;
 			this.sequenceRepresentation = sequenceRepresentation;
@@ -82,6 +95,8 @@
 			this.independent = independent;
 			this.axisType = axisType;
 			this.globalFlag = globalFlag;
+			this.rawDataType = rawDataType;
+			this.meaQuantityId = meaQuantityId;
 		}
 
 		public String getName() {
@@ -140,6 +155,34 @@
 			this.globalFlag = globalFlag;
 		}
 
+		public ScalarType getRawDataType() {
+			return rawDataType;
+		}
+
+		public void setRawDataType(ScalarType rawDataType) {
+			this.rawDataType = rawDataType;
+		}
+
+		public ScalarType getDataType() {
+			return dataType;
+		}
+
+		public void setDataType(ScalarType dataType) {
+			this.dataType = dataType;
+		}
+
+		public String getMeaQuantityId() {
+			return meaQuantityId;
+		}
+
+		public String getUnit() {
+			return unit;
+		}
+
+		public void setUnit(String unit) {
+			this.unit = unit;
+		}
+
 	}
 
 	// ======================================================================
@@ -190,87 +233,58 @@
 			}
 		}
 
-		ValueMatrix valueMatrix = null;
-		Column[] arrColumns = null;
+		// Load ColumnAttributes:
+		Map<String, ColumnAttributes> mapColumnAttributes = getColumnAttributes(readRequest);
+
+		if (mapColumnAttributes.isEmpty()) {
+			return Collections.emptyList();
+		}
 
 		List<MeasuredValues> listMeasuredValues = new ArrayList<>();
 
-		try {
-			// Load ColumnAttributes:
-			Map<String, ColumnAttributes> mapColumnAttributes = getColumnAttributes(readRequest);
-
-			final Map<String, List<ExternalComponent>> mapExternalComponents;
-
-			if (ValuesMode.STORAGE_PRESERVE_EXTCOMPS == readRequest.getValuesMode() && externalComponentExists()) {
-
-				EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
-				EntityType externalComponentEntityType = modelManager.getEntityType("ExternalComponent");
-
-				Relation relExternalComponentToLocalColumn = externalComponentEntityType
-						.getRelation(localColumnEntityType);
-
-				List<Attribute> listExtCompAttributes = externalComponentEntityType.getAttributes();
-
-				boolean hasExtCompBitCount = (listExtCompAttributes.stream()
-						.filter(a -> ExternalComponentEntity.ATTR_BITCOUNT.equals(a.getName())).count() > 0);
-				boolean hasExtCompBitOffset = (listExtCompAttributes.stream()
-						.filter(a -> ExternalComponentEntity.ATTR_BITOFFSET.equals(a.getName())).count() > 0);
-
-				List<String> ids = new ArrayList<>();
-				for (Map.Entry<String, ColumnAttributes> me : mapColumnAttributes.entrySet()) {
-					if (me.getValue().getSequenceRepresentation().isExternal()) {
-						ids.add(me.getKey());
-					}
-				}
-
-				mapExternalComponents = queryService.createQuery().select(externalComponentEntityType.getAttributes())
-						.select(relExternalComponentToLocalColumn.getAttribute())
-						.order(relExternalComponentToLocalColumn.getAttribute(),
-								externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_ORDINALNUMBER))
-						.fetch(Filter.and()
-								.add(ComparisonOperator.IN_SET.create(relExternalComponentToLocalColumn.getAttribute(),
-										ids.toArray(new String[0]))))
-						.stream()
-						.map(r -> new ImmutablePair<String, ExternalComponent>(
-								r.getValue(relExternalComponentToLocalColumn.getAttribute()).extract(),
-								new ExternalComponent(
-										r.getValue(externalComponentEntityType.getAttribute(
-												ExternalComponentEntity.ATTR_TYPESPECIFICATION)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_LENGTH)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_STARTOFFSET)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_BLOCKSIZE)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_VALUESPERBLOCK)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_VALUEOFFSET)).extract(),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_FILENAMEURL))
-												.extractWithDefault(null),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_FLAGSFILENAMEURL))
-												.extractWithDefault(null),
-										r.getValue(externalComponentEntityType
-												.getAttribute(ExternalComponentEntity.ATTR_FLAGSSTARTOFFSET))
-												.extractWithDefault(null),
-										(hasExtCompBitCount
-												? r.getValue(externalComponentEntityType
-														.getAttribute(ExternalComponentEntity.ATTR_BITCOUNT)).extract()
-												: Short.valueOf((short) 0)),
-										(hasExtCompBitOffset
-												? r.getValue(externalComponentEntityType
-														.getAttribute(ExternalComponentEntity.ATTR_BITOFFSET)).extract()
-												: Short.valueOf((short) 0)))))
-						.collect(Collectors.groupingBy(Pair::getKey,
-								Collectors.mapping(Pair::getValue, Collectors.toList())));
-
-			} else {
-				mapExternalComponents = new HashMap<>();
+		if (ValuesMode.STORAGE_PRESERVE_EXTCOMPS == readRequest.getValuesMode() && externalComponentExists()) {
+			List<String> processedLcIds = new ArrayList<>();
+			Map<String, List<ExternalComponent>> mapExternalComponents = loadExternalComponents(mapColumnAttributes);
+			LOGGER.debug("Loaded external components for {} localcolumns.", mapExternalComponents.size());
+			// Create MeasuredValues for local columns whose external components have been
+			// loaded above:
+			for (Map.Entry<String, List<ExternalComponent>> entry : mapExternalComponents.entrySet()) {
+				String lcId = entry.getKey();
+				List<ExternalComponent> listExtComps = entry.getValue();
+				listMeasuredValues
+						.add(ODSConverter.fromExternalComponents(listExtComps, mapColumnAttributes.get(lcId)));
+				processedLcIds.add(lcId);
 			}
 
-			if (mapColumnAttributes.size() > 0) {
+			// implict
+			for (Map.Entry<String, ColumnAttributes> entry : mapColumnAttributes.entrySet()) {
+				String lcId = entry.getKey();
+				ColumnAttributes ca = entry.getValue();
+				if (entry.getValue().getSequenceRepresentation().isImplicit()) {
+
+					ScalarType scalarType = ca.getRawDataType();
+					if (scalarType == ScalarType.UNKNOWN) {
+						scalarType = ca.getDataType();
+					}
+
+					listMeasuredValues.add(scalarType.createMeasuredValues(ca.getName(), ca.getUnit(),
+							ca.getSequenceRepresentation(), ca.getGenerationParameters(), ca.isIndependentColumn(),
+							ca.getAxisType(), ca.getGlobalFlag(), Collections.emptyList()));
+					processedLcIds.add(lcId);
+				}
+			}
+			// remove processed localcolumns
+			processedLcIds.forEach(lcId -> mapColumnAttributes.remove(lcId));
+		}
+
+		if (!mapColumnAttributes.isEmpty()) {
+			// if there are still localcolumns left we load them be valuematrix
+			LOGGER.debug("Loading {} localcolumns by value matrix.", mapColumnAttributes.size());
+
+			ValueMatrix valueMatrix = null;
+			Column[] arrColumns = null;
+
+			try {
 				valueMatrix = getValueMatrix(readRequest);
 				List<Pair<Column, ColumnAttributes>> listColumnPairs = getODSColumns(readRequest, valueMatrix,
 						mapColumnAttributes);
@@ -278,36 +292,100 @@
 				// Get array of all ODS columns for later release:
 				arrColumns = listColumnPairs.stream().map(Pair::getLeft).toArray(Column[]::new);
 
-				// Create MeasuredValues for local columns whose external components have been
-				// loaded above:
-				for (Pair<Column, ColumnAttributes> pair : listColumnPairs) {
-					Column column = pair.getLeft();
-					ColumnAttributes ca = pair.getRight();
-					List<ExternalComponent> listExtComps = mapExternalComponents.get(ca.getId());
-					if (listExtComps != null) {
-						listMeasuredValues.add(ODSConverter.fromExternalComponents(listExtComps,
-								column.getRawDataType(), column.getUnit(), ca));
-					}
-				}
-
 				// Create MeasuredValues for the remaining local columns:
-				NameValueSeqUnit[] nvsus = valueMatrix.getValue(
-						listColumnPairs.stream()
-								.filter(p -> !contains(mapExternalComponents.keySet(), p.getRight().getId()))
-								.map(p -> p.getLeft()).toArray(Column[]::new),
-						readRequest.getStartIndex(), readRequest.getRequestSize());
+				NameValueSeqUnit[] nvsus = valueMatrix.getValue(arrColumns, readRequest.getStartIndex(),
+						readRequest.getRequestSize());
 				listMeasuredValues.addAll(ODSConverter.fromODSMeasuredValuesSeq(nvsus,
 						listColumnPairs.stream().map(Pair::getRight).toArray(ColumnAttributes[]::new)));
+			} catch (AoException aoe) {
+				throw new DataAccessException(aoe.reason, aoe);
+			} finally {
+				releaseColumns(arrColumns);
+				releaseValueMatrix(valueMatrix);
 			}
-
-			return listMeasuredValues;
-
-		} catch (AoException aoe) {
-			throw new DataAccessException(aoe.reason, aoe);
-		} finally {
-			releaseColumns(arrColumns);
-			releaseValueMatrix(valueMatrix);
 		}
+
+		return listMeasuredValues;
+	}
+
+	/**
+	 * Loads External Components for all ColumnAttributes which satisfy
+	 * {@link SequenceRepresentation#isExternal()}
+	 * 
+	 * @param mapColumnAttributes
+	 * @return a map with the LocalColumn.Id as key and a list of its
+	 *         ExternalComponents as value
+	 */
+	private Map<String, List<ExternalComponent>> loadExternalComponents(
+			Map<String, ColumnAttributes> mapColumnAttributes) {
+		Map<String, List<ExternalComponent>> mapExternalComponents;
+		EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
+		EntityType externalComponentEntityType = modelManager.getEntityType("ExternalComponent");
+
+		Relation relExternalComponentToLocalColumn = externalComponentEntityType.getRelation(localColumnEntityType);
+
+		List<Attribute> listExtCompAttributes = externalComponentEntityType.getAttributes();
+
+		boolean hasExtCompBitCount = (listExtCompAttributes.stream()
+				.filter(a -> ExternalComponentEntity.ATTR_BITCOUNT.equals(a.getName())).count() > 0);
+		boolean hasExtCompBitOffset = (listExtCompAttributes.stream()
+				.filter(a -> ExternalComponentEntity.ATTR_BITOFFSET.equals(a.getName())).count() > 0);
+
+		List<String> ids = new ArrayList<>();
+		for (Map.Entry<String, ColumnAttributes> me : mapColumnAttributes.entrySet()) {
+			if (me.getValue().getSequenceRepresentation().isExternal()) {
+				ids.add(me.getKey());
+			}
+		}
+
+		if (ids.isEmpty()) {
+			// no external sequence representations -> no externalcomponents to load
+			return Collections.emptyMap();
+		}
+
+		mapExternalComponents = queryService.createQuery().select(externalComponentEntityType.getAttributes())
+				.select(relExternalComponentToLocalColumn.getAttribute())
+				.order(relExternalComponentToLocalColumn.getAttribute(),
+						externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_ORDINALNUMBER))
+				.fetch(Filter.and()
+						.add(ComparisonOperator.IN_SET
+								.create(relExternalComponentToLocalColumn.getAttribute(), ids.toArray(new String[0]))))
+				.stream()
+				.map(r -> new ImmutablePair<String, ExternalComponent>(
+						r.getValue(relExternalComponentToLocalColumn.getAttribute()).extract(),
+						new ExternalComponent(
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_TYPESPECIFICATION)).extract(),
+								r.getValue(
+										externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_LENGTH))
+										.extract(),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_STARTOFFSET)).extract(),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_BLOCKSIZE)).extract(),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_VALUESPERBLOCK)).extract(),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_VALUEOFFSET)).extract(),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_FILENAMEURL))
+										.extractWithDefault(null),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_FLAGSFILENAMEURL))
+										.extractWithDefault(null),
+								r.getValue(externalComponentEntityType
+										.getAttribute(ExternalComponentEntity.ATTR_FLAGSSTARTOFFSET))
+										.extractWithDefault(null),
+								(hasExtCompBitCount
+										? r.getValue(externalComponentEntityType
+												.getAttribute(ExternalComponentEntity.ATTR_BITCOUNT)).extract()
+										: Short.valueOf((short) 0)),
+								(hasExtCompBitOffset
+										? r.getValue(externalComponentEntityType
+												.getAttribute(ExternalComponentEntity.ATTR_BITOFFSET)).extract()
+										: Short.valueOf((short) 0)))))
+				.collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList())));
+		return mapExternalComponents;
 	}
 
 	private boolean externalComponentExists() {
@@ -404,8 +482,8 @@
 		Map<String, ColumnAttributes> mapColumnAttributes = new HashMap<>();
 
 		EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
-		Attribute idAttr = localColumnEntityType.getAttribute("Id");
-		Attribute nameAttr = localColumnEntityType.getAttribute("Name");
+		Attribute idAttr = localColumnEntityType.getIDAttribute();
+		Attribute nameAttr = localColumnEntityType.getNameAttribute();
 		Attribute submatrixAttr = localColumnEntityType.getAttribute("SubMatrix");
 
 		// Don't query GenerationParameters together with other non-ID attributes as
@@ -413,7 +491,8 @@
 		Query query1 = queryService.createQuery().select(Lists.newArrayList(idAttr, nameAttr,
 				localColumnEntityType.getAttribute("SequenceRepresentation"),
 				localColumnEntityType.getAttribute("IndependentFlag"), localColumnEntityType.getAttribute("GlobalFlag"),
-				localColumnEntityType.getAttribute("axistype")));
+				localColumnEntityType.getAttribute("axistype"), localColumnEntityType.getAttribute("RawDatatype"),
+				localColumnEntityType.getAttribute("MeaQuantity")));
 
 		// OpenATFX can't handle multiple search conditions, so use just one...
 		Filter filter = Filter.and().add(
@@ -430,12 +509,14 @@
 
 			if (null == setColumnNames || setColumnNames.contains(columnName)) {
 				String columnId = result.getRecord(localColumnEntityType).getID();
-				ColumnAttributes ca = new ColumnAttributes(columnName, columnId,
-						(ValuesMode.CALCULATED == readRequest.getValuesMode() ? SequenceRepresentation.EXPLICIT
-								: mapValues.get("SequenceRepresentation").extract()),
-						new double[0], ((short) mapValues.get("IndependentFlag").extract() != 0),
-						mapValues.get("axistype").extract(),
-						mapValues.get("GlobalFlag").extractWithDefault(null));
+				SequenceRepresentation seqRep = ValuesMode.CALCULATED == readRequest.getValuesMode()
+						? SequenceRepresentation.EXPLICIT
+						: mapValues.get("SequenceRepresentation").extract();
+
+				ColumnAttributes ca = new ColumnAttributes(columnName, columnId, seqRep, new double[0],
+						((short) mapValues.get("IndependentFlag").extract() != 0), mapValues.get("axistype").extract(),
+						mapValues.get("GlobalFlag").extractWithDefault(null), mapValues.get("RawDatatype").extract(),
+						mapValues.get("MeaQuantity").extract());
 
 				mapColumnAttributes.put(mapValues.get("Id").extract(), ca);
 			}
@@ -456,10 +537,59 @@
 			}
 		}
 
+		applyUnitNamesAndDataTypesFallback(mapColumnAttributes.values());
+
 		return mapColumnAttributes;
 	}
 
 	/**
+	 * Loads and sets the unit property of the given ColumnAttributes. This method
+	 * does not use Queries with joins as openATFX does not support it. Instead, two
+	 * queries are used. (LocalColumn.Id, LocalColumn.MeaQuantity) ->
+	 * (MeaQuantity.Id, MeaQuantity.Unit) -> (Unit.Id, Unit.Name)
+	 * 
+	 * @param mapColumnAttributes ColumnAttributes to set the unit.
+	 */
+	private void applyUnitNamesAndDataTypesFallback(Collection<ColumnAttributes> columnAttributes) {
+		EntityType meaQuantityEntityType = modelManager.getEntityType(Channel.class);
+
+		Query queryMq = queryService.createQuery().select(meaQuantityEntityType.getIDAttribute(),
+				meaQuantityEntityType.getAttribute("DataType"), meaQuantityEntityType.getAttribute("Unit"));
+		Filter filterMq = Filter.and().add(ComparisonOperator.IN_SET.create(meaQuantityEntityType.getIDAttribute(),
+				columnAttributes.stream().map(ca -> ca.getMeaQuantityId()).toArray(String[]::new)));
+
+		Map<String, String> mqId2unitId = new HashMap<>();
+		Map<String, ScalarType> mqId2datatype = new HashMap<>();
+
+		for (Result result : queryMq.fetch(filterMq)) {
+			Map<String, Value> mapValues = result.getRecord(meaQuantityEntityType).getValues();
+			mqId2unitId.put(result.getRecord(meaQuantityEntityType).getID(), mapValues.get("Unit").extract());
+			mqId2datatype.put(result.getRecord(meaQuantityEntityType).getID(), mapValues.get("DataType").extract());
+		}
+
+		EntityType unitEntityType = modelManager.getEntityType(Unit.class);
+
+		Query queryUnit = queryService.createQuery().select(unitEntityType.getIDAttribute(),
+				unitEntityType.getNameAttribute());
+		Filter filterUnit = Filter.and().add(ComparisonOperator.IN_SET.create(unitEntityType.getIDAttribute(),
+				mqId2unitId.values().stream().distinct().toArray(String[]::new)));
+
+		Map<String, String> mqId2unitName = new HashMap<>();
+
+		for (Result result : queryUnit.fetch(filterUnit)) {
+			Map<String, Value> mapValues = result.getRecord(unitEntityType).getValues();
+
+			mqId2unitName.put(result.getRecord(unitEntityType).getID(), mapValues.get("Name").extract());
+		}
+
+		columnAttributes.forEach(ca -> {
+			String unitId = mqId2unitId.get(ca.getMeaQuantityId());
+			ca.setUnit(mqId2unitName.get(unitId));
+			ca.setDataType(mqId2datatype.get(ca.getMeaQuantityId()));
+		});
+	}
+
+	/**
 	 * Returns the {@link ValueMatrix} CORBA service object associated with given
 	 * {@link ReadRequest}.
 	 *
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
index 0b6e839..94e5b1a 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
@@ -26,6 +26,7 @@
 import org.eclipse.mdm.api.base.model.ContextType;
 import org.eclipse.mdm.api.base.model.Entity;
 import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.MDMLocalization;
 import org.eclipse.mdm.api.base.model.Measurement;
 import org.eclipse.mdm.api.base.model.Parameter;
 import org.eclipse.mdm.api.base.model.ParameterSet;
@@ -228,6 +229,9 @@
 		EntityConfig<ExtSystem> extSystemConfig = create(modelManager, new Key<>(ExtSystem.class), "ExtSystem", false);
 		entityConfigRepository.register(extSystemConfig);
 
+		// i18n / localization
+		entityConfigRepository.register(create(modelManager, new Key<>(MDMLocalization.class), "MDMLocalization", false));
+		
 		LOGGER.debug("Entity configurations loaded in {} ms.", System.currentTimeMillis() - start);
 		return entityConfigRepository;
 	}
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
index 6628b8f..000c939 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
@@ -252,6 +252,15 @@
 		return entityType.get();
 	}
 
+	@Override
+	public String getMimeType(EntityType entityType) {
+		try {
+			return getEntityConfig(entityType).getMimeType();
+		} catch (IllegalArgumentException e) {
+			return "";
+		}
+	}
+
 	/**
 	 * Returns the {@link AoSession} of this model manager.
 	 *
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 d9fd9ce..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
@@ -25,6 +25,7 @@
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import org.asam.ods.AIDName;
 import org.asam.ods.AIDNameUnitId;
@@ -231,6 +232,7 @@
 			qse.joinSeq = joinSeq.toArray(new JoinDef[joinSeq.size()]);
 			qse.orderBy = orderBy.toArray(new SelOrder[orderBy.size()]);
 
+			logQuery();
 			List<Result> results = new ArrayList<>();
 			long start = System.currentTimeMillis();
 			for (Result result : new ResultFactory(entityTypesByID, applElemAccess.getInstancesExt(qse, limit)[0])) {
@@ -253,6 +255,27 @@
 	// Private methods
 	// ======================================================================
 
+	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 -> getName(j.fromAID) + "-[" + joinType(j.joiningType) + j.refName + "]->" + getName(j.toAID))
+						.collect(Collectors.joining(", ")));
+	}
+
+	private String getName(T_LONGLONG aid) {
+		EntityType entityType = entityTypesByID.get("" + ODSConverter.fromODSLong(aid));
+		if (entityType == null) {
+			return "EntityType(aid=" + ODSConverter.fromODSLong(aid) + ")";
+		} else {
+			return entityType.getName();
+		}
+	}
+
+	private String joinType(org.asam.ods.JoinType joinType) {
+		return (joinType == org.asam.ods.JoinType.JTDEFAULT ? "" : "outer: ");
+	}
+
 	/**
 	 * Converts given {@link Attribute} and {@link Aggregation} to an ODS
 	 * {@link SelAIDNameUnitId}.
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 cd882dd..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
@@ -17,6 +17,7 @@
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.reducing;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +45,7 @@
 import org.eclipse.mdm.api.base.query.Query;
 import org.eclipse.mdm.api.base.query.QueryService;
 import org.eclipse.mdm.api.base.query.Result;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.base.search.Searchable;
 import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
@@ -109,7 +111,7 @@
 	 * {@inheritDoc}
 	 */
 	@Override
-	public final List<EntityType> listEntityTypes() {
+	public List<EntityType> listEntityTypes() {
 		return joinTree.getNodeNames().stream().map(modelManager::getEntityType).collect(Collectors.toList());
 	}
 
@@ -117,7 +119,7 @@
 	 * {@inheritDoc}
 	 */
 	@Override
-	public final Searchable getSearchableRoot() {
+	public Searchable getSearchableRoot() {
 		Function<String, SearchableNode> factory = k -> {
 			return new SearchableNode(modelManager.getEntityType(k));
 		};
@@ -138,7 +140,7 @@
 	 * {@inheritDoc}
 	 */
 	@Override
-	public final List<Value> getFilterValues(Attribute attribute, Filter filter) throws DataAccessException {
+	public List<Value> getFilterValues(Attribute attribute, Filter filter) throws DataAccessException {
 		Query query = queryService.createQuery().select(attribute, Aggregation.DISTINCT).group(attribute);
 
 		// add required joins
@@ -147,15 +149,38 @@
 		});
 
 		addImplicitLocalColumnJoin(query);
-		
-		return query.fetch(filter).stream().map(r -> r.getValue(attribute)).collect(Collectors.toList());
+
+		return query.fetch(filter).stream().map(r -> r.getValue(attribute, Aggregation.DISTINCT))
+				.collect(Collectors.toList());
 	}
 
 	/**
 	 * {@inheritDoc}
 	 */
 	@Override
-	public final List<Result> fetchComplete(List<EntityType> entityTypes, 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());
+			query.select(attribute, Aggregation.DISTINCT);
+			query.group(attribute);
+		}
+
+		// add required joins
+		filter.stream().filter(FilterItem::isCondition).map(FilterItem::getCondition).forEach(c -> {
+			addJoins(query, c.getAttribute().getEntityType());
+		});
+
+		addImplicitLocalColumnJoin(query);
+
+		return query.fetch(filter);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public List<Result> fetchComplete(List<EntityType> entityTypes, Filter filter) throws DataAccessException {
 		Query query = queryService.createQuery().selectID(modelManager.getEntityType(entityClass));
 
 		// add required joins
@@ -171,7 +196,7 @@
 	 * {@inheritDoc}
 	 */
 	@Override
-	public final List<Result> fetch(List<Attribute> attributes, Filter filter) throws DataAccessException {
+	public List<Result> fetch(List<Attribute> attributes, Filter filter) throws DataAccessException {
 		Query query = queryService.createQuery().selectID(modelManager.getEntityType(entityClass));
 
 		// add required joins
@@ -296,7 +321,8 @@
 	 * @param entityType The target {@link EntityType}.
 	 */
 	private void addJoins(Query query, EntityType entityType) {
-		if (query.isQueried(entityType)) {
+		EntityType searchQueryEntityType = modelManager.getEntityType(entityClass);
+		if (query.isQueried(entityType) || entityType.equals(searchQueryEntityType)) {
 			return;
 		}
 
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelGroupSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelGroupSearchQuery.java
index 1768def..15e05d9 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelGroupSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelGroupSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelSearchQuery.java
index eeb8b13..3c018d6 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ChannelSearchQuery.java
@@ -21,6 +21,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MeasurementSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MeasurementSearchQuery.java
index ab19975..44e981e 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MeasurementSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/MeasurementSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
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 d25742c..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,18 +17,27 @@
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.reducing;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 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.DataAccessException;
 import org.eclipse.mdm.api.base.query.Filter;
+import org.eclipse.mdm.api.base.query.Record;
 import org.eclipse.mdm.api.base.query.Result;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.base.search.Searchable;
 
@@ -91,7 +100,7 @@
 
 		return Stream.concat(orderValues.stream(), resultValues.stream())
 				// group by value and merge values
-				.collect(groupingBy(v -> v.extract(), reducing((v1, v2) -> v1)))
+				.collect(groupingBy(Value::extract, reducing((v1, v2) -> v1)))
 				// collect merged results
 				.values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
 	}
@@ -100,6 +109,31 @@
 	 * {@inheritDoc}
 	 */
 	@Override
+	public List<Result> getFilterResults(List<Attribute> attributes, Filter filter, ContextState contextState)
+			throws DataAccessException {
+		ContextState filterContext = filter.getContext() != null ? filter.getContext() : contextState;
+		List<Result> results;
+
+		Set<EntityType> entityTypesFromAttributes = getTypesFromAttributes(attributes);
+		if (entityTypesFromAttributes.size() > 1) {
+			throw new DataAccessException("Cannot handle multiple types here");
+		}
+
+		if (ContextState.ORDERED.equals(filterContext)) {
+			results = byOrder.getFilterResults(attributes, filter, contextState);
+			swapValues(attributes, results, Aggregation.DISTINCT);
+		} else {
+			results = byResult.getFilterResults(attributes, filter, contextState);
+		}
+
+		return results;
+
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
 	public List<Result> fetchComplete(List<EntityType> entityTypes, Filter filter) throws DataAccessException {
 		return mergeResults(byOrder.fetchComplete(entityTypes, filter), byResult.fetchComplete(entityTypes, filter));
 	}
@@ -109,7 +143,13 @@
 	 */
 	@Override
 	public List<Result> fetch(List<Attribute> attributes, Filter filter) throws DataAccessException {
-		return mergeResults(byOrder.fetch(attributes, filter), byResult.fetch(attributes, filter));
+		ContextState contextState = filter.getContext();
+		if (ContextState.ORDERED.equals(contextState)) {
+			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, results -> createIdFilter(results, entityType, Aggregation.NONE, contextState));
+		}
+		return fetch(contextState, byResult, byOrder, attributes, filter, list -> filter);
 	}
 
 	// ======================================================================
@@ -117,6 +157,160 @@
 	// ======================================================================
 
 	/**
+	 * Will fetch the values based on the given primary and secondary
+	 * {@link BaseEntitySearchQuery} with the provided {@link Filter}. Will swap
+	 * ordered/measured values if necessary.
+	 *
+	 * @param contextState   the {@link ContextState} were the values came from.
+	 * @param primary        the primary {@link BaseEntitySearchQuery}
+	 * @param secondary      the secondary {@link BaseEntitySearchQuery}
+	 * @param attributes     selected attributes as list of {@link Attribute}
+	 * @param filter         the current {@link Filter}
+	 * @param filterFunction this function provides the filter for the secondary
+	 *                       query
+	 *
+	 * @return list of merged {@link Result}
+	 */
+	private List<Result> fetch(ContextState contextState, BaseEntitySearchQuery primary,
+			BaseEntitySearchQuery secondary, List<Attribute> attributes, Filter filter,
+			Function<List<Result>, Filter> filterFunction) {
+		List<Result> primaryResults = primary.fetch(attributes, filter);
+		if (primaryResults.isEmpty()) {
+			// return early as secondaryResults do not matter, if no primaryResults are
+			// found.
+			return Collections.emptyList();
+		}
+		Filter additionalFilter = filterFunction.apply(primaryResults);
+		List<Result> secondaryResults = secondary.fetch(attributes, additionalFilter);
+		if (ContextState.ORDERED.equals(contextState)) {
+			swapValues(attributes, primaryResults, Aggregation.NONE);
+		} else if (ContextState.MEASURED.equals(contextState)) {
+			swapValues(attributes, secondaryResults, Aggregation.NONE);
+		}
+		return mergeResults(primaryResults, secondaryResults);
+	}
+
+	/**
+	 * Swaps the ordered/measured values inside the {@link Value} object related to
+	 * the given attributes.
+	 *
+	 * @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, 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)
+				.map(Map::values)
+				.flatMap(Collection::stream)
+				.filter(value -> relevantNames.contains(value.getName()))
+				.forEach(Value::swapContext);
+	}
+
+	/**
+	 * 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, EntityType currentEntityType, Aggregation aggregation, ContextState contextState) {
+		Filter idFilter = Filter.or();
+		results.stream()
+				.flatMap(Result::stream)
+				.filter(record -> hasID(record, currentEntityType, aggregation))
+				.collect(Collectors.groupingBy(Record::getEntityType,
+						Collectors.mapping(record -> getID(record, currentEntityType, aggregation, contextState), Collectors.toList())))
+				.forEach(idFilter::ids);
+		return idFilter;
+	}
+
+	/**
+	 * Checks if a given {@link Record} contains a valid ID
+	 *
+	 * @param record the record which should be checked
+	 *
+	 * @return <code>true</code> if the record has a valid ID, <code>false</code>
+	 *         otherwise
+	 */
+	private boolean hasID(Record record, EntityType entityType, Aggregation aggregation) {
+		if (record != null) {
+			try {
+				String idKey =  getAggregatedName(entityType.getIDAttribute().getName(), aggregation);
+				Value idValue = record.getValues().get(idKey);
+				return idValue != null;
+			} catch (IllegalStateException exception) {
+				return false;
+			}
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * 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.
 	 *
@@ -125,11 +319,56 @@
 	 * @return The merged {@link Result} is returned.
 	 */
 	private List<Result> mergeResults(List<Result> results1, List<Result> results2) {
+		return mergeResults(entityType, Aggregation.NONE, results1, results2);
+	}
+
+	private List<Result> mergeResults(EntityType mergeType, Aggregation aggregation, List<Result> results1, List<Result> results2) {
 		return Stream.concat(results1.stream(), results2.stream())
 				// group by instance ID and merge grouped results
-				.collect(groupingBy(r -> r.getRecord(entityType).getID(), reducing(Result::merge)))
+				.collect(groupingBy(r -> getMergeId(mergeType, r.getRecord(mergeType), aggregation), reducing(Result::merge)))
 				// collect merged results
 				.values().stream().filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
 	}
 
+	/**
+	 * Returns the ID of the record, if ID of ContextState.MEASURED is not set,
+	 * returns ID of ContextState.ORDERED.
+	 * 
+	 * @param rec {@link Record} which ID is requested.
+	 * @return Returns the ID of the record, if ID of ContextState.MEASURED is not
+	 *         set, returns ID of ContextState.ORDERED.
+	 */
+	private String getMergeId(EntityType idType, Record rec, Aggregation aggregation) {
+
+		String idKey;
+		if (Aggregation.NONE == aggregation) {
+			idKey = idType.getIDAttribute().getName();
+		} else {
+			idKey = String.format("%s(%s)", aggregation.name(), idType.getIDAttribute().getName());
+		}
+
+		Value idValue = rec.getValues().get(idKey);
+		String id =  idValue.extract();
+
+		if (id == null) {
+			// MEASURED (default) ContextState is null, so we use the ID stored in ORDERED
+			return rec.getValues().get(idKey).extract(ContextState.ORDERED);
+		} else {
+			return id;
+		}
+
+	}
+
+	/**
+	 * 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 bf64b9d..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
@@ -35,6 +35,7 @@
 import org.eclipse.mdm.api.base.query.Filter;
 import org.eclipse.mdm.api.base.query.QueryService;
 import org.eclipse.mdm.api.base.query.Result;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.base.search.SearchService;
 import org.eclipse.mdm.api.base.search.Searchable;
@@ -123,6 +124,14 @@
 		return findSearchQuery(entityClass).getFilterValues(attribute, filter);
 	}
 
+	@Override
+	public List<Result> getFilterResults(final Class<? extends Entity> entityClass,
+	                                               final List<Attribute> attributes,
+	                                               final Filter filter,
+	                                               final ContextState contextState) throws DataAccessException {
+		return findSearchQuery(entityClass).getFilterResults(attributes, filter, contextState);
+	}
+
 	/**
 	 * {@inheritDoc}
 	 */
@@ -148,13 +157,15 @@
 	public List<Result> fetchResults(Class<? extends Entity> entityClass, List<Attribute> attributes, Filter filter,
 			String query) throws DataAccessException {
 		Filter mergedFilter = getMergedFilter(filter, query);
-		if (mergedFilter.isEmtpty()) {
-			return Collections.emptyList();
-		}
+//		if (mergedFilter.isEmtpty()) {
+//			return Collections.emptyList();
+//		}
 
 		EntityType entityType = context.getODSModelManager().getEntityType(entityClass);
 		Map<String, Result> recordsByEntityID = new HashMap<>();
-		for (Result result : findSearchQuery(entityClass).fetch(attributes, mergedFilter)) {
+		SearchQuery searchQuery = findSearchQuery(entityClass);
+		List<Result> results = searchQuery.fetch(attributes, mergedFilter);
+		for (Result result : results) {
 			recordsByEntityID.put(result.getRecord(entityType).getID(), result);
 		}
 
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/PoolSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/PoolSearchQuery.java
index df8d1b7..e8b5b3e 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/PoolSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/PoolSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ProjectSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ProjectSearchQuery.java
index b879d32..634df80 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ProjectSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/ProjectSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestSearchQuery.java
index be16688..66899fa 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestStepSearchQuery.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestStepSearchQuery.java
index 5c048f5..67d4316 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestStepSearchQuery.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/search/TestStepSearchQuery.java
@@ -20,6 +20,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchQuery;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
index 9e3a884..4f97478 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
@@ -131,8 +131,9 @@
 		values.get(AE_LC_ATTR_AXISTYPE).set(writeRequest.getAxisType());
 		values.get(AE_LC_ATTR_PARAMETERS).set(writeRequest.getGenerationParameters());
 
+		ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
+
 		if (writeRequest.hasValues()) {
-			ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
 			String unitName = writeRequest.getChannel().getUnit().getName();
 			values.put(AE_LC_ATTR_VALUES,
 					valueType.create(AE_LC_ATTR_VALUES, unitName, true, writeRequest.getValues()));
@@ -152,6 +153,9 @@
 				IntStream.range(0, genParamD.length)
 						.forEach(i -> genParamD[i] = ((Number) Array.get(genParamValues, i)).doubleValue());
 				values.get(AE_LC_ATTR_PARAMETERS).set(genParamD);
+
+				// remove GenerationParameters from Values
+				values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
 			}
 
 			// flags
@@ -162,9 +166,10 @@
 				values.get(AE_LC_ATTR_FLAGS).set(flags);
 			}
 		} else if (writeRequest.hasExternalComponents()) {
-			// No values to write (ext comps are used instead), so remove Values attribute from map:
-			values.remove(AE_LC_ATTR_VALUES); 
-			
+			// No values to write (ext comps are used instead), but we have to set the
+			// Values attribute to empty, based on the rawDataType
+			values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
+
 			// Set global flag as specified in the WriteRequest:
 			values.get(AE_LC_ATTR_GLOBAL_FLAG).set(writeRequest.getGlobalFlag());
 			
@@ -172,15 +177,17 @@
 					.getEntityType("ExternalComponent");
 			
 			List<Attribute> listAttrsExtComp = externalComponentEntityType.getAttributes();
-			boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName())).count() > 0);
-			boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName())).count() > 0);
-			boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName())).count() > 0);
+			boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName()))
+					.count() > 0);
+			boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName()))
+					.count() > 0);
+			boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName()))
+					.count() > 0);
 
 			int ordinalNumber = 1;
 			for (ExternalComponent extComp : writeRequest.getExternalComponents()) {
 				Core extCompCore = new DefaultCore(externalComponentEntityType);
-				ExternalComponentEntity extCompEntity = new ExternalComponentEntity(
-						extCompCore);
+				ExternalComponentEntity extCompEntity = new ExternalComponentEntity(extCompCore);
 				extCompEntity.setName(writeRequest.getChannel().getName());
 				if (hasMimeType) {
 					extCompEntity.setMimeType(new MimeType("application/x-asam.aoexternalcomponent"));
@@ -203,7 +210,8 @@
 				}
 
 				core.getChildrenStore().add(extCompEntity);
-				extCompCore.getPermanentStore().set(new BaseEntity(core) {});
+				extCompCore.getPermanentStore().set(new BaseEntity(core) {
+				});
 			}
 		} else {
 			throw new IllegalStateException("Given write request neither has measured values nor external components");
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
index 1ecf311..dd023e6 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
@@ -758,8 +758,8 @@
 		}
 
 		if (null == columnAttributes) {
-			columnAttributes = new ReadRequestHandler.ColumnAttributes("", null, null, new double[0], false, null,
-					null);
+			columnAttributes = new ReadRequestHandler.ColumnAttributes("", null, null, new double[0], false, null, null,
+					null, null);
 		}
 
 		return scalarType.createMeasuredValues(odsMeasuredValues.valName, odsMeasuredValues.unit,
@@ -775,46 +775,18 @@
 	 * @return The converted {@code MeasuredValues} is returned.
 	 * @throws DataAccessException Thrown on conversion errors.
 	 */
-	public static MeasuredValues fromExternalComponents(List<ExternalComponent> externalComponents, DataType dataType,
-			String unit, ReadRequestHandler.ColumnAttributes columnAttributes) throws DataAccessException {
-		ScalarType scalarType;
-
-		if (DataType.DT_STRING == dataType) {
-			scalarType = ScalarType.STRING;
-		} else if (DataType.DT_DATE == dataType) {
-			scalarType = ScalarType.DATE;
-		} else if (DataType.DT_BOOLEAN == dataType) {
-			scalarType = ScalarType.BOOLEAN;
-		} else if (DataType.DT_BYTE == dataType) {
-			scalarType = ScalarType.BYTE;
-		} else if (DataType.DT_SHORT == dataType) {
-			scalarType = ScalarType.SHORT;
-		} else if (DataType.DT_LONG == dataType) {
-			scalarType = ScalarType.INTEGER;
-		} else if (DataType.DT_LONGLONG == dataType) {
-			scalarType = ScalarType.LONG;
-		} else if (DataType.DT_FLOAT == dataType) {
-			scalarType = ScalarType.FLOAT;
-		} else if (DataType.DT_DOUBLE == dataType) {
-			scalarType = ScalarType.DOUBLE;
-		} else if (DataType.DT_BYTESTR == dataType) {
-			scalarType = ScalarType.BYTE_STREAM;
-		} else if (DataType.DT_COMPLEX == dataType) {
-			scalarType = ScalarType.FLOAT_COMPLEX;
-		} else if (DataType.DT_DCOMPLEX == dataType) {
-			scalarType = ScalarType.DOUBLE_COMPLEX;
-		} else if (DataType.DT_EXTERNALREFERENCE == dataType) {
-			scalarType = ScalarType.FILE_LINK;
-		} else {
-			throw new DataAccessException(
-					"Conversion for ODS measured points of type '" + dataType.toString() + "' does not exist.");
-		}
-
+	public static MeasuredValues fromExternalComponents(List<ExternalComponent> externalComponents,
+			ReadRequestHandler.ColumnAttributes columnAttributes) throws DataAccessException {
 		if (null == columnAttributes) {
 			throw new DataAccessException("No ColumnAttributes provided.");
 		}
 
-		return scalarType.createMeasuredValues(columnAttributes.getName(), unit,
+		ScalarType scalarType = columnAttributes.getRawDataType();
+		if (scalarType == ScalarType.UNKNOWN) {
+			scalarType = columnAttributes.getDataType();
+		}
+
+		return scalarType.createMeasuredValues(columnAttributes.getName(), columnAttributes.getUnit(),
 				columnAttributes.getSequenceRepresentation(), columnAttributes.getGenerationParameters(),
 				columnAttributes.isIndependentColumn(), columnAttributes.getAxisType(),
 				columnAttributes.getGlobalFlag(), externalComponents);
diff --git a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
index 00d7d20..4f4d4bd 100644
--- a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
+++ b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
@@ -398,46 +398,26 @@
 			FileLink fileLinkFlags2 = FileLink.newLocal(fileFlags2.toPath());
 
 			wrb = WriteRequest.create(channelGroup, channels.get(3), AxisType.Y_AXIS);
-			writeRequests.add(wrb.explicit().externalComponents(
-					ScalarType.BYTE,
-					Lists.newArrayList((new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE)
-							.setLength(5)
-							.setStartOffset(0)
-							.setBlocksize(5)
-							.setValuesPerBlock(5)
-							.setValueOffset(0)
-							.setFileLink(fileLinkValues1)
-							.setFlagsStartOffset(0L)
-							.setFlagsFileLink(fileLinkFlags1),
-							(new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE)
-							.setLength(5)
-							.setStartOffset(0)
-							.setBlocksize(5)
-							.setValuesPerBlock(5)
-							.setValueOffset(0)
-							.setFileLink(fileLinkValues2)
-							.setFlagsStartOffset(0L)
-							.setFlagsFileLink(fileLinkFlags2)))
+			writeRequests.add(wrb.explicitExternal().externalComponents(ScalarType.BYTE, Lists.newArrayList(
+					(new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE).setLength(5)
+							.setStartOffset(0).setBlocksize(5).setValuesPerBlock(5).setValueOffset(0)
+							.setFileLink(fileLinkValues1).setFlagsStartOffset(0L).setFlagsFileLink(fileLinkFlags1),
+					(new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE).setLength(5)
+							.setStartOffset(0).setBlocksize(5).setValuesPerBlock(5).setValueOffset(0)
+							.setFileLink(fileLinkValues2).setFlagsStartOffset(0L).setFlagsFileLink(fileLinkFlags2)))
 					.build());
 
 			wrb = WriteRequest.create(channelGroup, channels.get(4), AxisType.Y_AXIS);
-			writeRequests.add(wrb.explicit().externalComponents(
-					ScalarType.INTEGER,
-					Lists.newArrayList((new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
-							.setLength(5)
-							.setStartOffset(5L)
-							.setBlocksize(20)
-							.setValuesPerBlock(5)
-							.setValueOffset(0)
-							.setFileLink(fileLinkValues1),
-							(new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
-							.setLength(5)
-							.setStartOffset(5L)
-							.setBlocksize(20)
-							.setValuesPerBlock(5)
-							.setValueOffset(0)
-							.setFileLink(fileLinkValues2)))
-					.build());
+			writeRequests
+					.add(wrb.explicitExternal()
+							.externalComponents(ScalarType.INTEGER, Lists.newArrayList(
+									(new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
+											.setLength(5).setStartOffset(5L).setBlocksize(20).setValuesPerBlock(5)
+											.setValueOffset(0).setFileLink(fileLinkValues1),
+									(new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
+											.setLength(5).setStartOffset(5L).setBlocksize(20).setValuesPerBlock(5)
+											.setValueOffset(0).setFileLink(fileLinkValues2)))
+							.build());
 		} catch (IOException exc) {
 
 		}
diff --git a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQueryTest.java b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQueryTest.java
new file mode 100644
index 0000000..2fa2f27
--- /dev/null
+++ b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/MergedSearchQueryTest.java
@@ -0,0 +1,279 @@
+package org.eclipse.mdm.api.odsadapter.search;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.mdm.api.base.adapter.Attribute;
+import org.eclipse.mdm.api.base.adapter.EntityType;
+import org.eclipse.mdm.api.base.adapter.Relation;
+import org.eclipse.mdm.api.base.model.ContextType;
+import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.base.model.ValueType;
+import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.api.base.query.Filter;
+import org.eclipse.mdm.api.base.query.Record;
+import org.eclipse.mdm.api.base.query.Result;
+import org.eclipse.mdm.api.base.search.ContextState;
+import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
+import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
+
+public class MergedSearchQueryTest {
+
+	private static final String ID_NAME = "Id";
+
+	private EntityType fooEntityType;
+	private EntityType barEntityType;
+
+	private Attribute idAttribute;
+	private Attribute aAttribute;
+	private List<Attribute> attributes;
+	private ODSModelManager mockedODSModelManager;
+
+	@org.junit.Before
+	public void setup() {
+		idAttribute = mock(Attribute.class);
+		doReturn(ID_NAME).when(idAttribute).getName();
+
+		Relation fooParentRelation = mock(Relation.class);
+		doReturn(ContextType.UNITUNDERTEST.typeName()).when(fooParentRelation).getName();
+		fooEntityType = mock(EntityType.class, "Foo");
+		doReturn(Collections.singletonList(fooParentRelation)).when(fooEntityType).getParentRelations();
+		doReturn(idAttribute).when(fooEntityType).getIDAttribute();
+
+		Relation barParentRelation = mock(Relation.class);
+		doReturn(ContextType.UNITUNDERTEST.typeName()).when(barParentRelation).getName();
+		barEntityType = mock(EntityType.class, "Bar");
+		doReturn(Collections.singletonList(barParentRelation)).when(barEntityType).getParentRelations();
+		doReturn(idAttribute).when(barEntityType).getIDAttribute();
+
+		aAttribute = mock(Attribute.class);
+		doReturn("a").when(aAttribute).getName();
+		doReturn(barEntityType).when(aAttribute).getEntityType();
+		attributes = Collections.singletonList(aAttribute);
+
+		mockedODSModelManager = mock(ODSModelManager.class);
+		EntityConfig entityConfig = mock(EntityConfig.class);
+		doReturn(entityConfig).when(mockedODSModelManager).getEntityConfig(any(EntityConfig.Key.class));
+
+	}
+
+	@org.junit.Test
+	public void justByResult() {
+
+		List<Result> byResult = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+		List<Result> byOrder = Collections.emptyList();
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(null).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(3, results.size());
+		assertContainsValue(results, 3, "a", null);
+	}
+
+	@org.junit.Test
+	public void justByResultMeasured() {
+
+		List<Result> byResult = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+		List<Result> byOrder = Collections.emptyList();
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(ContextState.MEASURED).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(3, results.size());
+		assertContainsValue(results, 3, "a", null);
+	}
+
+	@org.junit.Test
+	public void justByResultOrdered() {
+
+		List<Result> byResult = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+		List<Result> byOrder = Collections.emptyList();
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(ContextState.ORDERED).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(0, results.size());
+	}
+
+	@org.junit.Test
+	public void justByOrder() {
+
+		List<Result> byResult = Collections.emptyList();
+
+		List<Result> byOrder = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(null).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(0, results.size());
+	}
+
+	@org.junit.Test
+	public void bothMeasured() {
+
+		List<Result> byResult = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+
+		List<Result> byOrder = Arrays.asList(
+				createResult("a", "0", "b"),
+				createResult("a", "1", "b"),
+				createResult("a", "2", "b"));
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(ContextState.MEASURED).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(3, results.size());
+		assertContainsValue(results, 3, "a", "b");
+	}
+
+	@org.junit.Test
+	public void bothOrdered() {
+
+		List<Result> byResult = Arrays.asList(
+				createResult("a", "0", "a"),
+				createResult("a", "1", "a"),
+				createResult("a", "2", "a"));
+
+		List<Result> byOrder = Arrays.asList(
+				createResult("a", "0", "b"),
+				createResult("a", "1", "b"),
+				createResult("a", "2", "b"));
+
+		MergedSearchQuery searchQuery = new MergedSearchQuery(
+				fooEntityType, createSearchQueryFactory(byResult, byOrder));
+
+		Filter mockedFilter = mock(Filter.class);
+		doReturn(ContextState.ORDERED).when(mockedFilter).getContext();
+
+		List<Result> results = searchQuery.fetch(attributes, mockedFilter);
+		assertNotNull(results);
+		assertEquals(3, results.size());
+		assertContainsValue(results, 3, "a", "b");
+	}
+
+	private void assertContainsValue(List<Result> results, int size, String measured, String ordered) {
+		List<Value> values = results.stream()
+				.map(records -> records.getValue(aAttribute))
+				.filter(v -> {
+					String currentMeasured = v.extract(ContextState.MEASURED);
+					String currentOrdered = v.extract(ContextState.ORDERED);
+
+					boolean matchMeasured = (measured == null && currentMeasured == null)
+							|| (measured.equals(currentMeasured));
+					boolean matchOrdered = (ordered == null && currentOrdered == null)
+							|| (ordered.equals(currentOrdered));
+					return matchMeasured && matchOrdered;
+				})
+				.collect(Collectors.toList());
+		assertNotNull(values);
+		assertFalse(values.isEmpty());
+		assertEquals(size, values.size());
+	}
+
+	private Result createResult(String name, String id, String testValue) {
+		Result result = new Result();
+
+		Record barRecord = createRecord(barEntityType, name, testValue);
+		result.addRecord(barRecord);
+
+		Record fooRecord = createRecord(fooEntityType, ID_NAME, id);
+		result.addRecord(fooRecord);
+
+		return result;
+	}
+
+	private Record createRecord(EntityType entityType, String name, String value) {
+
+		Record record = new Record(entityType);
+		Value val = ValueType.STRING.create(name, value);
+		record.addValue(val);
+		return record;
+	}
+
+	private Function<ContextState, BaseEntitySearchQuery> createSearchQueryFactory(List<Result> byResult,
+			List<Result> byOrder) {
+		return contextState -> {
+			if (ContextState.ORDERED.equals(contextState)) {
+				return new MockedSearchQuery(mockedODSModelManager, byOrder);
+			} else {
+				return new MockedSearchQuery(mockedODSModelManager, byResult);
+			}
+		};
+	}
+
+	private static class MockedSearchQuery extends BaseEntitySearchQuery {
+
+		List<Result> results;
+
+		public MockedSearchQuery(ODSModelManager odsModelManager, List<Result> results) {
+			super(odsModelManager, null, null, null);
+			this.results = results;
+		}
+
+		@Override
+		public List<Value> getFilterValues(final Attribute attribute) throws DataAccessException {
+			return Collections.emptyList();
+		}
+
+		@Override
+		public List<Result> fetchComplete(final List<EntityType> entityTypes, final Filter filter)
+				throws DataAccessException {
+			return results;
+		}
+
+		@Override
+		public List<Result> fetch(final List<Attribute> attributes, final Filter filter)
+				throws DataAccessException {
+			return results;
+		}
+	}
+}
diff --git a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceIT.java b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceIT.java
index f60b0a0..ae44358 100644
--- a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceIT.java
+++ b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceIT.java
@@ -44,6 +44,7 @@
 import org.eclipse.mdm.api.base.query.Filter;
 import org.eclipse.mdm.api.base.query.FilterItem;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.base.search.SearchService;
 import org.eclipse.mdm.api.dflt.ApplicationContext;
 import org.eclipse.mdm.api.dflt.model.Pool;
diff --git a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java
index 653c2db..5d1a549 100755
--- a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java
+++ b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java
@@ -22,6 +22,7 @@
 import org.eclipse.mdm.api.base.model.Test;
 import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.query.QueryService;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
 import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
index 376e776..1634346 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
@@ -14,7 +14,6 @@
 package org.eclipse.mdm.apicopy.boundary;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -23,6 +22,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -71,12 +71,16 @@
 
 		final Path exportAtfx = exportService.exportAtfx(basket);
 
-		List<File> files = Files.list(exportAtfx).map(p -> p.toFile()).collect(Collectors.toList());
-		if (files.size() == 1) {
+		final List<Path> paths;
+		try (Stream<Path> files = Files.walk(exportAtfx)) {
+			paths = files.filter(p -> p.toFile().isFile()).collect(Collectors.toList());
+		}
+
+		if (paths.size() == 1) {
 			StreamingOutput output = new StreamingOutput() {
 				@Override
 				public void write(OutputStream out) throws IOException {
-					try (final InputStream responseStream = new FileInputStream(files.get(0))) {
+					try (final InputStream responseStream = Files.newInputStream(paths.get(0))) {
 						int length;
 						byte[] buffer = new byte[1024];
 						while ((length = responseStream.read(buffer)) != -1) {
@@ -103,10 +107,9 @@
 				@Override
 				public void write(OutputStream output) throws IOException, WebApplicationException {
 					try (ZipOutputStream out = new ZipOutputStream(output)) {
-						List<File> files = Files.list(exportAtfx).map(p -> p.toFile()).collect(Collectors.toList());
-						for (File f : files) {
-							out.putNextEntry(new ZipEntry(f.getName()));
-							try (InputStream in = new FileInputStream(f)) {
+						for (Path p : paths) {
+							out.putNextEntry(new ZipEntry(exportAtfx.relativize(p).toString()));
+							try (InputStream in = Files.newInputStream(p)) {
 								int read = 0;
 								byte[] bytes = new byte[1024];
 
@@ -115,8 +118,6 @@
 								}
 							}
 						}
-					} catch (Exception e) {
-						e.printStackTrace();
 					} finally {
 						if (exportAtfx != null) {
 							try {
@@ -142,6 +143,8 @@
 	 * @throws IOException
 	 */
 	private void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
-		Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		try (Stream<Path> stream = Files.walk(pathToBeDeleted)) {
+			stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		}
 	}
 }
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportService.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportService.java
index 1280602..64e1568 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportService.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportService.java
@@ -86,8 +86,10 @@
 			Path atfxFile = tmpDir.resolve("export.atfx");
 			writeToFile(atfxFile, ExportTask.class.getResourceAsStream("/emptyAtfx.xml"));
 
-			Map<String, String> params = ImmutableMap.of("atfxfile", atfxFile.toFile().getAbsolutePath(),
-					"freetext.active", "false", "includeCatalog", "true");
+			Map<String, String> params = ImmutableMap.<String, String>builder()
+					.put("atfxfile", atfxFile.toFile().getAbsolutePath()).put("freetext.active", "false")
+					.put("includeCatalog", "true").put("WRITE_EXTERNALCOMPONENTS", "true").put("write_mode", "file")
+					.build();
 			ApplicationContext contextDst = connectContext(atfxContextFactoryClassname, params);
 
 			Map<String, List<MDMItem>> map = basket.getItems().stream().map(i -> i.getRestURI().getPath())
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
index d6db379..4a7a944 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
@@ -21,6 +21,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Stream;
 
 import javax.ejb.Stateless;
 import javax.inject.Inject;
@@ -130,7 +131,9 @@
 	 * @throws IOException
 	 */
 	private void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
-		Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		try (Stream<Path> paths = Files.walk(pathToBeDeleted)) {
+			paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		}
 	}
 
 	/**
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/ImportTask.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/ImportTask.java
index 1fc4ace..ebaa0ba 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/ImportTask.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/ImportTask.java
@@ -213,17 +213,22 @@
 		EntityHolder ehDst = mapSrcDstEntities.get(ehSrc);
 
 		if (null == ehDst) {
-			LOG.trace("Importing Project '{}'", projectSrc.getName());
 
-			Project projectDst = fetchOne(entityManagerDst, Project.class, projectSrc.getName())
-					.orElseGet(() -> entityFactoryDst.createProject(projectSrc.getName()));
+			Optional<Project> projectOpt = fetchOne(entityManagerDst, Project.class, projectSrc.getName());
 
-			copyValues(projectSrc, projectDst, Arrays.asList("Id", "Name"), false);
+			if (projectOpt.isPresent()) {
+				LOG.trace("Project '{}' already exists.", projectSrc.getName());
+				ehDst = new EntityHolder(projectOpt.get(), entityManagerDst);
+			} else {
+				LOG.trace("Importing Project '{}'.", projectSrc.getName());
 
-			// TODO only persist if changed
-			persist(transaction, projectDst);
+				Project projectDst = entityFactoryDst.createProject(projectSrc.getName());
+				copyValues(projectSrc, projectDst, Arrays.asList("Id", "Name"), false);
 
-			ehDst = new EntityHolder(projectDst, entityManagerDst);
+				persist(transaction, projectDst);
+				ehDst = new EntityHolder(projectDst, entityManagerDst);
+			}
+
 			mapSrcDstEntities.put(ehSrc, ehDst);
 
 			if (recursive) {
@@ -247,14 +252,21 @@
 					.get(new EntityHolder(entityManagerSrc.loadParent(poolSrc, Project.class).get(), entityManagerSrc))
 					.getEntity();
 
-			Pool poolDst = fetchChild(entityManagerDst, projectParentDst, Pool.class, poolSrc.getName())
-					.orElseGet(() -> entityFactoryDst.createPool(poolSrc.getName(), projectParentDst));
+			Optional<Pool> poolOpt = fetchChild(entityManagerDst, projectParentDst, Pool.class, poolSrc.getName());
 
-			copyValues(poolSrc, poolDst, Arrays.asList("Id", "Name"), false);
+			if (poolOpt.isPresent()) {
+				LOG.trace("Pool '{}' already exists.", poolSrc.getName());
+				ehDst = new EntityHolder(poolOpt.get(), entityManagerDst);
+			} else {
+				LOG.trace("Importing Pool '{}'.", poolSrc.getName());
 
-			persist(transaction, poolDst);
+				Pool poolDst = entityFactoryDst.createPool(poolSrc.getName(), projectParentDst);
+				copyValues(poolSrc, poolDst, Arrays.asList("Id", "Name"), false);
 
-			ehDst = new EntityHolder(poolDst, entityManagerDst);
+				persist(transaction, poolDst);
+				ehDst = new EntityHolder(poolDst, entityManagerDst);
+			}
+
 			mapSrcDstEntities.put(ehSrc, ehDst);
 
 			if (recursive) {
@@ -281,36 +293,41 @@
 
 			String rootProjectName = getProjectName(testSrc, entityManagerSrc);
 
-			Test testDst;
+			Optional<Test> testOpt = fetchChild(entityManagerDst, poolParentDst, Test.class, testSrc.getName());
 
-			if (templateTestDst.isPresent()) {
-				LOG.trace("Importing Test '{}' using TestTemplate '{}'", testSrc.getName(),
-						templateTestDst.get().getName());
-				testDst = fetchChild(entityManagerDst, poolParentDst, Test.class, testSrc.getName())
-						.orElseGet(() -> entityFactoryDst.createTest(testSrc.getName(), poolParentDst,
-								templateTestDst.get(), classificationUtil.getClassification(rootProjectName), false));
+			if (testOpt.isPresent()) {
+				LOG.trace("Test '{}' already exists.", testSrc.getName());
+				ehDst = new EntityHolder(testOpt.get(), entityManagerDst);
 			} else {
-				LOG.trace("Importing Test '{}' using no TestTemplate", testSrc.getName());
-				testDst = fetchChild(entityManagerDst, poolParentDst, Test.class, testSrc.getName())
-						.orElseGet(() -> entityFactoryDst.createTest(testSrc.getName(), poolParentDst, null,
-								classificationUtil.getClassification(rootProjectName), false));
+				Test testDst;
+				if (templateTestDst.isPresent()) {
+					LOG.trace("Importing Test '{}' using TestTemplate '{}'", testSrc.getName(),
+							templateTestDst.get().getName());
+					testDst = entityFactoryDst.createTest(testSrc.getName(), poolParentDst, templateTestDst.get(),
+							classificationUtil.getClassification(rootProjectName), false);
+				} else {
+					LOG.trace("Importing Test '{}' using no TestTemplate", testSrc.getName());
+					testDst = entityFactoryDst.createTest(testSrc.getName(), poolParentDst, null,
+							classificationUtil.getClassification(rootProjectName), false);
+				}
+
+				copyValues(testSrc, testDst, Arrays.asList("Id", "Name"), true);
+
+				// copy responsible person:
+				Optional<User> userSrc = testSrc.getResponsiblePerson();
+				if (userSrc.isPresent()) {
+					User userDst = entityManagerDst.loadAll(User.class, userSrc.get().getName()).stream().findFirst()
+							.orElseThrow(() -> new ApiCopyException(String.format(
+									"No User instance with name %s found in destination!", userSrc.get().getName())));
+
+					testDst.setResponsiblePerson(userDst);
+				}
+
+				persist(transaction, testDst);
+
+				ehDst = new EntityHolder(testDst, entityManagerDst);
 			}
 
-			copyValues(testSrc, testDst, Arrays.asList("Id", "Name"), true);
-
-			// copy responsible person:
-			Optional<User> userSrc = testSrc.getResponsiblePerson();
-			if (userSrc.isPresent()) {
-				User userDst = entityManagerDst.loadAll(User.class, userSrc.get().getName()).stream().findFirst()
-						.orElseThrow(() -> new ApiCopyException(String.format(
-								"No User instance with name %s found in destination!", userSrc.get().getName())));
-
-				testDst.setResponsiblePerson(userDst);
-			}
-
-			persist(transaction, testDst);
-
-			ehDst = new EntityHolder(testDst, entityManagerDst);
 			mapSrcDstEntities.put(ehSrc, ehDst);
 
 			if (recursive) {
@@ -323,7 +340,7 @@
 					throw new ApiCopyException("No relation to Test found at TestStep!");
 				}
 
-				copyTestSteps(testSrc, recursive, templateTestDst, testDst, transaction);
+				copyTestSteps(testSrc, recursive, templateTestDst, (Test) ehDst.getEntity(), transaction);
 				entityManagerSrc.loadChildren(testSrc, TestStep.class)
 						.forEach(testStep -> copyTestStep(testStep, recursive, transaction));
 			}
@@ -762,9 +779,8 @@
 
 						}
 
-						listWriteRequests
-								.add(createWriteRequest(channelGroupDst, channelDst, measuredValues,
-										sourceUnits.get(0)));
+						listWriteRequests.add(
+								createWriteRequest(channelGroupDst, channelDst, measuredValues, sourceUnits.get(0)));
 					}
 				}
 
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
index 417dfca..4d933d6 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
@@ -403,15 +403,11 @@
 			}
 		}
 
-		if (SequenceRepresentation.EXPLICIT.equals(seqRep) || SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
+		if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {
 			builder = wrb.explicit();
-
-			if (SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
-
-				return castBuilder(builder, AnyTypeValuesBuilder.class)
-						.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
-						.build();
-			}
+		} else if (SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
+			return wrb.explicitExternal().externalComponents(scalarType, measuredValues.getExternalComponents())
+					.independent(independent).build();
 		} else if (SequenceRepresentation.IMPLICIT_CONSTANT.equals(seqRep)) {
 			checkGenerationParameters(generationParameters, 1);
 
@@ -427,38 +423,36 @@
 			return wrb
 					.implicitSaw(scalarType, generationParameters[0], generationParameters[1], generationParameters[2])
 					.build();
-		} else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)
-				|| SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
+		} else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {
 			checkGenerationParameters(generationParameters, 2);
 
 			builder = wrb.rawLinear(generationParameters[0], generationParameters[1]);
 
-			if (SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
-				return castBuilder(builder, AnyTypeValuesBuilder.class)
-						.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
-						.build();
-			}
-		} else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)
-				|| SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
+		} else if (SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
+			checkGenerationParameters(generationParameters, 2);
+
+			return wrb.rawLinearExternal(generationParameters[0], generationParameters[1])
+					.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+					.build();
+		} else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {
 			checkGenerationParameters(generationParameters, 3);
 
 			builder = wrb.rawLinearCalibrated(generationParameters[0], generationParameters[1],
 					generationParameters[2]);
+		} else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
+			checkGenerationParameters(generationParameters, 3);
 
-			if (SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
-				return castBuilder(builder, AnyTypeValuesBuilder.class)
-						.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
-						.build();
-			}
-		} else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)
-				|| SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
+			return wrb
+					.rawLinearCalibratedExternal(generationParameters[0], generationParameters[1],
+							generationParameters[2])
+					.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+					.build();
+		} else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {
 			builder = wrb.rawPolynomial(generationParameters);
-
-			if (SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
-				return castBuilder(builder, AnyTypeValuesBuilder.class)
-						.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
-						.build();
-			}
+		} else if (SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
+			return wrb.rawPolynomialExternal(generationParameters)
+					.externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+					.build();
 		}
 
 		if (scalarType.isString()) {
@@ -629,7 +623,7 @@
 	}
 
 	<T extends NumericalValuesBuilder> T castBuilder(NumericalValuesBuilder builder, Class<T> cls) {
-		if (!cls.isInstance(builder)) {
+		if (!builder.getClass().isAssignableFrom(cls)) {
 			throw new IllegalStateException(String.format(
 					"Error creating the write values builder, expected class is %s, actual class is %s (likely column data type and sequence representation mismatch)!",
 					cls.getName(), (null == builder ? "???" : builder.getClass().getName())));
diff --git a/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
index 19e8542..3494439 100644
--- a/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
+++ b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
@@ -22,6 +22,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Comparator;
+import java.util.stream.Stream;
 
 public class FileUtils {
 	/**
@@ -49,6 +50,8 @@
 	 * @throws IOException
 	 */
 	public static void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
-		Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		try (Stream<Path> paths = Files.walk(pathToBeDeleted)) {
+			paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+		}
 	}
 }
diff --git a/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/control/RoundTripTest.java b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/control/RoundTripTest.java
new file mode 100644
index 0000000..66199e2
--- /dev/null
+++ b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/control/RoundTripTest.java
@@ -0,0 +1,204 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.apicopy.control;
+
+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 java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.asam.ods.AoException;
+import org.asam.ods.AoSession;
+import org.asam.ods.ApplicationAttribute;
+import org.asam.ods.DataType;
+import org.asam.ods.InstanceElement;
+import org.asam.ods.NameValueUnit;
+import org.asam.ods.TS_Union;
+import org.asam.ods.TS_Value;
+import org.eclipse.mdm.api.atfxadapter.ATFXContextFactory;
+import org.eclipse.mdm.api.base.ConnectionException;
+import org.eclipse.mdm.api.dflt.ApplicationContext;
+import org.eclipse.mdm.api.odsadapter.ODSContextFactory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.omg.CORBA.ORB;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+
+import de.rechner.openatfx.AoServiceFactory;
+
+@Ignore
+//FIXME 09.03.2021: 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 RoundTripTest {
+	public static final String ATFX_FILE = "src/main/resources/emptyAtfx.xml";
+
+	public static ApplicationContext contextODSServer;
+
+	@ClassRule
+	public static TemporaryFolder tmpFolder = new TemporaryFolder();
+
+	private static final String NAME_SERVICE = "corbaloc::1.2@%s:%s/NameService";
+
+	private static final String USER = "sa";
+	private static final String PASSWORD = "sa";
+
+	@BeforeClass
+	public static void init() throws ConnectionException, IOException {
+
+		// Source context
+		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);
+
+		contextODSServer = new ODSContextFactory().connect(connectionParameters);
+	}
+
+	@AfterClass
+	public static void teardown() throws ConnectionException {
+		contextODSServer.close();
+	}
+
+	@Test
+	public void testRoundTrip() throws ConnectionException, AoException, IOException {
+		File exportedAtfx = exportAtfx();
+
+		File readyToImportAtfx = addTemplateAttributes(exportedAtfx, "Roundtrip1", "PBN_UNECE_R51",
+				"PBN_UNECE_R51_RUN");
+
+		importAtfx(readyToImportAtfx);
+	}
+
+	private File exportAtfx() throws ConnectionException, IOException {
+
+		// target context
+		File f = tmpFolder.newFile("exported.atfx");
+		Files.copy(new File(ATFX_FILE), f);
+
+		Map<String, String> map = ImmutableMap.<String, String>builder().put("atfxfile", f.getAbsolutePath())
+				.put("freetext.active", "false").put("includeCatalog", "true").put("WRITE_EXTERNALCOMPONENTS", "true")
+				.put("write_mode", "file").build();
+
+		try (ApplicationContext contextDst = new ATFXContextFactory().connect(map)) {
+			ExportTask task = new ExportTask(contextODSServer, contextDst);
+			task.copyCatalog();
+		}
+
+		try (ApplicationContext contextDst = new ATFXContextFactory().connect(map)) {
+			ExportTask task = new ExportTask(contextODSServer, contextDst);
+			task.copy(Arrays.asList(
+					contextODSServer.getEntityManager().get().load(org.eclipse.mdm.api.base.model.Test.class, "2017")));
+		}
+		return f;
+	}
+
+	/**
+	 * open ATFx file, add template attributes and set the appropriate template
+	 * names.
+	 * 
+	 * @param exportedAtfx
+	 * @param newTestName
+	 * @param testTplName
+	 * @param testStepTplName
+	 * @return
+	 * @throws AoException
+	 */
+	private File addTemplateAttributes(File exportedAtfx, String newTestName, String testTplName,
+			String testStepTplName) throws AoException {
+
+		ORB orb = ORB.init(new String[] {}, System.getProperties());
+		AoSession session = AoServiceFactory.getInstance().newAoSession(orb, exportedAtfx);
+
+		session.startTransaction();
+		ApplicationAttribute testTemplate = session.getApplicationStructure().getElementByName("Test")
+				.createAttribute();
+		testTemplate.setName("template");
+		testTemplate.setDataType(DataType.DT_STRING);
+
+		ApplicationAttribute testStepTemplate = session.getApplicationStructure().getElementByName("TestStep")
+				.createAttribute();
+		testStepTemplate.setName("template");
+		testStepTemplate.setDataType(DataType.DT_STRING);
+
+		session.commitTransaction();
+		session.startTransaction();
+		InstanceElement[] insts = session.getApplicationStructure().getElementByName("Test").getInstances("*")
+				.nextN(1000);
+		for (InstanceElement inst : insts) {
+			TS_Union u = new TS_Union();
+			u.stringVal(testTplName);
+			inst.setValue(new NameValueUnit("template", new TS_Value(u, (short) 15), ""));
+
+			inst.setName(newTestName);
+		}
+
+		insts = session.getApplicationStructure().getElementByName("TestStep").getInstances("*").nextN(1000);
+		for (InstanceElement inst : insts) {
+			TS_Union u = new TS_Union();
+			u.stringVal(testStepTplName);
+			inst.setValue(new NameValueUnit("template", new TS_Value(u, (short) 15), ""));
+		}
+
+		session.commitTransaction();
+		session.close();
+
+		return exportedAtfx;
+	}
+
+	private void importAtfx(File readyToImportAtfx) throws ConnectionException {
+		Map<String, String> mapContextImportSrc = ImmutableMap.<String, String>builder()
+				.put("atfxfile", readyToImportAtfx.getAbsolutePath()).put("freetext.active", "false")
+				.put("includeCatalog", "true").put("WRITE_EXTERNALCOMPONENTS", "true").put("write_mode", "file")
+				.build();
+
+		try (ApplicationContext contextImportSrc = new ATFXContextFactory().connect(mapContextImportSrc)) {
+
+			ImportTask importTask = new ImportTask(contextImportSrc, contextODSServer, new DefaultTemplateManager());
+
+			importTask.copy(Arrays.asList(contextImportSrc.getEntityManager().get()
+					.loadAll(org.eclipse.mdm.api.base.model.Test.class).get(0)));
+		}
+	}
+}
diff --git a/nucleus/application/src/main/webconfig/web.xml b/nucleus/application/src/main/webconfig/web.xml
index 1f9d321..8dc4d2a 100644
--- a/nucleus/application/src/main/webconfig/web.xml
+++ b/nucleus/application/src/main/webconfig/web.xml
@@ -51,12 +51,13 @@
 		<dispatcher>REQUEST</dispatcher>
 	</filter-mapping>
 
+	<!-- If using SessionContext#isCallerInRole(), roles also have to be declared at class level with @DeclareRoles(value = { "Admin", "DescriptiveDataAuthor", "Guest" }) -->
 	<security-role>
 		<role-name>Admin</role-name>
 	</security-role>
 
 	<security-role>
-		<role-name>User</role-name>
+		<role-name>DescriptiveDataAuthor</role-name>
 	</security-role>
 	
 	<security-role>
diff --git a/nucleus/businessobjects/build.gradle b/nucleus/businessobjects/build.gradle
index 894efca..48c2414 100644
--- a/nucleus/businessobjects/build.gradle
+++ b/nucleus/businessobjects/build.gradle
@@ -22,6 +22,7 @@
 
 dependencies {
 	compile project(':nucleus:connector')
+	compile project(':nucleus:preferences')
 	compile 'com.fasterxml.jackson.core:jackson-databind:2.5.1'
 	compile 'io.vavr:vavr:0.9.1'
 	compile 'org.glassfish.jersey.media:jersey-media-multipart:2.23.2'
@@ -35,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/antlr/org/eclipse/mdm/businessobjects/filter/FilterGrammar.g4 b/nucleus/businessobjects/src/main/antlr/org/eclipse/mdm/businessobjects/filter/FilterGrammar.g4
index 017ef69..05047e9 100644
--- a/nucleus/businessobjects/src/main/antlr/org/eclipse/mdm/businessobjects/filter/FilterGrammar.g4
+++ b/nucleus/businessobjects/src/main/antlr/org/eclipse/mdm/businessobjects/filter/FilterGrammar.g4
@@ -1,16 +1,16 @@
-/********************************************************************************

- * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation

- *

- * See the NOTICE file(s) distributed with this work for additional

- * information regarding copyright ownership.

- *

- * This program and the accompanying materials are made available under the

- * terms of the Eclipse Public License v. 2.0 which is available at

- * http://www.eclipse.org/legal/epl-2.0.

- *

- * SPDX-License-Identifier: EPL-2.0

- *

- ********************************************************************************/

+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
 
 
 grammar FilterGrammar;
@@ -118,17 +118,18 @@
 DECIMAL              : '-'? DIGIT+ '.' DIGIT+ ;
 LONG                 : '-'? DIGIT+ ;
 BOOL                 : T R U E | F A L S E ;
+CONTEXT_STATE        : O R D E R E D | M E A S U R E D ;
 IDENTIFIER           : [a-zA-Z_] [a-zA-Z_0-9]* ;
-ATTRIBUTE_IDENTIFIER : IDENTIFIER '.' IDENTIFIER ;
-STRINGLITERAL        : SINGLE_QUOTE ( ~'\'' | ESCAPED_SINGLE_QUOTE )* SINGLE_QUOTE 

-                     | DOUBLE_QUOTE ( ~'\"' | ESCAPED_DOUBLE_QUOTE )* DOUBLE_QUOTE ;

+ATTRIBUTE_IDENTIFIER : (CONTEXT_STATE '.')? IDENTIFIER '.' IDENTIFIER ;
+STRINGLITERAL        : SINGLE_QUOTE ( ~'\'' | ESCAPED_SINGLE_QUOTE )* SINGLE_QUOTE 
+                     | DOUBLE_QUOTE ( ~'\"' | ESCAPED_DOUBLE_QUOTE )* DOUBLE_QUOTE ;
 WS                   : [ \r\t\u000C\n]+ -> skip ;
 
 fragment DIGIT : [0-9] ;
 fragment ESCAPED_SINGLE_QUOTE : '\\\'' ;
-fragment ESCAPED_DOUBLE_QUOTE : '\\\"' ;

+fragment ESCAPED_DOUBLE_QUOTE : '\\\"' ;
 fragment SINGLE_QUOTE         : '\'' ;
-fragment DOUBLE_QUOTE         : '\"' ;

+fragment DOUBLE_QUOTE         : '\"' ;
 
 /* case insensitive lexer matching */
 fragment A          : ('a'|'A');
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogAttributeResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogAttributeResource.java
index dfff1d8..86d5071 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogAttributeResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogAttributeResource.java
@@ -210,6 +210,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), CatalogAttribute.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogComponentResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogComponentResource.java
index 8254eea..1321be2 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogComponentResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogComponentResource.java
@@ -184,6 +184,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), CatalogComponent.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorAttributeResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorAttributeResource.java
index 4741145..f58a9ed 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorAttributeResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorAttributeResource.java
@@ -200,6 +200,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), CatalogAttribute.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorResource.java
index 6604c34..7d8045b 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/CatalogSensorResource.java
@@ -188,6 +188,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), CatalogSensor.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
index 8970874..43a9d81 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
@@ -125,6 +125,7 @@
 	@Operation(summary = "Get the ChannelGroup localizations", description = "Returns ChannelGroup localizations", responses = {
 			@ApiResponse(description = "The ChannelGroup localizations", content = @Content(schema = @Schema(implementation = I18NResponse.class))),
 			@ApiResponse(responseCode = "500", description = "Error") })
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupService.java
index 7e00f54..1c51796 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupService.java
@@ -110,6 +110,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link ChannelGroup} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.I18NActivity.localizeAttributes(sourceName, ChannelGroup.class);
 	}
@@ -120,6 +121,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link ChannelGroup} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.I18NActivity.localizeType(sourceName, ChannelGroup.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
index 677a8e5..25af914 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
@@ -131,6 +131,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam("SOURCENAME") String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelService.java
index 40f433d..fa737c7 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelService.java
@@ -130,6 +130,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Channel} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Channel.class);
 	}
@@ -140,6 +141,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Channel} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Channel.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentResource.java
index ca16a41..adf665b 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentResource.java
@@ -140,6 +140,7 @@
 			@ApiResponse(responseCode = "400", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/{" + REQUESTPARAM_SOURCENAME + "}/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
 			@Parameter(description = "Get all localized attributes?", required = true) @QueryParam("all") boolean all) {
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentService.java
index 2069e12..2f2cd8e 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/EnvironmentService.java
@@ -90,6 +90,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Environment} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Environment.class);
 	}
@@ -100,6 +101,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Environment} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Environment.class);
 	}
@@ -110,6 +112,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized attributes from the given source
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAllAttributes(String sourceName) {
 		return this.i18nActivity.localizeAllAttributes(sourceName);
 	}
@@ -120,6 +123,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized types from the given source
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeAllTypes(String sourceName) {
 		return this.i18nActivity.localizeAllTypes(sourceName);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationResource.java
new file mode 100644
index 0000000..6e41b65
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationResource.java
@@ -0,0 +1,152 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.boundary;
+
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import javax.ejb.EJB;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.eclipse.mdm.api.base.model.MDMLocalization;
+import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 
+ * @author jst
+ *
+ */
+
+@Tag(name = "Localization")
+@Path("/environments/{" + REQUESTPARAM_SOURCENAME + "}/mdmlocalizations")
+public class LocalizationResource {
+
+	private static final String LOCALIZATION_PREFIX = "mdm://i18n/";
+
+	@EJB
+	private LocalizationService localizationService;
+
+	@GET
+	@Operation(summary = "Get all localizations", description = "Returns all localizations as hierarchical map", responses = {
+			@ApiResponse(description = "All localizations as map", content = @Content(schema = @Schema(implementation = Map.class))),
+			@ApiResponse(responseCode = "400", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON + "+packed")
+	public Response read(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
+		return ServiceUtils.toResponse(groupByKey(localizationService.localize(sourceName)));
+	}
+
+	@GET
+	@Operation(summary = "Get all localizations as entities", description = "Returns all localizations as list of MDMLocalization entities.", responses = {
+			@ApiResponse(description = "All localizations", content = @Content(schema = @Schema(implementation = MDMEntityResponse.class))),
+			@ApiResponse(responseCode = "400", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	public Response readEntities(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
+		return ServiceUtils
+				.toResponse(new MDMEntityResponse(MDMLocalization.class, localizationService.localize(sourceName)));
+
+	}
+
+	@GET
+	@Operation(summary = "Get default mimetypes", description = "Returns all default mimetypes.", responses = {
+			@ApiResponse(description = "All default mimetypes", content = @Content(schema = @Schema(implementation = Map.class))),
+			@ApiResponse(responseCode = "400", description = "Error") })
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("mimetypes")
+	public Response getDefaultMimeTypes(
+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
+		return ServiceUtils.toResponse(localizationService.getDefaultMimeTypes(sourceName));
+
+	}
+
+	/**
+	 * Pack localization information, by organizing translations in a hierarchical
+	 * map.
+	 *
+	 * Names of {@link MDMLocalization} are used as keys. The keys are split on
+	 * slashes (not the escaped ones) and the prefix is omitted. The actual
+	 * translation values are held as array of the AliasNames.
+	 *
+	 * @param localizations the flat list of {@link MDMLocalization} entities.
+	 * @return hierarchical translation map
+	 */
+	private Map<String, Object> groupByKey(List<MDMLocalization> localizations) {
+		Map<String, Object> result = new HashMap<>();
+		for (MDMLocalization localization : localizations) {
+			// remove prefix since it contains no information
+			String key = localization.getName();
+			if (key.contains(LOCALIZATION_PREFIX)) {
+				key = key.substring(LOCALIZATION_PREFIX.length());
+			}
+			// create stack with key fragments in backward order
+			String[] keyFragments = key.split("/");
+			Stack<String> stack = new Stack<>();
+			for (int i = keyFragments.length - 1; i >= 0; --i) {
+				stack.push(keyFragments[i]);
+			}
+			// add to result map
+			addToMap(result, stack, localization.getTranslations());
+		}
+		return result;
+	}
+
+	/**
+	 * Adds translations recursively to a hierarchical map.
+	 * 
+	 * @param current      current submap
+	 * @param keyStack     remaining key fragements
+	 * @param translations array with translations
+	 * @return
+	 */
+	@SuppressWarnings("unchecked")
+	private void addToMap(Map<String, Object> current, Stack<String> keyStack, List<String> translations) {
+		String key = keyStack.pop().replace("%2F", "/");
+		// key chain contains more fragments
+		if (!keyStack.isEmpty()) {
+			// find existing sub-map or create and add new
+			Map<String, Object> next;
+			if (current.containsKey(key)) {
+				next = (Map<String, Object>) current.get(key);
+			} else {
+				next = new HashMap<String, Object>();
+				current.put(key, next);
+			}
+			// process next fragment in sub-map
+			addToMap(next, keyStack, translations);
+			// last key fragment reached, thus translations are inserted now.
+		} else {
+			// last wins
+			current.put(key, translations);
+		}
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationService.java
new file mode 100644
index 0000000..2d76ac5
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/LocalizationService.java
@@ -0,0 +1,63 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.businessobjects.boundary;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.model.MDMLocalization;
+import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.businessobjects.control.MDMEntityAccessException;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+
+@Stateless
+public class LocalizationService {
+
+	@Inject
+	private ConnectorService connectorService;
+
+	public List<MDMLocalization> localize(String sourceName) {
+		try {
+			return this.connectorService.getContextByName(sourceName).getEntityManager()
+					.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present!"))
+					.loadAll(MDMLocalization.class);
+		} catch (DataAccessException e) {
+			throw new MDMEntityAccessException(e.getMessage(), e);
+		}
+	}
+
+	public Map<String, String> getDefaultMimeTypes(String sourceName) {
+		try {
+			ModelManager mm = this.connectorService.getContextByName(sourceName).getModelManager()
+					.orElseThrow(() -> new MDMEntityAccessException("Model manager not present!"));
+
+			Map<String, String> map = new HashMap<>();
+
+			mm.listEntityTypes().stream()
+					.forEach(et -> map.put(ServiceUtils.workaroundForTypeMapping(et.getName()), mm.getMimeType(et)));
+
+			return map;
+
+		} catch (DataAccessException e) {
+			throw new MDMEntityAccessException(e.getMessage(), e);
+		}
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementResource.java
index 4f1ad3c..2b78924 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementResource.java
@@ -358,6 +358,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementService.java
index 8000170..bc25df0 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/MeasurementService.java
@@ -186,6 +186,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Measurement} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Measurement.class);
 	}
@@ -196,6 +197,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Measurement} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Measurement.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/NestedTemplateAttributeResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/NestedTemplateAttributeResource.java
index 74cc80d..81c4176 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/NestedTemplateAttributeResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/NestedTemplateAttributeResource.java
@@ -207,6 +207,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateAttribute.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PhysicalDimensionResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PhysicalDimensionResource.java
index a5c1cd5..7f5ae25 100755
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PhysicalDimensionResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PhysicalDimensionResource.java
@@ -166,6 +166,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), PhysicalDimension.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolResource.java
index 509cc5e..d7fe903 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolResource.java
@@ -143,6 +143,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolService.java
index 810a687..91265d2 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/PoolService.java
@@ -143,6 +143,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Pool} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Pool.class);
 	}
@@ -153,6 +154,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Pool} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Pool.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectResource.java
index dd175f3..f32b27b 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectResource.java
@@ -148,6 +148,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		Map<Attribute, String> localizedAttributeMap = this.projectService.localizeAttributes(sourceName);
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectService.java
index f6a4083..e04ad6c 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ProjectService.java
@@ -131,6 +131,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Project} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Project.class);
 	}
@@ -141,6 +142,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Project} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Project.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/QuantityResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/QuantityResource.java
index 6592be6..bf5075b 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/QuantityResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/QuantityResource.java
@@ -172,6 +172,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), Quantity.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
index 2aee5ba..3676b31 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
@@ -119,6 +119,11 @@
 	public static final String REQUESTPARAM_DESCRIPTION = "DESCRIPTION";
 
 	/**
+	 * Parameter holding the encoded string representation of a {@link Filter}
+	 */
+	public static final String REQUESTPARAM_FILTER = "FILTER";
+
+	/**
 	 * Parameter holding the name of the {@link Entity} in the request body
 	 */
 	public static final String ENTITYATTRIBUTE_NAME = "Name";
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateAttributeResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateAttributeResource.java
index 0e87195..cda4848 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateAttributeResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateAttributeResource.java
@@ -196,6 +196,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateAttribute.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
index c6a4463..64360a3 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateComponentResource.java
@@ -200,6 +200,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateComponent.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateRootResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateRootResource.java
index 0523634..4eee2ff 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateRootResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateRootResource.java
@@ -183,6 +183,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateRoot.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorAttributeResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorAttributeResource.java
index b7fceda..487a8e4 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorAttributeResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorAttributeResource.java
@@ -168,6 +168,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateAttribute.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorResource.java
index 73339d2..7fe28b5 100755
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateSensorResource.java
@@ -201,6 +201,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateSensor.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestResource.java
index 40771b9..8df1d06 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestResource.java
@@ -163,6 +163,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateTest.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepResource.java
index d3905bd..de65c88 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepResource.java
@@ -173,6 +173,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateTestStep.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepUsageResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepUsageResource.java
index 49ddd45..6ec1647 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepUsageResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TemplateTestStepUsageResource.java
@@ -186,6 +186,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), TemplateTestStepUsage.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
index bf888c6..7e4e8f5 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestResource.java
@@ -169,6 +169,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestService.java
index 42ad3b0..3e12db9 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestService.java
@@ -139,6 +139,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Test} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, Test.class);
 	}
@@ -149,6 +150,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link Test} type name
 	 */
+	@Deprecated
 	public Map<EntityType, String> localizeType(String sourceName) {
 		return this.i18nActivity.localizeType(sourceName, Test.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
index 44ec98f..3023ea5 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepResource.java
@@ -360,6 +360,7 @@
 			@ApiResponse(responseCode = "500", description = "Error") })
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(
 			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepService.java
index 23f2c94..8ce3a45 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/TestStepService.java
@@ -177,6 +177,7 @@
 	 * @return a map with the TestEquipment sensor context data (ordered and
 	 *         measured)
 	 */
+	@Deprecated
 	public Map<String, List<ContextSensor>> getSensors(String sourceName, String testStepId) {
 		return this.contextActivity.getTestStepSensorContext(sourceName, testStepId);
 	}
@@ -187,6 +188,7 @@
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @return the localized {@link TestStep} attributes
 	 */
+	@Deprecated
 	public Map<Attribute, String> localizeAttributes(String sourceName) {
 		return this.i18nActivity.localizeAttributes(sourceName, TestStep.class);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UnitResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UnitResource.java
index 396c079..6515830 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UnitResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/UnitResource.java
@@ -199,6 +199,7 @@
 	@Operation(summary = "Get the Unit localizations", description = "Returns Unit localizations", responses = {
 			@ApiResponse(description = "The Unit localizations", content = @Content(schema = @Schema(implementation = I18NResponse.class))),
 			@ApiResponse(responseCode = "500", description = "Error") })
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), Unit.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListResource.java
index f1ebdd1..1e2c541 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListResource.java
@@ -166,6 +166,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), ValueList.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListValueResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListValueResource.java
index 1a1cf96..0794b42 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListValueResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValueListValueResource.java
@@ -177,6 +177,7 @@
 	@GET
 	@Produces(MediaType.APPLICATION_JSON)
 	@Path("/localizations")
+	@Deprecated
 	public Response localize(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName) {
 		return ServiceUtils.buildLocalizationResponse(V(sourceName), ValueListValue.class, entityService);
 	}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FilterParser.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FilterParser.java
index f142405..687cc17 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FilterParser.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FilterParser.java
@@ -14,10 +14,15 @@
 
 package org.eclipse.mdm.businessobjects.control;
 
+import static java.util.stream.Collectors.joining;
+
 import java.time.LocalDateTime;
 import java.time.format.DateTimeParseException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.antlr.v4.runtime.ANTLRInputStream;
 import org.antlr.v4.runtime.BaseErrorListener;
@@ -31,7 +36,10 @@
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.model.ValueType;
 import org.eclipse.mdm.api.base.query.ComparisonOperator;
+import org.eclipse.mdm.api.base.query.Condition;
 import org.eclipse.mdm.api.base.query.Filter;
+import org.eclipse.mdm.api.base.query.FilterItem;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.eclipse.mdm.businessobjects.filter.FilterGrammarBaseVisitor;
 import org.eclipse.mdm.businessobjects.filter.FilterGrammarLexer;
 import org.eclipse.mdm.businessobjects.filter.FilterGrammarParser;
@@ -129,9 +137,10 @@
 		public Filter visitComparatorExpression(ComparatorExpressionContext ctx) {
 			ComparisonOperator operator = getOperator(ctx.op);
 			Attribute attribute = getAttribute(ctx.left);
+			ContextState contextState = getContextState(ctx.left);
 			Object value = createConditionValue(attribute.getValueType(), getValue(ctx.right));
 
-			return Filter.and().add(operator.create(attribute, value));
+			return Filter.and().add(operator.create(contextState, attribute, value));
 		}
 
 		/*
@@ -145,9 +154,10 @@
 		public Filter visitListComparatorExpression(ListComparatorExpressionContext ctx) {
 			ComparisonOperator operator = getOperator(ctx.op);
 			Attribute attribute = getAttribute(ctx.left);
+			ContextState contextState = getContextState(ctx.left);
 			Object value = createConditionValues(attribute.getValueType(), getValues(ctx.right));
 
-			return Filter.and().add(operator.create(attribute, value));
+			return Filter.and().add(operator.create(contextState, attribute, value));
 		}
 
 		/*
@@ -161,9 +171,10 @@
 		public Filter visitUnaryComparatorExpression(UnaryComparatorExpressionContext ctx) {
 			ComparisonOperator operator = getOperator(ctx.op);
 			Attribute attribute = getAttribute(ctx.left);
+			ContextState contextState = getContextState(ctx.left);
 			Object value = createConditionValue(attribute.getValueType(), null);
 
-			return Filter.and().add(operator.create(attribute, value));
+			return Filter.and().add(operator.create(contextState, attribute, value));
 		}
 
 		/*
@@ -241,11 +252,72 @@
 		 *                                  given by <code>ctx</code> cannot be found.
 		 */
 		private Attribute getAttribute(AttributeContext ctx) {
-			String[] name = ctx.getText().split("\\.");
-			return availableEntityTypes.stream().filter(e -> ServiceUtils.workaroundForTypeMapping(e).equals(name[0]))
-					.findAny()
-					.orElseThrow(() -> new IllegalArgumentException("Entity " + name[0] + " not found in data source!"))
-					.getAttribute(name[1]);
+			final String typeName = getEntityTypeName(ctx);
+			final String attributeName = getAttributeName(ctx);
+			final Predicate<EntityType> filterByEntityTypeName = entityType -> ServiceUtils
+					.workaroundForTypeMapping(entityType).equals(typeName);
+
+			return availableEntityTypes.stream().filter(filterByEntityTypeName).findAny()
+					.orElseThrow(
+							() -> new IllegalArgumentException("Entity " + typeName + " not found in data source!"))
+					.getAttribute(attributeName);
+		}
+
+		/**
+		 * Reads {@link ContextState} from {@link AttributeContext}
+		 *
+		 * @param ctx parsed entitytype / attribute
+		 * @return the matched {@link ContextState} or <code>null</code>
+		 */
+		private ContextState getContextState(AttributeContext ctx) {
+			final String[] names = ctx.getText().split("\\.");
+			if (names.length == 3) {
+				try {
+					return ContextState.valueOf(names[0].toUpperCase());
+				} catch (IllegalArgumentException exception) {
+					return null;
+				}
+			} else {
+				return null;
+			}
+		}
+
+		/**
+		 * Extracts the {@link EntityType} name from a give {@link AttributeContext}
+		 * 
+		 * @param ctx parsed entitytype / attribute
+		 * @return the name of the entity
+		 * @throws IllegalArgumentException when the attribute context is invalid
+		 */
+		private String getEntityTypeName(AttributeContext ctx) {
+			final String[] names = ctx.getText().split("\\.");
+			if (names.length == 2) {
+				return names[0];
+			} else if (names.length == 3) {
+				return names[1];
+			} else {
+				throw new IllegalArgumentException("Cannot parse EntityType from AttributeContext");
+			}
+
+		}
+
+		/**
+		 * Extracts the {@link Attribute} name from a give {@link AttributeContext}
+		 * 
+		 * @param ctx parsed entitytype / attribute
+		 * @return the name of the entity
+		 * @throws IllegalArgumentException when the attribute context is invalid
+		 */
+		private String getAttributeName(AttributeContext ctx) {
+			final String[] names = ctx.getText().split("\\.");
+			if (names.length == 2) {
+				return names[1];
+			} else if (names.length == 3) {
+				return names[2];
+			} else {
+				throw new IllegalArgumentException("Cannot parse AttributeName from AttributeContext");
+			}
+
 		}
 
 		/**
@@ -371,6 +443,77 @@
 	}
 
 	/**
+	 * Returns a string representation of the {@link Filter}. Inverse of
+	 * {@link FilterParser#parseFilterString(List, String)}.
+	 * 
+	 * @param filter {@link Filter}
+	 * @return a string representation of the {@link Filter}.
+	 * @throws IllegalArgumentExceptionThrown if given {@link Filter} is invalid.
+	 */
+	public static String toString(Filter filter) {
+		return filter.stream().map(FilterParser::toString).collect(Collectors.joining(" "));
+	}
+
+	/**
+	 * Returns a string representation of the {@link FilterItem}.
+	 * 
+	 * @param filterItem {@link FilterItem}
+	 * @return a string representation of the {@link FilterItem}.
+	 * @throws IllegalArgumentExceptionThrown if given {@link FilterItem} is
+	 *                                        invalid.
+	 */
+	private static String toString(FilterItem filterItem) {
+		if (filterItem.isCondition()) {
+			return toString(filterItem.getCondition());
+		} else if (filterItem.isBracketOperator()) {
+			return filterItem.getBracketOperator().toString();
+		} else if (filterItem.isBooleanOperator()) {
+			return filterItem.getBooleanOperator().toString();
+		} else {
+			throw new IllegalArgumentException(
+					"FilterItem is neither Condition nor BracketOperator nor BooleanOperator!");
+		}
+
+	}
+
+	/**
+	 * Returns a string representation of the {@link Condition}.
+	 * 
+	 * @param condition {@link Condition}
+	 * @return a string representation of the {@link Condition}.
+	 */
+	private static String toString(Condition condition) {
+		StringBuilder builder = new StringBuilder();
+
+		ContextState contextState = condition.getContextState();
+		if (contextState != null) {
+			builder.append(contextState.toString());
+			builder.append(".");
+		}
+
+		builder.append(ServiceUtils.workaroundForTypeMapping(condition.getAttribute().getEntityType()));
+		builder.append(".");
+		builder.append(condition.getAttribute().getName());
+		builder.append(" ");
+		builder.append(condition.getComparisonOperator());
+		builder.append(" ");
+
+		boolean isValid = condition.getValue().isValid();
+
+		if (isValid) {
+			if (condition.getValue().getValueType().isNumericalType()) {
+				builder.append("" + condition.getValue().extract());
+			} else if (condition.getValue().getValueType().isSequence()) {
+				builder.append(Stream.of(condition.getValue().extract()).map(Object::toString)
+						.collect(joining("', '", "('", "')")));
+			} else {
+				builder.append("\"" + condition.getValue().extract() + "\"");
+			}
+		}
+		return builder.toString();
+	}
+
+	/**
 	 * Creates the value for the condition from the value given as string.
 	 * 
 	 * @param valueType     The type that the value should have.
@@ -380,12 +523,13 @@
 	 */
 	private static Object createConditionValue(ValueType<?> valueType, String valueAsString) {
 		Object ret = null;
-		if (ValueType.BOOLEAN.equals(valueType)) {
+		boolean hasNoValue = valueAsString == null;
+		if (hasNoValue || ValueType.STRING.equals(valueType)) {
+			ret = valueAsString;
+		} else if (ValueType.BOOLEAN.equals(valueType)) {
 			ret = Boolean.valueOf(valueAsString);
 		} else if (ValueType.LONG.equals(valueType)) {
 			ret = Long.valueOf(valueAsString);
-		} else if (ValueType.STRING.equals(valueType)) {
-			ret = valueAsString;
 		} else if (ValueType.BYTE.equals(valueType)) {
 			ret = Byte.valueOf(valueAsString);
 		} else if (ValueType.DOUBLE.equals(valueType)) {
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java
index 58a5184..b2513bd 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/entity/MDMContextEntity.java
@@ -21,10 +21,8 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
-import org.eclipse.mdm.api.base.model.BaseEntity;
 import org.eclipse.mdm.api.base.model.ContextComponent;
 import org.eclipse.mdm.api.base.model.Entity;
-import org.eclipse.mdm.api.base.model.Sortable;
 import org.eclipse.mdm.api.base.model.Value;
 import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
 import org.eclipse.mdm.api.dflt.model.TemplateComponent;
@@ -126,10 +124,6 @@
 			listAttrs.add(toContextAttribute(attr, tplAttr));
 		}
 
-		// clear unneeded elements
-		listAttrs.removeIf(attr -> attr.getName().equals(BaseEntity.ATTR_NAME)
-				|| attr.getName().equals(BaseEntity.ATTR_MIMETYPE) || attr.getName().equals(Sortable.ATTR_SORT_INDEX));
-
 		// pre-sort attributes by sort index
 		Collections.sort(listAttrs, Comparator.comparing(MDMContextAttribute::getSortIndex,
 				Comparator.nullsFirst(Comparator.naturalOrder())));
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
index e4a1515..50020c9 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ServiceUtils.java
@@ -41,7 +41,12 @@
 import org.eclipse.mdm.api.base.query.FilterItem;
 import org.eclipse.mdm.api.dflt.ApplicationContext;
 import org.eclipse.mdm.api.dflt.EntityManager;
-import org.eclipse.mdm.api.dflt.model.*;
+import org.eclipse.mdm.api.dflt.model.CatalogAttribute;
+import org.eclipse.mdm.api.dflt.model.CatalogComponent;
+import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
+import org.eclipse.mdm.api.dflt.model.TemplateComponent;
+import org.eclipse.mdm.api.dflt.model.TemplateRoot;
+import org.eclipse.mdm.api.dflt.model.ValueList;
 import org.eclipse.mdm.businessobjects.control.FilterParser;
 import org.eclipse.mdm.businessobjects.entity.I18NResponse;
 import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
@@ -49,6 +54,8 @@
 import org.eclipse.mdm.businessobjects.service.EntityService;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableBiMap;
+
 import io.vavr.Value;
 import io.vavr.collection.Map;
 import io.vavr.collection.Stream;
@@ -56,6 +63,13 @@
 
 public final class ServiceUtils {
 
+	public static ImmutableBiMap<String, String> entityType2entityName = new ImmutableBiMap.Builder<String, String>()
+			.put("StructureLevel", "Pool")
+			.put("MeaResult", "Measurement")
+			.put("SubMatrix", "ChannelGroup")
+			.put("MeaQuantity", "Channel")
+			.build();
+
 	private ServiceUtils() {
 	}
 
@@ -128,18 +142,28 @@
 	 * @return MDM business object name
 	 */
 	public static String workaroundForTypeMapping(EntityType entityType) {
-		switch (entityType.getName()) {
-		case "StructureLevel":
-			return "Pool";
-		case "MeaResult":
-			return "Measurement";
-		case "SubMatrix":
-			return "ChannelGroup";
-		case "MeaQuantity":
-			return "Channel";
-		default:
-			return entityType.getName();
-		}
+		return workaroundForTypeMapping(entityType.getName());
+	}
+
+	/**
+	 * Simple workaround for naming mismatch between Adapter and Business object
+	 * names.
+	 *
+	 * @param entityType entity type
+	 * @return MDM business object name
+	 */
+	public static String workaroundForTypeMapping(String entityType) {
+		return entityType2entityName.getOrDefault(entityType, entityType);
+	}
+
+	/**
+	 * Inverse of {@link ServiceUtils#workaroundForTypeMapping(String)}
+	 * 
+	 * @param type MDM business object name
+	 * @return entity type name
+	 */
+	public static String invertMapping(String type) {
+		return entityType2entityName.inverse().getOrDefault(type, type);
 	}
 
 	/**
@@ -224,6 +248,17 @@
 		GenericEntity<Object> genEntity = new GenericEntity<>(response, response.getClass());
 		return Response.status(status).entity(genEntity).type(MediaType.APPLICATION_JSON).build();
 	}
+	
+	/**
+	 * Converts the given object to a {@link Response} with the given Status.OK
+	 *
+	 * @param response object to convert
+	 * @param status   {@link Status} of the {@link Response}
+	 * @return the created {@link Response}
+	 */
+	public static Response toResponse(Object response) {
+		return toResponse(response, Status.OK);
+	}
 
 	/**
 	 * Return the search attributes for the {@link ValueList} type.
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
new file mode 100644
index 0000000..ebc2098
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResource.java
@@ -0,0 +1,136 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.boundary;

+

+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.MEDIATYPE_APPLICATION_PROTOBUF;

+

+import javax.ejb.EJB;

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.WebApplicationException;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import javax.ws.rs.core.Response.Status;

+

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

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

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

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

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

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

+

+/**

+ * {@link NodeProvider} resource

+ * 

+ *

+ */

+@Tag(name = "NodeProvder")

+@Path("/nodeprovider")

+@Produces({ MediaType.APPLICATION_JSON, MEDIATYPE_APPLICATION_PROTOBUF })

+public class NodeProviderResource {

+

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

+

+	@EJB

+	private NodeProviderService nodeProviderService;

+

+	public NodeProviderResource() {

+	}

+

+	public NodeProviderResource(NodeProviderService nodeProviderService) {

+		this.nodeProviderService = nodeProviderService;

+	}

+

+	@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.getNodeProviderIDs()).build();

+	}

+

+	@GET

+	@Path("{NODEPROVIDER}")

+	@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())

+					.build();

+

+		} catch (RuntimeException e) {

+			LOG.error(e.getMessage(), e);

+			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);

+		}

+	}

+

+	@GET

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

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

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

+					.build();

+

+		} catch (RuntimeException e) {

+			LOG.error(e.getMessage(), e);

+			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);

+		}

+	}

+

+	@POST

+	@Consumes(MediaType.APPLICATION_JSON)

+	@Path("{NODEPROVIDER}")

+	@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())

+					.build();

+

+		} catch (RuntimeException e) {

+			LOG.error(e.getMessage(), e);

+			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);

+		}

+	}

+}

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
new file mode 100644
index 0000000..ccc91a2
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderService.java
@@ -0,0 +1,71 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.boundary;

+

+import java.io.Serializable;

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.List;

+

+import javax.ejb.Stateless;

+import javax.inject.Inject;

+

+import org.eclipse.mdm.nodeprovider.control.NodeProviderException;

+import org.eclipse.mdm.nodeprovider.control.NodeProviderRepository;

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

+

+/**

+ * NodeProvidertService Bean implementation with available {@link NodeProvider}

+ * operations

+ *

+ */

+@Stateless

+public class NodeProviderService implements Serializable {

+

+	private static final long serialVersionUID = -6499586790511058985L;

+

+	@Inject

+	private NodeProviderRepository nodeProviders;

+

+	public NodeProviderService() {

+	}

+

+	// for unit tests

+	protected NodeProviderService(NodeProviderRepository nodeProviders) {

+		this.nodeProviders = nodeProviders;

+	}

+

+	/**

+	 * @return list of nodeprovider IDs

+	 */

+	public List<String> getNodeProviderIDs() {

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

+		Collections.sort(names);

+		return names;

+	}

+

+	/**

+	 * @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 " + nodeproviderId + " does not exist!");

+		}

+		return nodeProvider;

+	}

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/AttributeContainer.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/AttributeContainer.java
new file mode 100644
index 0000000..d85c578
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/AttributeContainer.java
@@ -0,0 +1,50 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.control;
+
+import org.eclipse.mdm.api.base.adapter.Attribute;
+import org.eclipse.mdm.api.base.search.ContextState;
+
+public class AttributeContainer {
+
+	private ContextState contextState;
+	private Attribute attribute;
+	private boolean valid;
+	private Object value;
+
+	public AttributeContainer(final ContextState contextState, final Attribute attribute, final boolean valid,
+			final Object value) {
+		this.contextState = contextState;
+		this.attribute = attribute;
+		this.valid = valid;
+		this.value = value;
+	}
+
+	public ContextState getContextState() {
+		return contextState;
+	}
+
+	public Attribute getAttribute() {
+		return attribute;
+	}
+
+	public boolean isValid() {
+		return valid;
+	}
+
+	public Object getValue() {
+		return value;
+	}
+}
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
new file mode 100644
index 0000000..7c93adb
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
@@ -0,0 +1,231 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.control;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.mdm.api.base.ServiceNotProvidedException;
+import org.eclipse.mdm.api.base.adapter.Attribute;
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.model.BaseEntity;
+import org.eclipse.mdm.api.base.model.Channel;
+import org.eclipse.mdm.api.base.model.ChannelGroup;
+import org.eclipse.mdm.api.base.model.Entity;
+import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.Test;
+import org.eclipse.mdm.api.base.model.TestStep;
+import org.eclipse.mdm.api.base.query.ComparisonOperator;
+import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.api.base.query.Filter;
+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.NavigationActivity;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+import org.eclipse.mdm.nodeprovider.entity.NodeProvider;
+import org.eclipse.mdm.nodeprovider.utils.SerializationUtil;
+import org.eclipse.mdm.protobuf.Mdm.Node;
+
+/**
+ * Simple implementation of a {@link NodeProvider} which loads {@link Node}s
+ * based on {@link NavigationActivity}. The returned Node hierachy is similar to
+ * the hierarchy stored in the datastore, without options to change the
+ * structure. For a configurable {@link NodeProvider} see
+ * {@link GenericNodeProvider}.
+ *
+ */
+public class DefaultNodeProvider implements NodeProvider {
+
+	private final NavigationActivity navigationActivity;
+	private final ConnectorService connectorService;
+
+	/**
+	 * Construct a new {@link DefaultNodeProvider} using the given
+	 * {@link ConnectorService}.
+	 * 
+	 * @param connectorService
+	 */
+	public DefaultNodeProvider(final ConnectorService connectorService) {
+		this.connectorService = connectorService;
+		this.navigationActivity = new NavigationActivity(connectorService);
+	}
+
+	/**
+	 * Constructor for unit tests.
+	 * 
+	 * @param navigationActivity
+	 * @param connectorService
+	 */
+	DefaultNodeProvider(final NavigationActivity navigationActivity, final ConnectorService connectorService) {
+		this.connectorService = connectorService;
+		this.navigationActivity = navigationActivity;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public String getName() {
+		return "Default";
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public List<Node> getRoots() {
+		return getChildren(null);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public List<Node> getChildren(final Node parent) {
+
+		if (parent == null) {
+			return navigationActivity.getEnvironments().stream().map(this::convertNode).collect(Collectors.toList());
+		} else {
+			return convert(parent);
+		}
+	}
+
+	public Optional<? extends Entity> getParent(final Entity child) {
+		EntityManager entityManager = connectorService.getContextByName(child.getSourceName()).getEntityManager()
+				.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class));
+
+		String typeName = ServiceUtils.workaroundForTypeMapping(child.getTypeName());
+
+		if (compareTypes(Project.class, typeName)) {
+			return Optional.of(entityManager.loadEnvironment());
+		} else if (compareTypes(Pool.class, typeName)) {
+			return entityManager.loadParent(child, Project.class);
+		} else if (compareTypes(Test.class, typeName)) {
+			return entityManager.loadParent(child, Pool.class);
+		} else if (compareTypes(TestStep.class, typeName)) {
+			return entityManager.loadParent(child, Test.class);
+		} else if (compareTypes(Measurement.class, typeName)) {
+			return entityManager.loadParent(child, TestStep.class);
+		} else if (compareTypes(ChannelGroup.class, typeName)) {
+			return entityManager.loadParent(child, Measurement.class);
+		} else if (compareTypes(Channel.class, typeName)) {
+			return entityManager.loadParent(child, ChannelGroup.class);
+		} else {
+			return Optional.empty();
+		}
+
+	}
+
+	public Entity getEntity(final Node child) {
+		EntityManager entityManager = connectorService.getContextByName(child.getSource()).getEntityManager()
+				.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class));
+
+		String typeName = ServiceUtils.workaroundForTypeMapping(child.getType());
+
+		if (compareTypes(Project.class, typeName)) {
+			return entityManager.load(Project.class, child.getId());
+		} else if (compareTypes(Pool.class, typeName)) {
+			return entityManager.load(Pool.class, child.getId());
+		} else if (compareTypes(Test.class, typeName)) {
+			return entityManager.load(Test.class, child.getId());
+		} else if (compareTypes(TestStep.class, typeName)) {
+			return entityManager.load(TestStep.class, child.getId());
+		} else if (compareTypes(Measurement.class, typeName)) {
+			return entityManager.load(Measurement.class, child.getId());
+		} else if (compareTypes(ChannelGroup.class, typeName)) {
+			return entityManager.load(ChannelGroup.class, child.getId());
+		} else if (compareTypes(Channel.class, typeName)) {
+			return entityManager.load(Channel.class, child.getId());
+		} else {
+			throw new DataAccessException("Cannot load entity with type " + typeName + " and id" + child.getId());
+		}
+
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public List<Node> getTreePath(Node node) {
+		List<Node> nodes = new ArrayList<>();
+
+		Optional<? extends Entity> current = Optional.of(getEntity(node));
+
+		while (current.isPresent()) {
+			nodes.add(convertNode(current.get()));
+			current = getParent(current.get());
+		}
+		Collections.reverse(nodes);
+		return nodes;
+	}
+
+	private List<Node> convert(final Node parent) {
+		String typeName = ServiceUtils.workaroundForTypeMapping(parent.getType());
+		String source = parent.getSource();
+		List<? extends BaseEntity> entities;
+
+		String parentId = parent.getId();
+
+		boolean hasParentId = StringUtils.isNotBlank(parentId);
+
+		if (compareTypes(Environment.class, typeName)) {
+			entities = navigationActivity.getProjects(source);
+		} else if (hasParentId && compareTypes(Project.class, typeName)) {
+			entities = navigationActivity.getPools(source, parentId);
+		} else if (hasParentId && compareTypes(Pool.class, typeName)) {
+			entities = navigationActivity.getTests(source, parentId);
+		} else if (hasParentId && compareTypes(Test.class, typeName)) {
+			entities = navigationActivity.getTestSteps(source, parentId);
+		} else if (hasParentId && compareTypes(TestStep.class, typeName)) {
+			entities = navigationActivity.getMeasurements(source, parentId);
+		} else if (hasParentId && compareTypes(Measurement.class, typeName)) {
+			entities = navigationActivity.getChannelGroups(source, parentId);
+		} else if (hasParentId && compareTypes(ChannelGroup.class, typeName)) {
+			entities = navigationActivity.getChannels(source, parentId);
+		} else if (compareTypes(Channel.class, typeName)) {
+			entities = Collections.emptyList();
+		} else {
+			entities = navigationActivity.getEnvironments();
+		}
+
+		return convertNodes(entities);
+	}
+
+	private <T extends Entity> boolean compareTypes(final Class<T> typeClass, final String typeName) {
+		return typeClass.getSimpleName().equals(typeName);
+	}
+
+	private <T extends Entity> List<Node> convertNodes(final List<T> entities) {
+		return entities.stream().map(this::convertNode).collect(Collectors.toList());
+	}
+
+	private Node convertNode(Entity entity) {
+		ModelManager modelManager = connectorService.getContextByName(entity.getSourceName()).getModelManager()
+				.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));
+
+		Attribute idAttribute = modelManager.getEntityType(entity).getIDAttribute();
+		Filter filter = Filter.and().add(ComparisonOperator.EQUAL.create(idAttribute, entity.getID()));
+		return SerializationUtil.createNode(entity.getSourceName(),
+				ServiceUtils.workaroundForTypeMapping(entity.getTypeName()), entity.getID(), idAttribute.getName(),
+				filter, entity.getName());
+	}
+}
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
new file mode 100644
index 0000000..34a9382
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
@@ -0,0 +1,593 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.control;

+

+import static java.util.stream.Collectors.toMap;

+

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.Comparator;

+import java.util.HashSet;

+import java.util.List;

+import java.util.Set;

+import java.util.function.Supplier;

+import java.util.stream.Collectors;

+

+import javax.el.ValueExpression;

+

+import org.apache.commons.lang3.StringUtils;

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

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

+import org.eclipse.mdm.api.base.adapter.EntityType;

+import org.eclipse.mdm.api.base.adapter.ModelManager;

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.base.query.Aggregation;

+import org.eclipse.mdm.api.base.query.BooleanOperator;

+import org.eclipse.mdm.api.base.query.ComparisonOperator;

+import org.eclipse.mdm.api.base.query.Filter;

+import org.eclipse.mdm.api.base.query.FilterItem;

+import org.eclipse.mdm.api.base.query.Query;

+import org.eclipse.mdm.api.base.query.QueryService;

+import org.eclipse.mdm.api.base.query.Result;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+

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

+

+/**

+ * Configurable implementation of a {@link NodeProvider}. A

+ * {@link NodeProviderRoot} must be provided, which defines the structure of the

+ * tree defined by the returned {@link Node}s.

+ *

+ */

+public class GenericNodeProvider implements NodeProvider {

+

+	private final ConnectorService connectorService;

+

+	private final NodeProviderRoot root;

+

+	private final MDMExpressionLanguageService elService;

+

+	/**

+	 * Construct a new {@link GenericNodeProvider} using the given

+	 * {@link ConnectorService} and {@link NodeProviderRoot}.

+	 * 

+	 * @param connectorService

+	 * @param root

+	 */

+	public GenericNodeProvider(ConnectorService connectorService, NodeProviderRoot root,

+			MDMExpressionLanguageService expressionLanguageService) {

+		this.connectorService = connectorService;

+		this.root = root;

+		this.elService = expressionLanguageService;

+	}

+

+	/**

+	 * {@inheritDoc}

+	 */

+	@Override

+	public String getName() {

+		return root.getName();

+	}

+

+	/**

+	 * {@inheritDoc}

+	 */

+	@Override

+	public List<Node> getRoots() {

+		return getChildren(null);

+	}

+

+	/**

+	 * {@inheritDoc}

+	 */

+	@Override

+	public List<Node> getChildren(Node parent) {

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

+

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

+			root.getChildNodeLevel(context, parent).map(nl -> loadChildNodes(context, parent, nl))

+					.ifPresent(nodes::addAll);

+		}

+

+		// Sort the complete list again, as only Nodes from each context are sorted.

+		// TODO 30.11.2020: sort by defined OrderAttributes

+		nodes.sort(compareByLabel);

+

+		return nodes;

+	}

+

+	/**

+	 * {@inheritDoc}

+	 */

+	@Override

+	public List<Node> getTreePath(Node node) {

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

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

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

+

+		// reload node to get correct label

+		Query query = createQueryForNodeLevel(context, nodeLevel);

+

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

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

+		}

+

+		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(result -> convertNode(result, sourceName, childNodeLevel, filter, Aggregation.NONE))

+					.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<Result> results = getSearchService(context).getFilterResults(TestStep.class,

+					childNodeLevel.getLabelAttributes(), filter, childNodeLevel.getContextState());

+

+			// Filter values are always in measured

+			return new ArrayList<>(results.stream()

+					.map(result -> convertNodeByLabel(result, sourceName, childNodeLevel, filter, Aggregation.DISTINCT,

+							() -> null))

+					.filter(node -> !StringUtils.isEmpty(node.getLabel()))

+					.collect(toMap(Node::getLabel, n -> n, (n1, n2) -> n1))

+					.values());

+		} 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, Aggregation.NONE);

+			}

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

+					parentLevel.getLabelAttributes(), childFilter, "");

+

+			for (Result r : results) {

+				nodes.add(convertNode(r, node.getSource(), parentLevel, childFilter, Aggregation.NONE));

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

+					getLabelAttribute(nodeLevel), 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(getFilterAttribute(nodeLevel).getEntityType());

+

+		List<Attribute> attributes = getAttributesFromNodeLevel(nodeLevel);

+		List<Result> results = getSearchService(context).fetchResults(entityClass, attributes, filter, "");

+

+		return results.stream().map(r -> convertNode(r, context.getSourceName(), nodeLevel, filter, Aggregation.NONE))

+				.collect(Collectors.toList());

+	}

+

+	/**

+	 * @param entityType

+	 * @return entity class of the given entityType

+	 * @throws NodeProviderException if entity class is not found

+	 */

+	private Class<? extends Entity> getEntityClass(EntityType entityType) {

+		String entityName = ServiceUtils.workaroundForTypeMapping(entityType);

+

+		switch (entityName) {

+		case "Environment":

+			return Environment.class;

+		case "Project":

+			return Project.class;

+		case "Pool":

+			return Pool.class;

+		case "Test":

+			return Test.class;

+		case "TestStep":

+			return TestStep.class;

+		case "Measurement":

+			return Measurement.class;

+		case "ChannelGroup":

+			return ChannelGroup.class;

+		case "Channel":

+			return Channel.class;

+		case "Quantity":

+			return Quantity.class;

+		default:

+			throw new NodeProviderException("Could not find entity class for entity with name '" + entityName + "'.");

+		}

+	}

+

+	/**

+	 * Checks if the given filter matches the given sourceName

+	 * 

+	 * @param filter

+	 * @param sourceName

+	 * @return true, if the sourceName could match the filter.

+	 */

+	private boolean sourceNameMatches(Filter filter, String sourceName) {

+		if (filter.isEmtpty()) {

+			return true;

+		}

+		for (FilterItem filterItem : filter) {

+			if (filterItem.isCondition()) {

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

+				if (isEnvironment(a.getEntityType()) && "Id".equals(a.getName())) {

+					return sourceName.equals(filterItem.getCondition().getValue().extract(ValueType.STRING));

+				}

+			} else if (filterItem.isBooleanOperator() && filterItem.getBooleanOperator() == BooleanOperator.AND) {

+				continue;

+			} else {

+				throw new NodeProviderException("Filter not supported yet: " + filter);

+			}

+		}

+		return true;

+	}

+

+	/**

+	 * Returns the filter of the given node.

+	 * 

+	 * @param context application context

+	 * @param node

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

+	 */

+	private Filter getFilter(ApplicationContext context, Node node) {

+

+		Filter filter = Filter.and();

+		if (node != null) {

+			String filterStr = node.getFilter();

+

+			if (!filterStr.isEmpty()) {

+				ModelManager mm = context.getModelManager()

+						.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));

+				filter = FilterParser.parseFilterString(mm.listEntityTypes(), filterStr);

+			}

+		}

+		return filter;

+	}

+

+	/**

+	 * Converts a Result to a Node

+	 * 

+	 * @param result       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 result, String sourceName, NodeLevel nodeLevel, Filter parentFilter,

+			Aggregation aggregation) {

+

+		return convertNodeByLabel(result, sourceName, nodeLevel, parentFilter, aggregation, () -> {

+			Value idValue = result.getValue(nodeLevel.getEntityType().getIDAttribute(), aggregation);

+			return idValue.extract();

+		});

+	}

+

+	/**

+	 * Converts a Result to a Node

+	 *

+	 * @param result            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 convertNodeByLabel(Result result, String sourceName, NodeLevel nodeLevel, Filter parentFilter, Aggregation aggregation, Supplier<String> idSupplier) {

+		ContextState contextState = nodeLevel.getContextState();

+

+		List<AttributeContainer> labelAttrContainers = convertToAttributeContainers(contextState, result, aggregation,

+				nodeLevel.getLabelAttributes(), nodeLevel.getValuePrecision());

+

+		String label = createLabel(nodeLevel, labelAttrContainers);

+

+		List<AttributeContainer> filterAttrContainers = convertToAttributeContainers(contextState, result, aggregation, nodeLevel.getFilterAttributes(), nodeLevel.getValuePrecision());

+

+		Filter newFilter = getNewNodeFilter(contextState, nodeLevel, parentFilter, filterAttrContainers);

+

+		return convertNode(idSupplier.get(), label, sourceName, nodeLevel.getEntityType(), getFilterAttribute(nodeLevel).getName(), newFilter);

+	}

+

+	/**

+	 * Creates label for {@link NodeLevel} respecting the NodeLevels

+	 * {@link ValueExpression}.

+	 *

+	 * @param nodeLevel           the {@link NodeLevel}

+	 * @param attributeContainers the label attributes

+	 * @return

+	 */

+	private String createLabel(NodeLevel nodeLevel, List<AttributeContainer> attributeContainers) {

+		String label;

+		if (nodeLevel.getLabelExpression() == null) {

+			label = createBasicLabel(nodeLevel.getContextState(), attributeContainers);

+		} else {

+			label = elService.evaluateValueExpression(nodeLevel.getLabelExpression(), attributeContainers);

+		}

+		return label;

+	}

+

+	/**

+	 * Creates a basic label for {@link NodeLevel} of label attributes, while

+	 * *ignoring* the NodeLevels {@link ValueExpression}.

+	 *

+	 * @param contextState

+	 * @param attributeContainers the label attributes

+	 * @return

+	 */

+	private String createBasicLabel(ContextState contextState, List<AttributeContainer> attributeContainers) {

+		String labelValue = attributeContainers.stream().map(this::mapAttributeContainerValue)

+				.collect(Collectors.joining(" "));

+

+		if (contextState == null) {

+			return labelValue;

+		} else {

+			return String.format("%s %s", contextState, labelValue);

+		}

+	}

+

+	private List<AttributeContainer> convertLabelAttributesToContainers(Result result, Aggregation aggregation,

+			NodeLevel nodeLevel) {

+		return convertToAttributeContainers(nodeLevel.getContextState(), result, aggregation,

+				nodeLevel.getLabelAttributes(), nodeLevel.getValuePrecision());

+	}

+

+	private List<AttributeContainer> convertToAttributeContainers(ContextState contextState, Result result,

+			Aggregation aggregation, List<Attribute> attributes, ValuePrecision precision) {

+		return attributes.stream().map(attribute -> {

+			Value value = result.getValue(attribute, aggregation);

+			precision.applyOn(result.getValue(attribute, aggregation));

+			Object extractedValue = value.extract(contextState);

+			boolean valid = value.isValid(contextState);

+			return new AttributeContainer(contextState, attribute, valid, extractedValue);

+		}).collect(Collectors.toList());

+	}

+

+	private String mapAttributeContainerValue(AttributeContainer attributeContainer) {

+		if (attributeContainer.isValid()) {

+			String valueAsString = String.valueOf(attributeContainer.getValue());

+			return String.format("%s", valueAsString);

+		} else {

+			return String.format("%s=invalid", attributeContainer.getAttribute().getName());

+		}

+	}

+

+	/**

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

+		List<Attribute> attributes = getAttributesFromNodeLevel(nodeLevel);

+		Query query = context.getQueryService().orElseThrow(() -> new ServiceNotProvidedException(QueryService.class))

+				.createQuery().select(attributes).group(attributes);

+

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

+

+		return query;

+	}

+

+	private List<Attribute> getAttributesFromNodeLevel(NodeLevel nodeLevel) {

+		Set<Attribute> attributes = new HashSet<>();

+		attributes.addAll(nodeLevel.getFilterAttributes());

+		attributes.addAll(nodeLevel.getLabelAttributes());

+

+		return new ArrayList<>(attributes);

+	}

+

+	private Node convertNode(String id, String label, String sourceName, EntityType entityType, String idAttr,

+			Filter newFilter) {

+		return SerializationUtil.createNode(sourceName, ServiceUtils.workaroundForTypeMapping(entityType), id, idAttr,

+				newFilter, label);

+	}

+

+	private Filter getNewNodeFilter(ContextState contextState, NodeLevel nodeLevel, Filter parentFilter,

+			List<AttributeContainer> attributeContainers) {

+		Filter newFilter = null;

+

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

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

+			newFilter = Filter.and();

+		} else if (parentFilter != null) {

+			newFilter = parentFilter.copy();

+

+			for (AttributeContainer attributeContainer : attributeContainers) {

+				if (attributeContainer.isValid()) {

+					newFilter.add(nodeLevel.getValuePrecision().getCondition(contextState,

+							attributeContainer.getAttribute(), attributeContainer.getValue()));

+				} else {

+					newFilter.add(

+							ComparisonOperator.IS_NULL.create(contextState, attributeContainer.getAttribute(), null));

+				}

+			}

+		}

+		return newFilter;

+	}

+

+	/**

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

+	}

+

+	private Attribute getFilterAttribute(NodeLevel nodeLevel) {

+		return nodeLevel.getFilterAttributes().stream().findFirst()

+				.orElseThrow(() -> new IllegalStateException("woops?!"));

+	}

+

+	private Attribute getLabelAttribute(NodeLevel nodeLevel) {

+		return nodeLevel.getLabelAttributes().stream().findFirst()

+				.orElseThrow(() -> new IllegalStateException("woops?!"));

+	}

+

+	/**

+	 * Compares {@link Node}s be {@link Node#getLabel()}.

+	 */

+	public static final Comparator<Node> compareByLabel = Comparator.comparing(Node::getLabel);

+

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/MDMExpressionLanguageService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/MDMExpressionLanguageService.java
new file mode 100644
index 0000000..991f101
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/MDMExpressionLanguageService.java
@@ -0,0 +1,99 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.control;
+
+import java.lang.reflect.Method;
+
+import javax.el.ELException;
+import javax.el.ELManager;
+import javax.el.ELProcessor;
+import javax.el.ExpressionFactory;
+import javax.el.StandardELContext;
+import javax.el.ValueExpression;
+
+import org.eclipse.mdm.nodeprovider.entity.AttributeContainerListResovler;
+import org.eclipse.mdm.nodeprovider.utils.ExpressionLanguageMethodProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MDMExpressionLanguageService {
+
+	private static final Logger LOG = LoggerFactory.getLogger(MDMExpressionLanguageService.class);
+
+	private final ELProcessor processor;
+	private final ExpressionFactory factory;
+
+	/**
+	 * Intentionally empty
+	 */
+	public MDMExpressionLanguageService() {
+		processor = new ELProcessor();
+		factory = ELManager.getExpressionFactory();
+		init();
+	}
+
+	/**
+	 * Initialize functionMapper and resolvers for expression language
+	 * interpretation.
+	 */
+	public void init() {
+		ELManager manager = processor.getELManager();
+		manager.addELResolver(new AttributeContainerListResovler());
+
+		for (Method method : ExpressionLanguageMethodProvider.class.getMethods()) {
+			try {
+				processor.defineFunction("fn", "", method);
+			} catch (NoSuchMethodException e) {
+				LOG.warn("Could not add method: {}", method);
+			}
+		}
+	}
+
+	/**
+	 * Parses {@link String} to a  {@link ValueExpression}.
+	 *
+	 * @param expressionString the value expression
+	 * @return the value expression, or null if none present
+	 */
+	public ValueExpression parseValueExpression(String expressionString) {
+		StandardELContext elContext = processor.getELManager().getELContext();
+		ValueExpression valueExpression = null;
+		if (expressionString != null) {
+			valueExpression = factory.createValueExpression(elContext, expressionString, String.class);
+		}
+		return valueExpression;
+	}
+
+	/**
+	 * Evaluates the {@link ValueExpression} with respect to given properties.
+	 * 
+	 * @param valueExpression the value expression
+	 * @param properties      the properties to be inserted
+	 * @return
+	 */
+	public String evaluateValueExpression(ValueExpression valueExpression, Object... properties) {
+		StandardELContext context = processor.getELManager().getELContext();
+		for (Object prop : properties) {
+			context.putContext(prop.getClass(), prop);
+		}
+		try {
+			Object value = valueExpression.getValue(context);
+			return String.valueOf(value);
+		} catch (NullPointerException | ELException exception) {
+			LOG.error("Could not retrieve value from given EL context", exception);
+			return null;
+		}
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderException.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderException.java
new file mode 100644
index 0000000..0a258ee
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderException.java
@@ -0,0 +1,39 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.control;

+

+/**

+ * Custom exception for NodeProvider related exceptions.

+ *

+ */

+public class NodeProviderException extends RuntimeException {

+

+	private static final long serialVersionUID = -271138672305207336L;

+

+	/**

+	 * @param message

+	 */

+	public NodeProviderException(String message) {

+		super(message);

+	}

+

+	/**

+	 * @param message

+	 * @param t

+	 */

+	public NodeProviderException(String message, Throwable t) {

+		super(message, t);

+	}

+}

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
new file mode 100644
index 0000000..5920a4a
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
@@ -0,0 +1,130 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.control;

+

+import java.io.Serializable;

+import java.security.Principal;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+import javax.annotation.PostConstruct;

+import javax.enterprise.context.SessionScoped;

+import javax.inject.Inject;

+

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

+ *

+ */

+@SessionScoped

+public class NodeProviderRepository implements Serializable {

+

+	private static final long serialVersionUID = -3081666937067348673L;

+

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

+

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

+

+	@Inject

+	private ConnectorService connectorService;

+

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

+

+		DefaultNodeProvider defaultNP = new DefaultNodeProvider(connectorService);

+

+		putNodeProvider("default", defaultNP);

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

+

+		MDMExpressionLanguageService expressionLanguageService = new MDMExpressionLanguageService();

+

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

+		for (PreferenceMessage msg : msgs) {

+			try {

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

+				putNodeProvider(root.getId(),

+						new GenericNodeProvider(connectorService, root, expressionLanguageService));

+				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/AttributeContainerListResovler.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/AttributeContainerListResovler.java
new file mode 100644
index 0000000..dceb689
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/AttributeContainerListResovler.java
@@ -0,0 +1,71 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.entity;
+
+import java.beans.FeatureDescriptor;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Optional;
+
+import javax.el.ELContext;
+import javax.el.ELResolver;
+
+import org.eclipse.mdm.nodeprovider.control.AttributeContainer;
+
+public class AttributeContainerListResovler extends ELResolver {
+
+	@Override
+	public void setValue(ELContext context, Object base, Object property, Object value) {
+		// Intentionally empty.
+	}
+
+	@Override
+	public boolean isReadOnly(ELContext context, Object base, Object property) {
+		return true;
+	}
+
+	@Override
+	public Object getValue(ELContext context, Object base, Object property) {
+		Optional<Object> resolved = getValue(context, property);
+		resolved.ifPresent(o -> context.setPropertyResolved(base, property));
+		return resolved.orElse(null);
+	}
+
+	@Override
+	public Class<?> getType(ELContext context, Object base, Object property) {
+		Optional<Object> resolved = getValue(context, property);
+		return resolved.<Class<?>>map(Object::getClass).orElse(null);
+	}
+
+	private Optional<Object> getValue(ELContext context, Object property) {
+		ArrayList<AttributeContainer> labelAttrContainers = (ArrayList<AttributeContainer>) context
+				.getContext(ArrayList.class);
+		return labelAttrContainers.stream()
+				.filter(attr -> attr.getAttribute().getName().equalsIgnoreCase(String.valueOf(property)))
+				.map(AttributeContainer::getValue).findFirst();
+	}
+
+	@Override
+	public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public Class<?> getCommonPropertyType(ELContext context, Object base) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeLevel.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeLevel.java
new file mode 100644
index 0000000..36706c4
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeLevel.java
@@ -0,0 +1,240 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.entity;

+

+import static java.util.stream.Collectors.toList;

+

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.List;

+

+import javax.el.ValueExpression;

+

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

+import org.eclipse.mdm.api.base.adapter.EntityType;

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

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

+

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

+

+import jersey.repackaged.com.google.common.collect.Lists;

+

+/**

+ * A {@link NodeLevel} describes one level in the hierarchy of the navigator

+ * tree. A {@link NodeLevel} references another {@link NodeLevel} defining the

+ * level below or in other words its children. Together with

+ * {@link NodeProviderRoot} it describes the hierarchy of provided by

+ * {@link GenericNodeProvider}.

+ *

+ */

+public class NodeLevel {

+

+	private EntityType type;

+

+	private List<Attribute> filterAttributes;

+	private List<Attribute> labelAttributes;

+	private ValueExpression labelExpression;

+	private ValuePrecision valuePrecision;

+	private List<SortAttribute> orderAttributes;

+

+	private ContextState contextState = null;

+

+	private boolean isVirtual;

+

+	private NodeLevel child;

+

+	/**

+	 * Constructs NodeLevel for the given {@link EntityType}. Using default ID and

+	 * Name attributes.

+	 * 

+	 * @param type

+	 */

+	public NodeLevel(EntityType type) {

+		this(type, type.getIDAttribute(), type.getNameAttribute(), Collections.emptyList());

+	}

+

+	/**

+	 * Constructs NodeLevel for the given {@link EntityType} using the given ID and

+	 * label attributes.

+	 * 

+	 * @param type

+	 * @param filterAttribute

+	 * @param labelAttribute

+	 */

+	public NodeLevel(EntityType type, Attribute filterAttribute, Attribute labelAttribute) {

+		this(type, filterAttribute, labelAttribute, Arrays.asList(new SortAttribute(labelAttribute)));

+	}

+

+	/**

+	 * Constructs NodeLevel for the given {@link EntityType} using the given ID and

+	 * label attributes with the given list of order attributes.

+	 *

+	 * @param type

+	 * @param filterAttribute

+	 * @param labelAttribute

+	 * @param orderAttributes

+	 */

+	public NodeLevel(EntityType type, Attribute filterAttribute, Attribute labelAttribute,

+			List<SortAttribute> orderAttributes) {

+		this(type, Arrays.asList(filterAttribute), Arrays.asList(labelAttribute), orderAttributes);

+	}

+

+	/**

+	 * Constructs NodeLevel for the given {@link EntityType} using the given ID and

+	 * label attributes with the given list of order attributes.

+	 *

+	 * @param type

+	 * @param filterAttributes

+	 * @param labelAttributes

+	 */

+	public NodeLevel(EntityType type, List<Attribute> filterAttributes, List<Attribute> labelAttributes) {

+		this(type, filterAttributes, labelAttributes,

+				labelAttributes.stream().map(SortAttribute::new).collect(toList()));

+	}

+

+	/**

+	 * Constructs NodeLevel for the given {@link EntityType} using the given ID and

+	 * label attributes with the given list of order attributes.

+	 * 

+	 * @param type

+	 * @param filterAttribute

+	 * @param labelAttribute

+	 * @param orderAttributes

+	 */

+	public NodeLevel(EntityType type, List<Attribute> filterAttribute, List<Attribute> labelAttribute,

+			List<SortAttribute> orderAttributes) {

+		this.type = type;

+		this.filterAttributes = filterAttribute;

+		this.labelAttributes = labelAttribute;

+		this.orderAttributes = Lists.newArrayList(orderAttributes);

+		this.valuePrecision = ValuePrecision.EXACT;

+	}

+

+	/**

+	 * @param child to set

+	 */

+	public void setChild(NodeLevel child) {

+		this.child = child;

+	}

+

+	/**

+	 * @param contextState to set

+	 */

+	public void setContextState(ContextState contextState) {

+		this.contextState = contextState;

+	}

+

+	/**

+	 * @param isVirtual

+	 */

+	public void setVirtual(boolean isVirtual) {

+		this.isVirtual = isVirtual;

+	}

+

+	/**

+	 * @return the entity type

+	 */

+	public EntityType getEntityType() {

+		return type;

+	}

+

+	/**

+	 * @return the entity type's name

+	 */

+	public String getType() {

+		return type.getName();

+	}

+

+	/**

+	 * @return isVirtual

+	 */

+	public boolean isVirtual() {

+		return isVirtual;

+	}

+

+	/**

+	 * @return the ID attribute

+	 */

+	public List<Attribute> getFilterAttributes() {

+		return filterAttributes;

+	}

+

+	/**

+	 * @return the label attribute

+	 */

+	public List<Attribute> getLabelAttributes() {

+		return labelAttributes;

+	}

+

+	/**

+	 * @return the context state

+	 */

+	public ContextState getContextState() {

+		return contextState;

+	}

+

+	/**

+	 * @return the child node level

+	 */

+	public NodeLevel getChild() {

+		return child;

+	}

+

+	/**

+	 * @return the list with order attributes

+	 */

+	public List<SortAttribute> getOrderAttributes() {

+		return orderAttributes;

+	}

+

+	/**

+	 * {@inheritDoc}

+	 */

+	@Override

+	public String toString() {

+		return MoreObjects.toStringHelper(NodeLevel.class).add("type", type).add("filterAttributes", filterAttributes)

+				.add("labelAttributes", labelAttributes).add("labelExpression", labelExpression)

+				.add("orderAttribute", orderAttributes).add("contextState", contextState).add("isVirtual", isVirtual)

+				.add("valuePrecision", valuePrecision).add("child", child).toString();

+	}

+

+	/**

+	 * @return the labelExpression

+	 */

+	public ValueExpression getLabelExpression() {

+		return labelExpression;

+	}

+

+	/**

+	 * @param labelExpression the labelExpression to set

+	 */

+	public void setLabelExpression(ValueExpression labelExpression) {

+		this.labelExpression = labelExpression;

+	}

+

+	/**

+	 * @return the valuePrecision

+	 */

+	public ValuePrecision getValuePrecision() {

+		return valuePrecision;

+	}

+

+	/**

+	 * @param valuePrecision the valuePrecision to set

+	 */

+	public void setValuePrecision(ValuePrecision valuePrecision) {

+		this.valuePrecision = valuePrecision != null ? valuePrecision : ValuePrecision.EXACT;

+	}

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProvider.java
new file mode 100644
index 0000000..60c3274
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProvider.java
@@ -0,0 +1,59 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.entity;

+

+import java.util.List;

+

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

+

+/**

+ * NodeProvider is responsible for providing an hierarchical view on the

+ * instances in openMDM.

+ *

+ */

+public interface NodeProvider {

+

+	/**

+	 * Returns the NodeProviders human readable name.

+	 * 

+	 * @return name of the NodeProvider

+	 */

+	String getName();

+

+	/**

+	 * Returns the list of root nodes of the NodeProvider.

+	 * 

+	 * @return list of root nodes

+	 */

+	List<Node> getRoots();

+

+	/**

+	 * Returns the child nodes of the given {@link Node}.

+	 * 

+	 * @param parent Parent node to retrieve its children from

+	 * @return list of child nodes

+	 */

+	List<Node> getChildren(Node parent);

+

+	/**

+	 * Returns the list of ancestors for the given node. So the last node in the

+	 * list is the given nodes parent and so on until a root node is reached, which

+	 * will be the first node in the list.

+	 * 

+	 * @param node

+	 * @return a list of ancestors o

+	 */

+	List<Node> getTreePath(Node node);

+}

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
new file mode 100644
index 0000000..7d7ba72
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
@@ -0,0 +1,197 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.entity;

+

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Optional;

+

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

+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

+ * {@link GenericNodeProvider}. It consists of an ID to uniquely identify the

+ * NodeProvider and a name that is displayed to the user. Furthermore it may

+ * contain {@link NodeLevel}s for each data source name defining the roots of

+ * the navigation trees. The special data source name

+ * {@link NodeProviderRoot#WILDCARD} is used for the fallback {@link NodeLevel}

+ * that is used if no other entry is present.

+ *

+ */

+public class NodeProviderRoot {

+

+	public static final String WILDCARD = "*";

+

+	private String id;

+	private String name;

+	private Map<String, NodeLevel> contexts = new HashMap<>();

+

+	/**

+	 * Default constructor

+	 */

+	public NodeProviderRoot() {

+	}

+

+	/**

+	 * @return the id

+	 */

+	public String getId() {

+		return id;

+	}

+

+	/**

+	 * @param id the id to set

+	 */

+	public void setId(String id) {

+		this.id = id;

+	}

+

+	/**

+	 * @return the name

+	 */

+	public String getName() {

+		return name;

+	}

+

+	/**

+	 * @param name the name to set

+	 */

+	public void setName(String name) {

+		this.name = name;

+	}

+

+	/**

+	 * @return the contexts

+	 */

+	public Map<String, NodeLevel> getContexts() {

+		return contexts;

+	}

+

+	/**

+	 * @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.

+	 * 

+	 * @param sourceName

+	 * @return NodeLevel for the given sourceName

+	 */

+	public NodeLevel getNodeLevel(String sourceName) {

+		NodeLevel nl = contexts.get(sourceName);

+		if (nl == null) {

+			// if the NodeProviderRoot was constructed via NodeLevelUtil contexts already

+			// contains a NodeLevel for each source name. If still nothing is found we check

+			// the wildcard again just to be sure.

+			nl = contexts.get(WILDCARD);

+		}

+		return nl;

+	}

+

+	/**

+	 * Find the parent NodeLevel for a given nodeLevel.

+	 * 

+	 * @param context

+	 * @param nodeLevel

+	 * @return parent NodeLevel

+	 */

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

+	 * @return NodeLevel for given type and idAttribute

+	 */

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

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

+

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

+			n = n.getChild();

+		}

+

+		return n;

+	}

+

+	/**

+	 * @param nodeLevel

+	 * @param type

+	 * @param filterAttribute

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

+	 */

+	/**

+	 * TODO 05.02.2021, jst: Match should respect labelAttributes / value precision

+	 * to work properly for nested virtual nodes of same type with different value

+	 * precision.

+	 * 

+	 * Current fails in scenarios like:

+	 * 

+	 * (...) | |---Test.DateCreated - YEAR | |---Test.DateCreated - MONTH | |---

+	 * Test.DateCreated - DAY

+	 */

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

+		String first = nodeLevel.getFilterAttributes().stream().findFirst().map(Attribute::getName).orElse(null);

+		return ServiceUtils.workaroundForTypeMapping(type)

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

+				&& (Strings.isNullOrEmpty(filterAttribute) || filterAttribute.equals(first));

+	}

+

+}

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
new file mode 100644
index 0000000..b3c9cc7
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
@@ -0,0 +1,25 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.entity;

+

+/**

+ * Enum for defining an order.

+ *

+ * @see SortAttribute

+ */

+public enum Order {

+

+	ASCENDING, DESCENDING;

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/SortAttribute.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/SortAttribute.java
new file mode 100644
index 0000000..0819a37
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/SortAttribute.java
@@ -0,0 +1,60 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.entity;

+

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

+

+/**

+ * Represents an {@link Attribute} and its ordering.

+ *

+ */

+public final class SortAttribute {

+	private Attribute attribute;

+	private Order order;

+

+	/**

+	 * Constructs an OrderAttribute with given attribute and ascending ordering

+	 * 

+	 * @param attribute

+	 */

+	public SortAttribute(Attribute attribute) {

+		this(attribute, Order.ASCENDING);

+	}

+

+	/**

+	 * Constructs an OrderAttribute with given attribute and ordering

+	 * 

+	 * @param attribute

+	 * @param ascending

+	 */

+	public SortAttribute(Attribute attribute, Order ascending) {

+		this.attribute = attribute;

+		this.order = ascending;

+	}

+

+	/**

+	 * @return the attribute

+	 */

+	public Attribute getAttribute() {

+		return attribute;

+	}

+

+	/**

+	 * @return the order

+	 */

+	public Order getOrder() {

+		return order;

+	}

+}
\ No newline at end of file
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/ValuePrecision.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/ValuePrecision.java
new file mode 100644
index 0000000..4b08045
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/ValuePrecision.java
@@ -0,0 +1,170 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.entity;
+
+import java.time.LocalDateTime;
+import java.time.temporal.TemporalAdjusters;
+
+import org.eclipse.mdm.api.base.adapter.Attribute;
+import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.base.model.ValueType;
+import org.eclipse.mdm.api.base.query.ComparisonOperator;
+import org.eclipse.mdm.api.base.query.Condition;
+import org.eclipse.mdm.api.base.search.ContextState;
+
+public enum ValuePrecision {
+
+	EXACT {
+		@Override
+		public void applyOn(Value value) {
+			// Intentionally empty
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return ComparisonOperator.EQUAL.create(contextState, attribute, value);
+		}
+	},
+	YEAR {
+		@Override
+		public void applyOn(Value value) {
+			ValuePrecision.applyDate(value, YEAR);
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return getDateCondition(contextState, attribute, value, YEAR);
+		}
+	},
+	MONTH {
+		@Override
+		public void applyOn(Value value) {
+			ValuePrecision.applyDate(value, MONTH);
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return getDateCondition(contextState, attribute, value, MONTH);
+		}
+	},
+	DAY {
+		@Override
+		public void applyOn(Value value) {
+			ValuePrecision.applyDate(value, DAY);
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return getDateCondition(contextState, attribute, value, DAY);
+		}
+	},
+	HOUR {
+		@Override
+		public void applyOn(Value value) {
+			ValuePrecision.applyDate(value, HOUR);
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return getDateCondition(contextState, attribute, value, HOUR);
+		}
+	},
+	MINUTE {
+		@Override
+		public void applyOn(Value value) {
+			ValuePrecision.applyDate(value, MINUTE);
+		}
+
+		@Override
+		public Condition getCondition(ContextState contextState, Attribute attribute, Object value) {
+			return getDateCondition(contextState, attribute, value, MINUTE);
+		}
+	};
+
+	/**
+	 * Applies this {@link ValuePrecision} to given {@link Value}s value, if
+	 * acceptable for related {@link ValueType}.
+	 * 
+	 * @param value the value
+	 */
+	abstract public void applyOn(Value value);
+
+	abstract public Condition getCondition(ContextState contextState, Attribute attribute, Object value);
+
+	/**
+	 * Applies this {@link ValuePrecision} to given {@link Value}s value, for
+	 * {@link ValueType.DATE}.
+	 *
+	 * @param value the value
+	 */
+	private static void applyDate(Value value, ValuePrecision precision) {
+		if (value.getValueType() == ValueType.DATE) {
+			LocalDateTime dateTime = value.extract();
+			if (dateTime != null) {
+				switch (precision) {
+				case YEAR:
+					dateTime = dateTime.with(TemporalAdjusters.firstDayOfYear());
+					// no break intentionally;
+				case MONTH:
+					dateTime = dateTime.with(TemporalAdjusters.firstDayOfMonth());
+					// no break intentionally;
+				case DAY:
+					dateTime = dateTime.withHour(0);
+					// no break intentionally;
+				case HOUR:
+					dateTime = dateTime.withMinute(0);
+					// no break intentionally;
+				case MINUTE:
+					dateTime = dateTime.withSecond(0);
+					break;
+				default:
+					// Intentionally empty.
+				}
+				value.set(dateTime);
+			}
+		}
+	}
+
+	private static Condition getDateCondition(ContextState contextState, Attribute attribute, Object value,
+			ValuePrecision precision) {
+		if (attribute.getValueType() == ValueType.DATE) {
+			LocalDateTime start = (LocalDateTime) value;
+			LocalDateTime end;
+			switch (precision) {
+			case YEAR:
+				end = start.plusYears(1);
+				break;
+			case MONTH:
+				end = start.plusMonths(1);
+				break;
+			case DAY:
+				end = start.plusDays(1);
+				break;
+			case HOUR:
+				end = start.plusHours(1);
+				break;
+			case MINUTE:
+				end = start.plusMinutes(1);
+				break;
+			default:
+				end = start.plusNanos(1);
+			}
+			return ComparisonOperator.BETWEEN.create(contextState, attribute,
+					new LocalDateTime[] { start, end.minusNanos(1) });
+		} else {
+			return ComparisonOperator.EQUAL.create(contextState, attribute, value);
+		}
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/ExpressionLanguageMethodProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/ExpressionLanguageMethodProvider.java
new file mode 100644
index 0000000..9756173
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/ExpressionLanguageMethodProvider.java
@@ -0,0 +1,32 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.nodeprovider.utils;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * All functions in the {@link ExpressionLanguageMethodProvider} are reflected
+ * in the 'fn' namespace in the expression language context. Therefore, they
+ * have to be static.
+ *
+ */
+public class ExpressionLanguageMethodProvider {
+
+	public static String formatDate(LocalDateTime input, String format) {
+		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
+		return input.format(formatter);
+	}
+}
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
new file mode 100644
index 0000000..c76ccaf
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
@@ -0,0 +1,304 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.utils;

+

+import java.io.IOException;

+import java.nio.charset.StandardCharsets;

+import java.util.Arrays;

+import java.util.Base64;

+import java.util.List;

+import java.util.Map;

+import java.util.stream.Collectors;

+

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

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

+import org.eclipse.mdm.api.base.adapter.EntityType;

+import org.eclipse.mdm.api.base.adapter.ModelManager;

+import org.eclipse.mdm.api.base.query.Filter;

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

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

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

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

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

+import org.eclipse.mdm.nodeprovider.control.MDMExpressionLanguageService;

+import org.eclipse.mdm.nodeprovider.control.NodeProviderException;

+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.dto.NodeLevelDTO;

+import org.eclipse.mdm.nodeprovider.utils.dto.NodeProviderRootDTO;

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

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

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+import com.fasterxml.jackson.databind.JsonNode;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.fasterxml.jackson.databind.SerializationFeature;

+import com.google.protobuf.InvalidProtocolBufferException;

+

+/**

+ * Utility class for converting and (de-)serializing {@link Node},

+ * {@link NodeLevel} and {@link NodeProviderRoot}. Deserializing is a two step

+ * process: First we deserialize the JSON string into a DTO. Secondly we convert

+ * the DTO checking its {@link EntityType} and {@link Attribute}s against the

+ * actual {@link ApplicationContext}.

+ * 

+ *

+ */

+public class SerializationUtil {

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

+

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

+		String nodeId = id == null ? "" : id;

+		String nodeLabel = label == null ? "" : label;

+		Node node = Node.newBuilder().setSource(source).setType(type).setId(nodeId)

+				.setIdAttribute(idAttribute).setFilter(filter).setLabel(nodeLabel).build();

+

+		byte[] bytes = node.toByteArray();

+		return node.toBuilder().setSerial(Base64.getUrlEncoder().encodeToString(bytes)).build();

+	}

+

+	/**

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

+	 * 

+	 * @param node

+	 * @return base64 encoded protobuf Node

+	 */

+	public static String serializeNode(Node node) {

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

+	}

+

+	/**

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

+	 * 

+	 * @param base64Protobuf

+	 * @return {@link Node}

+	 */

+	public static Node deserializeNode(String base64Protobuf) {

+		try {

+			return Mdm.Node.parseFrom(Base64.getUrlDecoder().decode(base64Protobuf.getBytes(StandardCharsets.UTF_8)));

+		} catch (InvalidProtocolBufferException e) {

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

+		}

+	}

+

+	/**

+	 * Serialize a {@link NodeLevel} and convert it to a {@link NodeLevelDTO} JSON

+	 * string.

+	 * 

+	 * @return NodeLevel

+	 */

+	public static String serializeNodeLevel(NodeLevel nl) {

+		try {

+			return mapper.writeValueAsString(SerializationUtil.convert(nl));

+		} catch (IOException e) {

+			throw new NodeProviderException("Cannot serialize NodeLevel: " + nl, e);

+		}

+	}

+

+	/**

+	 * Deserialize a {@link NodeLevelDTO} given as JSON string and convert it to a

+	 * {@link NodeLevel}

+	 * 

+	 * @param context

+	 * @param json

+	 * @return NodeLevel

+	 */

+	public static NodeLevel deserializeNodeLevel(ApplicationContext context, String json) {

+		try {

+			NodeLevelDTO readNld = mapper.readValue(json, NodeLevelDTO.class);

+			return SerializationUtil.convert(context, readNld);

+		} catch (IOException e) {

+			throw new NodeProviderException("Cannot deserialize NodeLevel: " + json, e);

+		}

+	}

+

+	/**

+	 * Deserialize a {@link NodeProviderRootDTO} given as JSON string and convert it

+	 * to a {@link NodeProviderRoot}.

+	 * 

+	 * @param connectorService

+	 * @param json

+	 * @return

+	 */

+	public static NodeProviderRoot deserializeNodeProviderRoot(ConnectorService connectorService, String json) {

+		try {

+			NodeProviderRootDTO dto = mapper.readValue(json, NodeProviderRootDTO.class);

+

+			NodeProviderRoot npr = new NodeProviderRoot();

+			npr.setId(dto.getId());

+			npr.setName(dto.getName());

+			if (dto.getContexts().containsKey(NodeProviderRoot.WILDCARD)) {

+				NodeLevelDTO nlDTO = mapper.treeToValue(dto.getContexts().get(NodeProviderRoot.WILDCARD),

+						NodeLevelDTO.class);

+

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

+					String sourceName = context.getEntityManager()

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

+							.getSourceName();

+					if (dto.getContexts().containsKey(sourceName)) {

+						continue;

+					}

+

+					try {

+						npr.getContexts().put(sourceName, convert(context, nlDTO));

+					} catch (Exception e) {

+						LOG.warn("Cannot use node provider definition {} for datasource {}", NodeProviderRoot.WILDCARD,

+								sourceName);

+					}

+				}

+			}

+

+			for (Map.Entry<String, JsonNode> e : dto.getContexts().entrySet()) {

+				if (NodeProviderRoot.WILDCARD.equals(e.getKey())) {

+					continue;

+				}

+				ApplicationContext context = connectorService.getContextByName(e.getKey());

+

+				NodeLevelDTO nlDTO = mapper.treeToValue(e.getValue(), NodeLevelDTO.class);

+

+				try {

+					npr.getContexts().put(e.getKey(), convert(context, nlDTO));

+				} catch (Exception ex) {

+					LOG.warn("Cannot use node provider definition {} for datasource {}", e.getKey(), e.getKey());

+				}

+			}

+			return npr;

+		} catch (IOException e) {

+			throw new NodeProviderException("Cannot deserialize NodeProviderRoot: " + json, e);

+		}

+	}

+

+	/**

+	 * Serialize a {@link NodeProviderRoot} and convert it to a

+	 * {@link NodeProviderRootDTO} JSON string.

+	 * 

+	 * @param npr

+	 * @return

+	 */

+	public static String serializeNodeProviderRoot(NodeProviderRoot npr) {

+		try {

+			NodeProviderRootDTO dto = new NodeProviderRootDTO();

+			dto.setId(npr.getId());

+			dto.setName(npr.getName());

+

+			for (Map.Entry<String, NodeLevel> e : npr.getContexts().entrySet()) {

+				dto.getContexts().put(e.getKey(), mapper.valueToTree(convert(e.getValue())));

+			}

+			return mapper.writeValueAsString(dto);

+		} catch (IOException e) {

+			throw new NodeProviderException("Cannot serialize NodeProviderRoot: " + npr, e);

+		}

+	}

+

+	/**

+	 * Converts a {@link NodeLevel} into a {@link NodeLevelDTO}.

+	 * 

+	 * @param nl a {@link NodeLevel}

+	 * @return the {@link NodeLevelDTO}

+	 */

+	public static NodeLevelDTO convert(NodeLevel nl) {

+		NodeLevelDTO nld = new NodeLevelDTO();

+		nld.setType(nl.getEntityType().getName());

+		nld.setFilterAttributes(Arrays.asList(getFilterAttribute(nl)));

+		nld.setLabelAttributes(Arrays.asList(getLabelAttribute(nl)));

+		if (nl.getLabelExpression() != null) {

+			nld.setLabelExpression(nl.getLabelExpression().getExpressionString());

+		}

+		nld.setContextState(nl.getContextState());

+		nld.setVirtual(nl.isVirtual());

+		nld.setOrderAttributes(nl.getOrderAttributes().stream()

+				.collect(Collectors.toMap(o -> o.getAttribute().getName(), o -> o.getOrder())));

+		if (nl.getChild() != null) {

+			nld.setChild(convert(nl.getChild()));

+		}

+		nld.setValuePrecision(nl.getValuePrecision());

+		return nld;

+	}

+

+	/**

+	 * Converts a {@link NodeLevelDTO} into a {@link NodeLevel} using the given

+	 * {@link ApplicationContext}.

+	 * 

+	 * @param context application context

+	 * @param nld     {@link NodeLevelDTO}

+	 * @return the {@link NodeLevel}

+	 */

+	public static NodeLevel convert(ApplicationContext context, NodeLevelDTO nld) {

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

+		EntityType e = mm.getEntityType(ServiceUtils.invertMapping(nld.getType()));

+

+		List<Attribute> filterAttributes = nld.getFilterAttributes().stream().map(e::getAttribute)

+				.collect(Collectors.toList());

+

+		List<Attribute> labelAttributes = nld.getLabelAttributes().stream().map(e::getAttribute)

+				.collect(Collectors.toList());

+

+		NodeLevel nl = new NodeLevel(e, filterAttributes, labelAttributes);

+

+		nl.setContextState(nld.getContextState());

+		nl.setVirtual(nld.isVirtual());

+		nl.getOrderAttributes().addAll(nld.getOrderAttributes().entrySet().stream()

+				.map(x -> new SortAttribute(e.getAttribute(x.getKey()), x.getValue())).collect(Collectors.toList()));

+		MDMExpressionLanguageService inst = new MDMExpressionLanguageService();

+		nl.setLabelExpression(inst.parseValueExpression(nld.getLabelExpression()));

+		nl.setValuePrecision(nld.getValuePrecision());

+		if (nld.getChild() != null) {

+			nl.setChild(convert(context, nld.getChild()));

+		}

+		return nl;

+	}

+

+	private static String getFilterAttribute(NodeLevel nodeLevel) {

+		return nodeLevel.getFilterAttributes().stream().map(Attribute::getName).findFirst()

+				.orElseThrow(() -> new IllegalStateException("woops?!"));

+	}

+

+	private static String getLabelAttribute(NodeLevel nodeLevel) {

+		return nodeLevel.getLabelAttributes().stream().map(Attribute::getName).findFirst()

+				.orElseThrow(() -> new IllegalStateException("woops?!"));

+	}

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeLevelDTO.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeLevelDTO.java
new file mode 100644
index 0000000..498bdda
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeLevelDTO.java
@@ -0,0 +1,176 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.utils.dto;

+

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

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

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

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

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

+

+import com.fasterxml.jackson.annotation.JsonInclude;

+import com.fasterxml.jackson.annotation.JsonInclude.Include;

+

+/**

+ * DTO for {@link NodeLevel} used for serialization.

+ *

+ */

+@JsonInclude(value = Include.NON_EMPTY)

+public class NodeLevelDTO {

+	private String type;

+

+	private List<String> filterAttributes;

+	private List<String> labelAttributes;

+	private String labelExpression;

+	private ValuePrecision valuePrecision;

+	private Map<String, Order> orderAttributes = new HashMap<>();

+

+	private ContextState contextState = null;

+

+	private boolean isVirtual;

+

+	private NodeLevelDTO child;

+

+	/**

+	 * @return the type

+	 */

+	public String getType() {

+		return type;

+	}

+

+	/**

+	 * @param type the type to set

+	 */

+	public void setType(String type) {

+		this.type = type;

+	}

+

+	/**

+	 * @return the idAttribute

+	 */

+	public List<String> getFilterAttributes() {

+		return filterAttributes;

+	}

+

+	/**

+	 * @param filterAttributes the idAttribute to set

+	 */

+	public void setFilterAttributes(List<String> filterAttributes) {

+		this.filterAttributes = filterAttributes;

+	}

+

+	/**

+	 * @return the labelAttribute

+	 */

+	public List<String> getLabelAttributes() {

+		return labelAttributes;

+	}

+

+	/**

+	 * @param labelAttributes the labelAttribute to set

+	 */

+	public void setLabelAttributes(List<String> labelAttributes) {

+		this.labelAttributes = labelAttributes;

+	}

+

+	/**

+	 * @return the orderAttributes

+	 */

+	public Map<String, Order> getOrderAttributes() {

+		return orderAttributes;

+	}

+

+	/**

+	 * @param orderAttributes the orderAttributes to set

+	 */

+	public void setOrderAttributes(Map<String, Order> orderAttributes) {

+		this.orderAttributes = orderAttributes;

+	}

+

+	/**

+	 * @return the contextState

+	 */

+	public ContextState getContextState() {

+		return contextState;

+	}

+

+	/**

+	 * @param contextState the contextState to set

+	 */

+	public void setContextState(ContextState contextState) {

+		this.contextState = contextState;

+	}

+

+	/**

+	 * @return the isVirtual

+	 */

+	public boolean isVirtual() {

+		return isVirtual;

+	}

+

+	/**

+	 * @param isVirtual the isVirtual to set

+	 */

+	public void setVirtual(boolean isVirtual) {

+		this.isVirtual = isVirtual;

+	}

+

+	/**

+	 * @return the child

+	 */

+	public NodeLevelDTO getChild() {

+		return child;

+	}

+

+	/**

+	 * @param child the child to set

+	 */

+	public void setChild(NodeLevelDTO child) {

+		this.child = child;

+	}

+

+	/**

+	 * 

+	 * @return

+	 */

+	public String getLabelExpression() {

+		return labelExpression;

+	}

+

+	/**

+	 * 

+	 * @param labelExpression

+	 */

+	public void setLabelExpression(String labelExpression) {

+		this.labelExpression = labelExpression;

+	}

+

+	/**

+	 * @return the valuePrecision

+	 */

+	public ValuePrecision getValuePrecision() {

+		return valuePrecision;

+	}

+

+	/**

+	 * @param valuePrecision the valuePrecision to set

+	 */

+	public void setValuePrecision(ValuePrecision valuePrecision) {

+		this.valuePrecision = valuePrecision;

+	}

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeProviderRootDTO.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeProviderRootDTO.java
new file mode 100644
index 0000000..1d23d2b
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/dto/NodeProviderRootDTO.java
@@ -0,0 +1,76 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.utils.dto;

+

+import java.util.HashMap;

+import java.util.Map;

+

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

+

+import com.fasterxml.jackson.databind.JsonNode;

+

+/**

+ * DTO for {@link NodeProviderRoot} used for serialization.

+ *

+ */

+public class NodeProviderRootDTO {

+

+	private String id;

+	private String name;

+	private Map<String, JsonNode> contexts = new HashMap<>();

+

+	/**

+	 * @return the id

+	 */

+	public String getId() {

+		return id;

+	}

+

+	/**

+	 * @param id the id to set

+	 */

+	public void setId(String id) {

+		this.id = id;

+	}

+

+	/**

+	 * @return the name

+	 */

+	public String getName() {

+		return name;

+	}

+

+	/**

+	 * @param name the name to set

+	 */

+	public void setName(String name) {

+		this.name = name;

+	}

+

+	/**

+	 * @return the contexts

+	 */

+	public Map<String, JsonNode> getContexts() {

+		return contexts;

+	}

+

+	/**

+	 * @param contexts the contexts to set

+	 */

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

+		this.contexts = contexts;

+	}

+

+}

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryResource.java
index da7025c..e9f5640 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryResource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryResource.java
@@ -51,7 +51,7 @@
 	@Produces(MediaType.APPLICATION_JSON)
 	@Consumes(MediaType.APPLICATION_JSON)
 	public QueryResult query(QueryRequest request) {
-		return new QueryResult(queryService.queryRows(request));
+		return queryService.queryRows(request);
 	}
 
 	@POST
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryService.java
index f242b72..873d110 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/boundary/QueryService.java
@@ -39,6 +39,7 @@
 import org.eclipse.mdm.connector.boundary.ConnectorServiceException;
 import org.eclipse.mdm.property.GlobalProperty;
 import org.eclipse.mdm.query.entity.QueryRequest;
+import org.eclipse.mdm.query.entity.QueryResult;
 import org.eclipse.mdm.query.entity.Row;
 import org.eclipse.mdm.query.entity.SourceFilter;
 import org.eclipse.mdm.query.entity.SuggestionRequest;
@@ -58,20 +59,22 @@
 
 	@Inject
 	@GlobalProperty(value = "businessobjects.query.maxresultspersource")
-	private String maxResultsPerSource = "1001";
+	private String maxResultsPerSource;
 
 	@Inject
 	ConnectorService connectorService;
 
-	public List<Row> queryRows(QueryRequest request) {
-		List<Row> rows = new ArrayList<>();
+	public QueryResult queryRows(QueryRequest request) {
+		QueryResult result =new QueryResult(new ArrayList<>(), 0);
 
 		for (SourceFilter filter : request.getFilters()) {
 			try {
 				ApplicationContext context = this.connectorService.getContextByName(filter.getSourceName());
 
-				rows.addAll(queryRowsForSource(context, request.getResultType(), request.getColumns(),
-						filter.getFilter(), filter.getSearchString()));
+				QueryResult inner = queryRowsForSource(context, request.getResultType(), request.getColumns(),
+						request.getResultOffset(), request.getResultLimit(), filter.getFilter(), filter.getSearchString());
+				result.getRows().addAll(inner.getRows());
+				result.setTotalRecords(result.getTotalRecords() + inner.getTotalRecords());
 			} catch (ConnectorServiceException e) {
 				LOG.warn("Could not retrieve EntityManager for environment '" + filter.getSourceName() + "'!", e);
 			} catch (Exception e) {
@@ -79,8 +82,7 @@
 						+ e.getMessage(), e);
 			}
 		}
-
-		return rows;
+		return result;
 	}
 
 	public List<String> getSuggestions(SuggestionRequest suggestionRequest) {
@@ -112,8 +114,8 @@
 		return suggestions;
 	}
 
-	List<Row> queryRowsForSource(ApplicationContext context, String resultEntity, List<String> columns,
-			String filterString, String searchString) throws DataAccessException {
+	private QueryResult queryRowsForSource(ApplicationContext context, String resultEntity, List<String> columns,
+			int resultOffset, Integer resultLimit, String filterString, String searchString) throws DataAccessException {
 
 		ModelManager modelManager = context.getModelManager()
 				.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));
@@ -130,9 +132,30 @@
 		Filter filter = FilterParser.parseFilterString(searchableTypes, filterString);
 
 		List<Result> result = searchService.fetchResults(resultType, attributes, filter, searchString);
-
-		return Util.convertResultList(result.subList(0, Math.min(result.size(), getMaxResultsPerSource())), resultType,
+		List<Row> rows = Util.convertResultList(extractRequestedSubList(resultOffset, resultLimit, result), resultType,
 				modelManager.getEntityType(resultType));
+		return new QueryResult(rows, result.size());
+	}
+
+	/**
+	 * Extracts requested results from all results.
+	 * 
+	 * @param resultOffset	if positive or zero: offset from start, if negative: offset from end
+	 * @param resultLimit	Specifies maximum results, if set to zero no limit is set. If null the value from maxResultsPerSource property is used instead.
+	 * @param results		the original list
+	 * @return
+	 */
+	private List<Result> extractRequestedSubList(int resultOffset, Integer resultLimit, List<Result> results) {
+		int firstIndex = resultOffset < 0 ? results.size() + resultOffset : resultOffset;
+		int endIndex;
+		if (resultLimit == null) {
+			endIndex = Math.min(results.size(), getMaxResultsPerSource() + firstIndex);
+		} else if (resultLimit == 0) {
+			endIndex = results.size();
+		} else {
+			endIndex = Math.min(results.size(), resultLimit + firstIndex);
+		}
+		return results.subList(firstIndex, endIndex);
 	}
 
 	private Optional<Attribute> getAttribute(List<EntityType> searchableTypes, String c) {
@@ -166,6 +189,7 @@
 		throw new IllegalArgumentException("Invalid Entity '" + name + "'. Allowed values are: "
 				+ s.listSearchableTypes().stream().map(Class::getSimpleName).collect(Collectors.joining(", ")));
 	}
+	
 
 	private int getMaxResultsPerSource() {
 		try {
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryRequest.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryRequest.java
index 9b576b9..4e831fd 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryRequest.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryRequest.java
@@ -26,6 +26,8 @@
 	private List<SourceFilter> filters;
 	private List<String> columns;
 	private String resultType;
+	private int resultOffset;
+	private Integer resultLimit;
 
 	public List<SourceFilter> getFilters() {
 		return filters;
@@ -50,4 +52,20 @@
 	public void setResultType(String resultType) {
 		this.resultType = resultType;
 	}
+
+	public int getResultOffset() {
+		return resultOffset;
+	}
+
+	public void setResultOffset(Integer resultOffset) {
+		this.resultOffset = resultOffset == null ? 0 : resultOffset;
+	}
+
+	public Integer getResultLimit() {
+		return resultLimit;
+	}
+
+	public void setResultLimit(Integer resultLimit) {
+		this.resultLimit = (resultLimit != null && resultLimit < 0) ? 0 : resultLimit;
+	}
 }
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryResult.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryResult.java
index 6a52966..5c63d20 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryResult.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/query/entity/QueryResult.java
@@ -23,9 +23,11 @@
  */
 public class QueryResult {
 	private List<Row> rows;
+	private long totalRecords;
 
-	public QueryResult(List<Row> rows) {
+	public QueryResult(List<Row> rows, long totalRecords) {
 		this.rows = rows;
+		this.totalRecords = totalRecords;
 	}
 
 	public List<Row> getRows() {
@@ -35,4 +37,12 @@
 	public void setRows(List<Row> rows) {
 		this.rows = rows;
 	}
+
+	public long getTotalRecords() {
+		return totalRecords;
+	}
+
+	public void setTotalRecords(long totalRecords) {
+		this.totalRecords = totalRecords;
+	}
 }
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionResource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionResource.java
new file mode 100644
index 0000000..e3123a7
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionResource.java
@@ -0,0 +1,61 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+package org.eclipse.mdm.webclient.boundary;
+
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_FILTER;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
+
+import java.util.List;
+
+import javax.ejb.EJB;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.eclipse.mdm.api.base.query.Condition;
+import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
+import org.eclipse.mdm.webclient.entity.ConditionResponse;
+
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 
+ * @author jst
+ *
+ */
+@Tag(name = "Conditions")
+@Path("environments/{" + REQUESTPARAM_SOURCENAME + "}/conditions/")
+public class ConditionResource {
+
+	@EJB
+	private ConditionService service;
+	
+	@GET
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("filter/{" + REQUESTPARAM_FILTER + "}")
+	public Response convert(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_FILTER) String filterString) {
+		try {
+			List<Condition> conditions = service.toCondition(sourceName, filterString);
+			return ServiceUtils.toResponse(new ConditionResponse(conditions), Status.OK);
+		} catch (RuntimeException e) {
+			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
+		}
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionService.java
new file mode 100644
index 0000000..902937a
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/boundary/ConditionService.java
@@ -0,0 +1,56 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.webclient.boundary;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
+
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+
+import org.eclipse.mdm.api.base.ServiceNotProvidedException;
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.query.Condition;
+import org.eclipse.mdm.api.base.query.Filter;
+import org.eclipse.mdm.api.base.query.FilterItem;
+import org.eclipse.mdm.api.dflt.ApplicationContext;
+import org.eclipse.mdm.businessobjects.control.FilterParser;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+
+/**
+ * 
+ * @author jst
+ *
+ */
+@Stateless
+public class ConditionService {
+
+	@Inject
+	ConnectorService connectorService;
+
+	public List<Condition> toCondition(String sourceName, String filterString) {
+		ApplicationContext context = this.connectorService.getContextByName(sourceName);
+		ModelManager modelManager = context.getModelManager()
+				.orElseThrow(() -> new ServiceNotProvidedException(ModelManager.class));
+		Filter filter = FilterParser.parseFilterString(modelManager.listEntityTypes(), filterString);
+		return toCondition(filter);
+	}
+	
+	private List<Condition> toCondition(Filter filter) {
+		return filter.stream().map(FilterItem::getCondition).collect(toList());
+	}
+
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionDTO.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionDTO.java
new file mode 100644
index 0000000..11bc58c
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionDTO.java
@@ -0,0 +1,71 @@
+package org.eclipse.mdm.webclient.entity;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.eclipse.mdm.api.base.query.ComparisonOperator;
+import org.eclipse.mdm.api.base.query.Condition;
+
+public class ConditionDTO {
+
+	private final String type;
+	private final String attribute;
+	private final ComparisonOperator operator;
+	private final List<String> value;
+	private final String valueType;
+
+	public ConditionDTO(final Condition condition) {
+		this.attribute = condition.getAttribute().getName();
+		this.valueType = condition.getAttribute().getValueType().toString();
+		this.type = condition.getAttribute().getEntityType().getName();
+		if (condition.getValue().isValid()) {
+			if (condition.getValue().getValueType().isSequence()) {
+				this.value = Stream.of(condition.getValue().extract()).map(Object::toString).collect(toList());
+			} else {
+				this.value = new ArrayList<>();
+				this.value.add(condition.getValue().extract().toString());
+			}
+		} else {
+			this.value = new ArrayList<>();
+		}
+		this.operator = condition.getComparisonOperator();
+	}
+
+	/**
+	 * @return the type
+	 */
+	public final String getType() {
+		return type;
+	}
+
+	/**
+	 * @return the attribute
+	 */
+	public final String getAttribute() {
+		return attribute;
+	}
+
+	/**
+	 * @return the operator
+	 */
+	public final ComparisonOperator getOperator() {
+		return operator;
+	}
+
+	/**
+	 * @return the value
+	 */
+	public final Object getValue() {
+		return value;
+	}
+
+	/**
+	 * @return the valueType
+	 */
+	public final String getValueType() {
+		return valueType;
+	}
+}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionResponse.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionResponse.java
new file mode 100644
index 0000000..c498f26
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/webclient/entity/ConditionResponse.java
@@ -0,0 +1,33 @@
+package org.eclipse.mdm.webclient.entity;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.mdm.api.base.query.Condition;
+
+
+public class ConditionResponse {
+
+	/** transferable data content */
+	private final List<ConditionDTO> data;
+
+	/**
+	 * Constructor
+	 * 
+	 * @param searchDefinitions list of {@link Suggestion}s to transfer
+	 */
+	public ConditionResponse(final List<Condition> conditions) {
+		this.data = conditions.stream().map(ConditionDTO::new).collect(toList());
+	}
+
+	public ConditionResponse() {
+		this.data = new ArrayList<>();
+	}
+
+	public List<ConditionDTO> getData() {
+		return Collections.unmodifiableList(this.data);
+	}
+}
diff --git a/nucleus/businessobjects/src/main/proto/mdm.proto b/nucleus/businessobjects/src/main/proto/mdm.proto
index 073d57d..becc592 100644
--- a/nucleus/businessobjects/src/main/proto/mdm.proto
+++ b/nucleus/businessobjects/src/main/proto/mdm.proto
@@ -371,4 +371,19 @@
 	repeated MeasuredValues min = 1; // minimum values for each chunk

 	repeated MeasuredValues avg = 2; // average values for each chunk

 	repeated MeasuredValues max = 3; // maximum values for each chunk

+}

+

+message Node {

+	string source = 1;

+	string type = 2;

+	string id = 3;

+	string label = 4;

+	string serial = 5;

+	bool leaf = 6;

+	string idAttribute = 7;

+	string filter = 8;

+}

+

+message NodeProviderResponse {

+	repeated Node data = 1;

 }
\ No newline at end of file
diff --git a/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties b/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
index 3d78479..8455e99 100644
--- a/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
+++ b/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
@@ -53,6 +53,11 @@
 ChannelGroup.MimeType = MimeType
 ChannelGroup.SubMatrixNoRows = Rows
 
+SubMatrix = ChannelGroup
+SubMatrix.Name = Name
+SubMatrix.MimeType = MimeType
+SubMatrix.SubMatrixNoRows = Rows
+
 Channel = Channel
 Channel.Name = Name
 Channel.Description = MimeType
@@ -60,4 +65,13 @@
 Channel.Minimum = Minimum
 Channel.Maximum = Maximum
 Channel.Average = Average
-Channel.Deviation = Deviation
\ No newline at end of file
+Channel.Deviation = Deviation
+
+MeaQuantity = Channel
+MeaQuantity.Name = Name
+MeaQuantity.Description = MimeType
+MeaQuantity.DataType = DataType
+MeaQuantity.Minimum = Minimum
+MeaQuantity.Maximum = Maximum
+MeaQuantity.Average = Average
+MeaQuantity.Deviation = Deviation
\ No newline at end of file
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/FilterParserTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/FilterParserTest.java
index 5571f3d..fc65119 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/FilterParserTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/FilterParserTest.java
@@ -22,12 +22,14 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.eclipse.mdm.api.base.adapter.Attribute;
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.model.Value;
 import org.eclipse.mdm.api.base.model.ValueType;
 import org.eclipse.mdm.api.base.query.Filter;
 import org.eclipse.mdm.query.boundary.QueryServiceTest;
 import org.junit.Test;
+import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -60,4 +62,36 @@
 		assertThat(filter4).isEqualTo(Filter.and().name(test, "With'quote'"));
 	}
 
+	@Test
+	public void testToString() {
+		EntityType entityType = mockEntityType();
+
+		assertThat(FilterParser.toString(Filter.idOnly(entityType, "123"))).isEqualTo("Test.Id eq \"123\"");
+
+		assertThat(FilterParser
+				.toString(Filter.and().merge(Filter.idOnly(entityType, "123")).merge(Filter.idOnly(entityType, "125"))))
+						.isEqualTo("Test.Id eq \"123\" and Test.Id eq \"125\"");
+	}
+
+	private EntityType mockEntityType() {
+
+		Attribute attribute = Mockito.mock(Attribute.class);
+		Mockito.when(attribute.getName()).thenReturn("Id");
+		Mockito.when(attribute.getValueType()).thenReturn(ValueType.STRING);
+		Mockito.when(attribute.createValue(Mockito.any(), Mockito.anyString())).thenAnswer(new Answer<Value>() {
+			@Override
+			public Value answer(InvocationOnMock invocation) {
+				return ValueType.STRING.create("Id", "", true, invocation.getArgument(1));
+			}
+		});
+
+		EntityType entityType = Mockito.mock(EntityType.class);
+		Mockito.when(entityType.getIDAttribute()).thenReturn(attribute);
+		Mockito.when(entityType.getAttribute(Mockito.eq("Id"))).thenReturn(attribute);
+		Mockito.when(entityType.getName()).thenReturn("Test");
+		Mockito.when(attribute.getEntityType()).thenReturn(entityType);
+
+		return entityType;
+	}
+
 }
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/search/FilterParserTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/search/FilterParserTest.java
index d62fd53..9f585e4 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/search/FilterParserTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/control/search/FilterParserTest.java
@@ -15,6 +15,7 @@
 package org.eclipse.mdm.businessobjects.control.search;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.eclipse.mdm.api.base.query.ComparisonOperator.BETWEEN;
 import static org.eclipse.mdm.api.base.query.ComparisonOperator.CASE_INSENSITIVE_EQUAL;
 import static org.eclipse.mdm.api.base.query.ComparisonOperator.CASE_INSENSITIVE_GREATER_THAN;
@@ -49,6 +50,7 @@
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.query.ComparisonOperator;
 import org.eclipse.mdm.api.base.query.Filter;
+import org.eclipse.mdm.api.base.search.ContextState;
 import org.junit.Test;
 
 public class FilterParserTest {
@@ -345,6 +347,60 @@
 	}
 
 	@Test
+	public void testOrderedContext() throws Exception {
+		String filterString = "ORDERED.TestStep.Name eq 'test'";
+
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.ORDERED, map.get("TestStep").getAttribute("Name"), "test"));
+
+		Filter parsed = parseFilterString(entities, filterString);
+		assertThat(parsed).isEqualTo(expected);
+	}
+
+	@Test
+	public void testMeasuredContext() throws Exception {
+		String filterString = "Measured.TestStep.Name eq 'test'";
+
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, map.get("TestStep").getAttribute("Name"), "test"));
+
+		Filter parsed = parseFilterString(entities, filterString);
+		assertThat(parsed).isEqualTo(expected);
+	}
+
+	@Test
+	public void testValidMultipleContext() throws Exception {
+		String filterString = "Measured.TestStep.Name eq 'test' and Measured.TestStep.Name eq 'test2'";
+
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, map.get("TestStep").getAttribute("Name"), "test"))
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, map.get("TestStep").getAttribute("Name"), "test2"));
+
+		Filter parsed = parseFilterString(entities, filterString);
+		assertThat(parsed).isEqualTo(expected);
+	}
+
+	@Test
+	public void testValidMixedContext() throws Exception {
+		String filterString = "Measured.TestStep.Name eq 'test' and TestStep.Name eq 'test2'";
+
+		Filter expected = Filter.and()
+				.add(ComparisonOperator.EQUAL.create(ContextState.MEASURED, map.get("TestStep").getAttribute("Name"), "test"))
+				.add(ComparisonOperator.EQUAL.create(map.get("TestStep").getAttribute("Name"), "test2"));
+
+		Filter parsed = parseFilterString(entities, filterString);
+		assertThat(parsed).isEqualTo(expected);
+	}
+
+	@Test
+	public void testInvalidMultipleContext() throws Exception {
+		String filterString = "Measured.TestStep.Name eq 'test' and Ordered.TestStep.Name eq 'test2'";
+
+		assertThatThrownBy(() -> parseFilterString(entities, filterString)).isInstanceOf(IllegalStateException.class)
+				.hasMessageStartingWith("Filter with different contexts cannot be merged");
+	}
+
+	@Test
 	public void testSimpleWithBrackets() throws Exception {
 
 		String filterString = "(TestStep.Name eq 'test')";
diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ServiceUtilsTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ServiceUtilsTest.java
new file mode 100644
index 0000000..1485d35
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ServiceUtilsTest.java
@@ -0,0 +1,31 @@
+package org.eclipse.mdm.businessobjects.utils;

+

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

+

+import org.junit.Test;

+

+public class ServiceUtilsTest {

+

+	@Test

+	public void testWorkaroundForTypeMapping() {

+		assertThat(ServiceUtils.workaroundForTypeMapping("asdf")).isEqualTo("asdf");

+		assertThat(ServiceUtils.workaroundForTypeMapping("Project")).isEqualTo("Project");

+

+		assertThat(ServiceUtils.workaroundForTypeMapping("StructureLevel")).isEqualTo("Pool");

+		assertThat(ServiceUtils.workaroundForTypeMapping("MeaResult")).isEqualTo("Measurement");

+		assertThat(ServiceUtils.workaroundForTypeMapping("SubMatrix")).isEqualTo("ChannelGroup");

+		assertThat(ServiceUtils.workaroundForTypeMapping("MeaQuantity")).isEqualTo("Channel");

+	}

+

+	@Test

+	public void testInvertMapping() {

+		assertThat(ServiceUtils.invertMapping("asdf")).isEqualTo("asdf");

+		assertThat(ServiceUtils.invertMapping("Project")).isEqualTo("Project");

+

+		assertThat(ServiceUtils.invertMapping("Pool")).isEqualTo("StructureLevel");

+		assertThat(ServiceUtils.invertMapping("Measurement")).isEqualTo("MeaResult");

+		assertThat(ServiceUtils.invertMapping("ChannelGroup")).isEqualTo("SubMatrix");

+		assertThat(ServiceUtils.invertMapping("Channel")).isEqualTo("MeaQuantity");

+	}

+

+}

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
new file mode 100644
index 0000000..feeabb9
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderResourceTest.java
@@ -0,0 +1,143 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.boundary;

+

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

+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.MEDIATYPE_APPLICATION_PROTOBUF;

+import static org.mockito.Mockito.when;

+

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.List;

+

+import javax.ejb.EJB;

+import javax.inject.Singleton;

+import javax.ws.rs.client.Entity;

+import javax.ws.rs.core.Application;

+import javax.ws.rs.core.GenericType;

+import javax.ws.rs.core.MediaType;

+

+import org.eclipse.mdm.api.base.query.Filter;

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

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

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

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

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

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

+import org.glassfish.hk2.api.Injectee;

+import org.glassfish.hk2.api.InjectionResolver;

+import org.glassfish.hk2.api.ServiceHandle;

+import org.glassfish.hk2.api.TypeLiteral;

+import org.glassfish.hk2.utilities.binding.AbstractBinder;

+import org.glassfish.jersey.client.ClientConfig;

+import org.glassfish.jersey.server.ResourceConfig;

+import org.glassfish.jersey.test.JerseyTest;

+import org.junit.Before;

+import org.junit.Test;

+import org.mockito.Mockito;

+

+public class NodeProviderResourceTest extends JerseyTest {

+	private static NodeProviderService nodeProviderService = Mockito.mock(NodeProviderService.class);

+	private static NodeProvider nodeProvider = Mockito.mock(NodeProvider.class);

+	private Node rootNode;

+	private Node childNode;

+

+	public static class NodeProviderRepositoryInjectionResolver implements InjectionResolver<NodeProviderService> {

+		@Override

+		public Object resolve(Injectee injectee, ServiceHandle<?> root) {

+			return nodeProviderService;

+		}

+

+		@Override

+		public boolean isConstructorParameterIndicator() {

+			return true;

+		}

+

+		@Override

+		public boolean isMethodParameterIndicator() {

+			return true;

+		}

+	}

+

+	@Override

+	protected void configureClient(ClientConfig config) {

+		config.register(JsonMessageBodyProvider.class);

+		config.register(ProtobufMessageBodyProvider.class);

+		super.configureClient(config);

+	}

+

+	@Override

+	public Application configure() {

+		ResourceConfig config = new ResourceConfig();

+

+		config.register(new AbstractBinder() {

+			@Override

+			protected void configure() {

+				bind(NodeProviderRepositoryInjectionResolver.class).to(new TypeLiteral<InjectionResolver<EJB>>() {

+				}).in(Singleton.class);

+			}

+		});

+

+		config.register(NodeProviderResource.class);

+		config.register(JsonMessageBodyProvider.class);

+		config.register(ProtobufMessageBodyProvider.class);

+		return config;

+	}

+

+	@Before

+	public void init() {

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

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

+

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

+		childNode = SerializationUtil.createNode("MDM", "Pool", "2", "Id", Filter.and(), "Pool1");

+

+		when(nodeProvider.getRoots()).thenReturn(Arrays.asList(rootNode));

+		when(nodeProvider.getChildren(Mockito.any())).thenReturn(Arrays.asList(childNode));

+		when(nodeProvider.getTreePath(Mockito.any())).thenReturn(Arrays.asList(rootNode, childNode));

+	}

+

+	@Test

+	public void testGetNodeProviders() {

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

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

+		});

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

+	}

+

+	@Test

+	public void testGetRoots() {

+		NodeProviderResponse nodes = target("nodeprovider/default/").request(MEDIATYPE_APPLICATION_PROTOBUF)

+				.get(NodeProviderResponse.class);

+		assertThat(nodes.getDataList()).hasSize(1).contains(rootNode);

+	}

+

+	@Test

+	public void testGetChildren() {

+		NodeProviderResponse nodes = target("nodeprovider/default/" + rootNode.getSerial())

+				.request(MEDIATYPE_APPLICATION_PROTOBUF).get(NodeProviderResponse.class);

+		assertThat(nodes.getDataList()).contains(childNode);

+	}

+

+	@Test

+	public void testGetTreePath() {

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

+

+		NodeProviderResponse nodes = target("nodeprovider/default/").request(MEDIATYPE_APPLICATION_PROTOBUF)

+				.post(Entity.entity(node, MediaType.APPLICATION_JSON_TYPE), NodeProviderResponse.class);

+

+		assertThat(nodes.getDataList()).containsExactly(rootNode, childNode);

+	}

+}

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
new file mode 100644
index 0000000..dc1254f
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/boundary/NodeProviderServiceTest.java
@@ -0,0 +1,325 @@
+/********************************************************************************

+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation

+ *

+ * See the NOTICE file(s) distributed with this work for additional

+ * information regarding copyright ownership.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0 which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * SPDX-License-Identifier: EPL-2.0

+ *

+ ********************************************************************************/

+

+package org.eclipse.mdm.nodeprovider.boundary;

+

+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 java.nio.file.Files;

+import java.nio.file.Paths;

+import java.util.Arrays;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.stream.Collectors;

+

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

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

+import org.eclipse.mdm.api.base.adapter.EntityType;

+import org.eclipse.mdm.api.base.adapter.ModelManager;

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

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

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

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

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

+import org.eclipse.mdm.api.base.query.ComparisonOperator;

+import org.eclipse.mdm.api.base.query.Filter;

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

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

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

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

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

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

+import org.eclipse.mdm.nodeprovider.control.NodeProviderRepository;

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

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

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

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

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

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

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

+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 org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

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

+

+@Ignore

+//FIXME 15.07.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 NodeProviderServiceTest {

+

+	/*

+	 * 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 Logger LOGGER = LoggerFactory.getLogger(NodeProviderServiceTest.class);

+

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

+	private static ApplicationContext context2;

+

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

+

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

+		connectionParameters2.put(PARAM_NAMESERVICE, String.format(NAME_SERVICE, nameServiceHost, 2807));

+		connectionParameters2.put(PARAM_SERVICENAME, "CRASHDEMO" + ".ASAM-ODS");

+		connectionParameters2.put(PARAM_USER, USER);

+		connectionParameters2.put(PARAM_PASSWORD, PASSWORD);

+

+		context2 = new ODSContextFactory().connect(connectionParameters2);

+	}

+

+	@AfterClass

+	public static void tearDownAfterClass() throws ConnectionException {

+		if (context != null) {

+			context.close();

+		}

+

+		if (context2 != null) {

+			context2.close();

+		}

+	}

+

+//	@Test

+//	public void testJson() throws IOException {

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

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

+//

+//		GenericNodeProvider np = new GenericNodeProvider(connectorService, ContextState.MEASURED);

+//

+//		NodeProviderRoot npr = new NodeProviderRoot();

+//		npr.setId("generic_measured");

+//		npr.setName("Generic Measured");

+//		npr.getContexts().put("*", np.createStructure(context, ContextState.MEASURED));

+//

+//		String json = NodeLevelUtil.serialize(npr);

+//

+//		System.out.println(json);

+//

+//		NodeProviderRoot readNpr = NodeLevelUtil.deserialize(connectorService, json);

+//

+//		System.out.println(readNpr);

+//	}

+

+	@Test

+	public void testRoot() throws Exception {

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

+		EntityType project = mm.getEntityType("Project");

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

+		Attribute model = vehicle.getAttribute("model");

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

+

+		NodeProviderService npService = mockNodeProviderService();

+

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

+

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

+

+		assertThat(roots).containsExactly(

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

+

+		List<Node> level1 = nodeProvider

+				.getChildren(roots.stream().filter(n -> "MDM-NVH".equals(n.getLabel())).findFirst().get());

+

+		assertThat(level1).containsExactly(

+				SerializationUtil.createNode("NVHDEMO", "Project", "5", "Id", Filter.and().id(project, "5"), "PMV 2PV"),

+				SerializationUtil.createNode("NVHDEMO", "Project", "4", "Id", Filter.and().id(project, "4"),

+						"PMV Model P"),

+				SerializationUtil.createNode("NVHDEMO", "Project", "3", "Id", Filter.and().id(project, "3"),

+						"PMV Summit"));

+

+		List<Node> level2 = nodeProvider.getChildren(getNodeWithLabel(level1, "PMV Summit"));

+

+		assertThat(level2).containsExactly(SerializationUtil.createNode("NVHDEMO", "vehicle", "", "model",

+				Filter.and().id(project, "3").add(ComparisonOperator.EQUAL.create(model, "Model P")), "Model P"),

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

+						Filter.and().id(project, "3").add(ComparisonOperator.EQUAL.create(model, "Summit")), "Summit"));

+

+		List<Node> level3 = nodeProvider.getChildren(getNodeWithLabel(level2, "Model P"));

+

+		System.out.println(level3);

+

+		Filter parent = Filter.and().id(project, "3").add(ComparisonOperator.EQUAL.create(model, "Model P"));

+

+		assertThat(level3).isEmpty();

+

+		level3 = nodeProvider.getChildren(getNodeWithLabel(level2, "Summit"));

+

+		System.out.println(level3);

+

+		parent = Filter.and().id(project, "3").add(ComparisonOperator.EQUAL.create(model, "Summit"));

+

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

+				parent.copy().id(test, "4"), "PBN_UNECE_R51_20140213130501"));

+	}

+

+	@Test

+	public void testTests() throws Exception {

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

+		EntityType project = mm.getEntityType("Project");

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

+		Attribute model = vehicle.getAttribute("model");

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

+

+		NodeProviderService repo = mockNodeProviderService();

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

+

+		List<Node> envs = nodeProvider.getRoots();

+		List<Node> projects = nodeProvider.getChildren(getNodeWithLabel(envs, "MDM-NVH"));

+		List<Node> vehicleModels = nodeProvider.getChildren(getNodeWithLabel(projects, "PMV Summit"));

+		List<Node> tests = nodeProvider.getChildren(getNodeWithLabel(vehicleModels, "Summit"));

+

+		System.out.println(tests);

+

+		Filter parent = Filter.and().id(project, "3").add(ComparisonOperator.EQUAL.create(model, "Summit"));

+

+		assertThat(tests)

+				.containsExactly(SerializationUtil.createNode("NVHDEMO", "Test", "4", "Id", parent.copy().id(test, "4"),

+						"PBN_UNECE_R51_20140213130501")/*

+														 * , new Node("NVHDEMO", "Test", "2",

+														 * parent.copy().id(ContextState.ORDERED, test, "13"),

+														 * "PBN_UNECE_R51_20140314170907")

+														 */);

+	}

+

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

+		return nodes.stream().filter(p -> label.equalsIgnoreCase(p.getLabel())).findAny().get();

+	}

+

+	@Test

+	public void test() {

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

+		NodeProviderRoot npr = new NodeProviderRoot();

+		npr.setId("generic_measured");

+		npr.setName("Measured");

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

+

+		System.out.println(SerializationUtil.serializeNodeProviderRoot(npr));

+	}

+

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

+	}

+

+	private NodeLevel createStructureDefault(ApplicationContext context) {

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

+

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

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

+		NodeLevel pool = new NodeLevel(modelManager.getEntityType(Pool.class));

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

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

+

+		env.setChild(project);

+		project.setChild(pool);

+		pool.setChild(test);

+		test.setChild(testStep);

+		testStep.setChild(measurement);

+		measurement.setChild(channelGroup);

+		channelGroup.setChild(channel);

+

+		return env;

+	}

+

+	private static NodeProviderService mockNodeProviderService() throws Exception {

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

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

+

+		PreferenceMessage preferenceMessage = new PreferenceMessage();

+		preferenceMessage.setId(1L);

+		preferenceMessage.setKey("nodeprovider.generic");

+		preferenceMessage.setScope(Scope.SYSTEM);

+		preferenceMessage.setValue(Files.readAllLines(Paths.get("src/test/resources/nodeprovider_generic.json"))

+				.stream().collect(Collectors.joining()));

+

+		PreferenceService preferenceService = Mockito.mock(PreferenceService.class);

+		Mockito.when(preferenceService.getPreferences(Mockito.eq("system"), Mockito.eq("nodeprovider.")))

+				.thenReturn(Arrays.asList(preferenceMessage));

+

+		NodeProviderRepository nodeProviders = new NodeProviderRepository(connectorService, preferenceService);

+

+		NodeProviderService repo = new NodeProviderService(nodeProviders);

+		return repo;

+	}

+}

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProviderTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProviderTest.java
new file mode 100644
index 0000000..9b6168c
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProviderTest.java
@@ -0,0 +1,258 @@
+package org.eclipse.mdm.nodeprovider.control;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.mdm.api.base.ConnectionException;
+import org.eclipse.mdm.api.base.adapter.Attribute;
+import org.eclipse.mdm.api.base.adapter.EntityType;
+import org.eclipse.mdm.api.base.adapter.ModelManager;
+import org.eclipse.mdm.api.base.model.BaseEntity;
+import org.eclipse.mdm.api.base.model.Channel;
+import org.eclipse.mdm.api.base.model.ChannelGroup;
+import org.eclipse.mdm.api.base.model.Entity;
+import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.TestStep;
+import org.eclipse.mdm.api.base.model.Value;
+import org.eclipse.mdm.api.base.model.ValueType;
+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.api.odsadapter.query.ODSModelManager;
+import org.eclipse.mdm.businessobjects.control.NavigationActivity;
+import org.eclipse.mdm.connector.boundary.ConnectorService;
+import org.eclipse.mdm.protobuf.Mdm.Node;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class DefaultNodeProviderTest {
+
+	private static final String SOURCE = "TESTSOURCE";
+	private static final String ENVIRONMENT = "Environment";
+	private static final String PROJECT = "Project";
+	private static final String POOL = "Pool";
+	private static final String TEST = "Test";
+	private static final String TESTSTEP = "TestStep";
+	private static final String MEASUREMENT = "Measurement";
+	private static final String CHANNELGROUP = "ChannelGroup";
+	private static final String CHANNEL = "Channel";
+
+	private static final String ENVIRONMENT_ID = "1";
+	private static final String PROJECT_ID = "10";
+	private static final String POOL_ID = "100";
+	private static final String TEST_ID = "1000";
+	private static final String TESTSTEP_ID = "10000";
+	private static final String MEASUREMENT_ID = "100000";
+	private static final String CHANNELGROUP_ID = "1000000";
+	private static final String CHANNEL_ID = "10000000";
+
+	private static DefaultNodeProvider defaultNodeProvider;
+
+	@BeforeClass
+	public static void setup() throws ConnectionException {
+
+		BaseEntity environment = mockEntity(Environment.class, ENVIRONMENT, ENVIRONMENT_ID);
+		BaseEntity project = mockEntity(Project.class, PROJECT, PROJECT_ID);
+		BaseEntity pool1 = mockEntity(Pool.class, POOL, POOL_ID);
+		BaseEntity test1 = mockEntity(org.eclipse.mdm.api.base.model.Test.class, TEST, TEST_ID);
+		BaseEntity teststep1 = mockEntity(TestStep.class, TESTSTEP, TESTSTEP_ID);
+		BaseEntity measurement1 = mockEntity(Measurement.class, MEASUREMENT, MEASUREMENT_ID);
+		BaseEntity channelgroup = mockEntity(ChannelGroup.class, CHANNELGROUP, CHANNELGROUP_ID);
+		BaseEntity channel1 = mockEntity(Channel.class, CHANNEL, CHANNEL_ID);
+
+		NavigationActivity navigationActivity = mock(NavigationActivity.class);
+		doReturn(Collections.singletonList(environment)).when(navigationActivity).getEnvironments();
+		doReturn(Collections.singletonList(project)).when(navigationActivity).getProjects(eq(SOURCE));
+		doReturn(Collections.singletonList(pool1)).when(navigationActivity).getPools(eq(SOURCE), eq(PROJECT_ID));
+		doReturn(Collections.singletonList(test1)).when(navigationActivity).getTests(eq(SOURCE), eq(POOL_ID));
+		doReturn(Collections.singletonList(teststep1)).when(navigationActivity).getTestSteps(eq(SOURCE), eq(TEST_ID));
+		doReturn(Collections.singletonList(measurement1)).when(navigationActivity).getMeasurements(eq(SOURCE),
+				eq(TESTSTEP_ID));
+		doReturn(Collections.singletonList(channelgroup)).when(navigationActivity).getChannelGroups(eq(SOURCE),
+				eq(MEASUREMENT_ID));
+		doReturn(Collections.singletonList(channel1)).when(navigationActivity).getChannels(eq(SOURCE),
+				eq(CHANNELGROUP_ID));
+
+		defaultNodeProvider = new DefaultNodeProvider(navigationActivity, mockConnectorService());
+	}
+
+	@Test
+	public void testFromRoot() {
+		List<Node> environments = defaultNodeProvider.getRoots();
+
+		assertThat(environments).hasSize(1);
+		Node environment = environments.get(0);
+
+		assertThat(environment.getId()).isEqualTo(ENVIRONMENT_ID);
+		assertThat(environment.getSource()).isEqualTo(SOURCE);
+		assertThat(environment.getType()).isEqualTo(ENVIRONMENT);
+
+		List<Node> projects = defaultNodeProvider.getChildren(environment);
+
+		assertThat(projects).hasSize(1);
+		Node project = projects.get(0);
+
+		assertThat(project.getId()).isEqualTo(PROJECT_ID);
+		assertThat(project.getSource()).isEqualTo(SOURCE);
+		assertThat(project.getType()).isEqualTo(PROJECT);
+
+		List<Node> pools = defaultNodeProvider.getChildren(project);
+
+		assertThat(pools).hasSize(1);
+		Node pool = pools.get(0);
+
+		assertThat(pool.getId()).isEqualTo(POOL_ID);
+		assertThat(pool.getSource()).isEqualTo(SOURCE);
+		assertThat(pool.getType()).isEqualTo(POOL);
+
+		List<Node> tests = defaultNodeProvider.getChildren(pool);
+
+		assertThat(tests).hasSize(1);
+		Node test = tests.get(0);
+
+		assertThat(test.getId()).isEqualTo(TEST_ID);
+		assertThat(test.getSource()).isEqualTo(SOURCE);
+		assertThat(test.getType()).isEqualTo(TEST);
+
+		List<Node> teststeps = defaultNodeProvider.getChildren(test);
+
+		assertThat(teststeps).hasSize(1);
+		Node teststep = teststeps.get(0);
+
+		assertThat(teststep.getId()).isEqualTo(TESTSTEP_ID);
+		assertThat(teststep.getSource()).isEqualTo(SOURCE);
+		assertThat(teststep.getType()).isEqualTo(TESTSTEP);
+
+		List<Node> measurements = defaultNodeProvider.getChildren(teststep);
+
+		assertThat(measurements).hasSize(1);
+		Node measurement = measurements.get(0);
+
+		assertThat(measurement.getId()).isEqualTo(MEASUREMENT_ID);
+		assertThat(measurement.getSource()).isEqualTo(SOURCE);
+		assertThat(measurement.getType()).isEqualTo(MEASUREMENT);
+
+		List<Node> channelgroups = defaultNodeProvider.getChildren(measurement);
+
+		assertThat(channelgroups).hasSize(1);
+		Node channelgroup = channelgroups.get(0);
+
+		assertThat(channelgroup.getId()).isEqualTo(CHANNELGROUP_ID);
+		assertThat(channelgroup.getSource()).isEqualTo(SOURCE);
+		assertThat(channelgroup.getType()).isEqualTo(CHANNELGROUP);
+
+		List<Node> channels = defaultNodeProvider.getChildren(channelgroup);
+
+		assertThat(channels).hasSize(1);
+		Node channel = channels.get(0);
+
+		assertThat(channel.getId()).isEqualTo(CHANNEL_ID);
+		assertThat(channel.getSource()).isEqualTo(SOURCE);
+		assertThat(channel.getType()).isEqualTo(CHANNEL);
+	}
+
+	private static ConnectorService mockConnectorService() {
+		ConnectorService connectorService = mock(ConnectorService.class);
+
+		ApplicationContext mockedApplicationContext = mockApplicationContext();
+
+		Mockito.when(connectorService.getContexts()).thenReturn(Arrays.asList(mockedApplicationContext));
+		Mockito.when(connectorService.getContextByName(anyString())).thenReturn(mockedApplicationContext);
+
+		return connectorService;
+	}
+
+	private static ApplicationContext mockApplicationContext() {
+		ApplicationContext applicationContext = mock(ApplicationContext.class);
+		EntityManager entityManager = mockEntityManager();
+		ModelManager modelManager = mockModelManager();
+
+		Mockito.when(applicationContext.getEntityManager()).thenReturn(Optional.of(entityManager));
+		Mockito.when(applicationContext.getModelManager()).thenReturn(Optional.of(modelManager));
+
+		return applicationContext;
+	}
+
+	private static ModelManager mockModelManager() {
+		ODSModelManager modelManger = mock(ODSModelManager.class);
+
+		doAnswer(onMock -> {
+			EntityType entityType = mock(EntityType.class);
+			Attribute idAttribute = mock(Attribute.class);
+			doReturn("Id").when(idAttribute).getName();
+			Value value = mock(Value.class);
+			doReturn(ValueType.STRING).when(value).getValueType();
+
+			doReturn(entityType).when(idAttribute).getEntityType();
+			doReturn(entityType).when(idAttribute).getEntityType();
+			doReturn(idAttribute).when(entityType).getIDAttribute();
+
+			Entity e = onMock.getArgument(0);
+			doReturn("").when(e).getName();
+			String type = e.getTypeName();
+
+			doReturn(type).when(entityType).getName();
+			if (ENVIRONMENT.equals(type)) {
+				doReturn(ENVIRONMENT_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (PROJECT.equals(type)) {
+				doReturn(PROJECT_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (POOL.equals(type)) {
+				doReturn(POOL_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (TEST.equals(type)) {
+				doReturn(TEST_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (TESTSTEP.equals(type)) {
+				doReturn(TESTSTEP_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (MEASUREMENT.equals(type)) {
+				doReturn(MEASUREMENT_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (CHANNELGROUP.equals(type)) {
+				doReturn(CHANNELGROUP_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			} else if (CHANNEL.equals(type)) {
+				doReturn(CHANNEL_ID).when(value).extract();
+				doReturn(value).when(idAttribute).createValue(anyString(), any());
+			}
+			return entityType;
+		}).when(modelManger).getEntityType(any(Entity.class));
+
+//        doAnswer(onMock -> {
+//        	return onMock.
+//        }).when(modelManger.listEntityTypes());
+
+		return modelManger;
+	}
+
+	private static EntityManager mockEntityManager() {
+		EntityManager entityManager = mock(EntityManager.class);
+
+		// TODO
+		return entityManager;
+	}
+
+	private static <T extends BaseEntity> BaseEntity mockEntity(Class<T> mockedClass, String typeName, String id) {
+		BaseEntity entity = mock(mockedClass);
+
+		doReturn(SOURCE).when(entity).getSourceName();
+		doReturn(typeName).when(entity).getTypeName();
+		doReturn(id).when(entity).getID();
+		return entity;
+	}
+}
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..a737579
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
@@ -0,0 +1,360 @@
+package org.eclipse.mdm.nodeprovider.control;

+

+import static java.util.stream.Collectors.toList;

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

+		

+		MDMExpressionLanguageService elService = Mockito.mock(MDMExpressionLanguageService.class);

+

+		return new GenericNodeProvider(connectorService, npr, elService);

+	}

+

+	@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().addAll(env.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		project.getOrderAttributes().addAll(project.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

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

+		vehicleModel.setVirtual(true);

+		vehicleModel.setContextState(contextState);

+		vehicleModel.getOrderAttributes().addAll(vehicleModel.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		test.getOrderAttributes().addAll(test.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		testStep.getOrderAttributes().addAll(testStep.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		measurement.getOrderAttributes().addAll(measurement.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		channelGroup.getOrderAttributes().addAll(channelGroup.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

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

+		channel.getOrderAttributes().addAll(channel.getLabelAttributes().stream().map(SortAttribute::new).collect(toList()));

+

+		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..3aca80c
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRootTest.java
@@ -0,0 +1,177 @@
+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.Attribute;

+import org.eclipse.mdm.api.base.adapter.EntityType;

+import org.eclipse.mdm.api.base.adapter.ModelManager;

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

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

+import org.eclipse.mdm.api.base.model.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.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl1.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl2.getFilterAttributes()).extracting(Attribute::getName).containsExactly("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.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl4.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl5.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl6.getFilterAttributes()).extracting(Attribute::getName).containsExactly("Id");

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

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

+

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

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

+		assertThat(nl7.getFilterAttributes()).extracting(Attribute::getName).containsExactly("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/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/utils/NodeSerializationTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/utils/NodeSerializationTest.java
new file mode 100644
index 0000000..fcdce1e
--- /dev/null
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/utils/NodeSerializationTest.java
@@ -0,0 +1,52 @@
+package org.eclipse.mdm.nodeprovider.utils;

+

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

+

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

+import org.eclipse.mdm.api.base.adapter.EntityType;

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

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

+import org.eclipse.mdm.api.base.query.Filter;

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

+import org.junit.Test;

+import org.mockito.Mockito;

+import org.mockito.invocation.InvocationOnMock;

+import org.mockito.stubbing.Answer;

+

+public class NodeSerializationTest {

+	@Test

+	public void testSerializeDeserialize() {

+		EntityType entityType = mockEntityType();

+

+		Node node = SerializationUtil.createNode("MDMNVH", "Test", "123", "Id", Filter.idOnly(entityType, "123"),

+				"xyz");

+

+		String serial = SerializationUtil.serializeNode(node);

+		Node deserNode = SerializationUtil.deserializeNode(serial);

+

+		assertThat(deserNode).isEqualTo(node);

+		assertThat(deserNode).hasFieldOrPropertyWithValue("source", "MDMNVH")

+				.hasFieldOrPropertyWithValue("type", "Test").hasFieldOrPropertyWithValue("label", "xyz");

+	}

+

+	private EntityType mockEntityType() {

+

+		Attribute attribute = Mockito.mock(Attribute.class);

+		Mockito.when(attribute.getName()).thenReturn("Id");

+		Mockito.when(attribute.getValueType()).thenReturn(ValueType.STRING);

+		Mockito.when(attribute.createValue(Mockito.any(), Mockito.anyString())).thenAnswer(new Answer<Value>() {

+			@Override

+			public Value answer(InvocationOnMock invocation) {

+				return ValueType.STRING.create("Id", "", true, invocation.getArgument(1));

+			}

+		});

+

+		EntityType entityType = Mockito.mock(EntityType.class);

+		Mockito.when(entityType.getIDAttribute()).thenReturn(attribute);

+		Mockito.when(entityType.getAttribute(Mockito.eq("Id"))).thenReturn(attribute);

+		Mockito.when(entityType.getName()).thenReturn("Test");

+		Mockito.when(attribute.getEntityType()).thenReturn(entityType);

+

+		return entityType;

+	}

+}

diff --git a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/query/boundary/QueryServiceTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/query/boundary/QueryServiceTest.java
index 606a905..1f8ae6e 100644
--- a/nucleus/businessobjects/src/test/java/org/eclipse/mdm/query/boundary/QueryServiceTest.java
+++ b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/query/boundary/QueryServiceTest.java
@@ -74,7 +74,7 @@
 		expectedRow.addColumns(
 				Arrays.asList(new Column("Test", "Id", "id1", null), new Column("Test", "Name", "Test-Name", null)));
 
-		assertThat(queryService.queryRows(request)).contains(expectedRow);
+		assertThat(queryService.queryRows(request).getRows()).contains(expectedRow);
 	}
 
 	@org.junit.Test
@@ -103,7 +103,7 @@
 		expectedRow.addColumns(
 				Arrays.asList(new Column("Test", "Id", "id1", null), new Column("Test", "Name", "Test-Name", null)));
 
-		assertThat(queryService.queryRows(request)).contains(expectedRow);
+		assertThat(queryService.queryRows(request).getRows()).contains(expectedRow);
 	}
 
 	@org.junit.Test
@@ -137,7 +137,7 @@
 		expectedRow.addColumns(
 				Arrays.asList(new Column("Test", "Id", "id1", null), new Column("Test", "Name", "Test-Name", null)));
 
-		assertThat(queryService.queryRows(request)).contains(expectedRow);
+		assertThat(queryService.queryRows(request).getRows()).contains(expectedRow);
 	}
 
 	@org.junit.Test
@@ -204,7 +204,7 @@
 		expectedRowEnv2.addColumns(Arrays.asList(new Column("Test", "Id", "id1", null),
 				new Column("Test", "Name", "Test-Name", null), new Column("Pool", "Name", "Pool-Name", null)));
 
-		List<Row> list = queryService.queryRows(request);
+		List<Row> list = queryService.queryRows(request).getRows();
 
 		assertThat(list).extracting("source", "type", "id").containsOnly(new Tuple("env1", "Test", "id1"),
 				new Tuple("env2", "Test", "id1"));
diff --git a/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql b/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql
new file mode 100644
index 0000000..41bca31
--- /dev/null
+++ b/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql
@@ -0,0 +1,68 @@
+INSERT INTO openmdm.preference (keycol,"source",username,valuecol) VALUES 

+('nodeprovider.generic_measured',NULL,NULL,'{

+	"id" : "generic_measured",

+	"name" : "Generic Measured",

+	"contexts" : {

+	    "*" : {

+		    "type" : "Environment",

+		    "filterAttributes" : ["Id"],

+		    "labelAttributes" : ["Name"],

+		    "virtual" : false,

+		    "child" : {

+		        "type" : "Project",

+		        "filterAttributes" : ["Id"],

+		        "labelAttributes" : ["Name", "Id"],

+		        "virtual" : false,

+		        "child" : {        

+			        "type" : "vehicle",

+			        "filterAttributes" : ["model"],

+			        "labelAttributes" : ["model"],

+		            "labelExpression" : "Model: ${model}",

+			        "virtual" : true,

+			        "contextState": "MEASURED",

+			        "child" : {

+						"type" : "Measurement",

+						"filterAttributes" : ["DateCreated"],

+						"labelAttributes" : ["DateCreated"],

+						"labelExpression" : "${fn:formatDate(DateCreated, \"YYYY 'KW' ww\")}",

+						"valuePrecision" : "DAY",

+						"virtual" : true,

+						"contextState": "MEASURED",

+						"child" : {

+							"type" : "Test",

+							"filterAttributes" : ["Id"],

+							"labelAttributes" : ["Name"],

+							"virtual" : false,

+							"child" : {

+								"type" : "TestStep",

+								"filterAttributes" : ["Id"],

+								"labelAttributes" : ["Name"],

+								"virtual" : false,

+								"child" : {

+									"type" : "Measurement",

+									"filterAttributes" : ["Id"],

+									"labelAttributes" : ["Name"],

+									"virtual" : false,

+									"child" : {

+										"type" : "ChannelGroup",

+										"filterAttributes" : ["Id"],

+										"labelAttributes" : ["Name"],

+										"virtual" : false,

+										"child" : {

+											"type" : "Channel",

+											"filterAttributes" : ["Id"],

+											"labelAttributes" : ["Name"],

+											"virtual" : false

+										}

+									}

+								}

+							}

+						}

+					}

+		        }

+		    }

+	    }

+	}

+}

+')

+;

diff --git a/nucleus/businessobjects/src/test/resources/nodeprovider_generic.json b/nucleus/businessobjects/src/test/resources/nodeprovider_generic.json
new file mode 100644
index 0000000..894972c
--- /dev/null
+++ b/nucleus/businessobjects/src/test/resources/nodeprovider_generic.json
@@ -0,0 +1,65 @@
+{

+	"id" : "generic_measured",

+	"name" : "Generic Measured",

+	"contexts" : {

+	    "*" : {

+		    "type" : "Environment",

+		    "filterAttributes" : ["Id"],

+		    "labelAttributes" : ["Name"],

+		    "virtual" : false,

+		    "child" : {

+		        "type" : "Project",

+		        "filterAttributes" : ["Id"],

+		        "labelAttributes" : ["Name", "Id"],

+		        "virtual" : false,

+		        "child" : {        

+			        "type" : "vehicle",

+			        "filterAttributes" : ["model"],

+			        "labelAttributes" : ["model"],

+		            "labelExpression" : "Model: ${model}",

+			        "virtual" : true,

+			        "contextState": "MEASURED",

+			        "child" : {

+						"type" : "Measurement",

+						"filterAttributes" : ["DateCreated"],

+						"labelAttributes" : ["DateCreated"],

+						"labelExpression" : "${fn:formatDate(DateCreated, \"YYYY 'KW' ww\")}",

+						"valuePrecision" : "DAY",

+						"virtual" : true,

+						"contextState": "MEASURED",

+						"child" : {

+							"type" : "Test",

+							"filterAttributes" : ["Id"],

+							"labelAttributes" : ["Name"],

+							"virtual" : false,

+							"child" : {

+								"type" : "TestStep",

+								"filterAttributes" : ["Id"],

+								"labelAttributes" : ["Name"],

+								"virtual" : false,

+								"child" : {

+									"type" : "Measurement",

+									"filterAttributes" : ["Id"],

+									"labelAttributes" : ["Name"],

+									"virtual" : false,

+									"child" : {

+										"type" : "ChannelGroup",

+										"filterAttributes" : ["Id"],

+										"labelAttributes" : ["Name"],

+										"virtual" : false,

+										"child" : {

+											"type" : "Channel",

+											"filterAttributes" : ["Id"],

+											"labelAttributes" : ["Name"],

+											"virtual" : false

+										}

+									}

+								}

+							}

+						}

+					}

+		        }

+		    }

+	    }

+	}

+}
\ No newline at end of file
diff --git a/nucleus/connector/src/main/java/org/eclipse/mdm/connector/boundary/ConnectorService.java b/nucleus/connector/src/main/java/org/eclipse/mdm/connector/boundary/ConnectorService.java
index 5911a81..21802c8 100644
--- a/nucleus/connector/src/main/java/org/eclipse/mdm/connector/boundary/ConnectorService.java
+++ b/nucleus/connector/src/main/java/org/eclipse/mdm/connector/boundary/ConnectorService.java
@@ -134,6 +134,7 @@
 	 */
 	@PostConstruct
 	public void connect() {
+		System.out.println("Init ConnectorService");
 		LOG.info("connecting user with name '" + principal.getName() + "'");
 		serviceConfigurationActivity.readServiceConfigurations().stream().forEach(this::connectContexts);
 	}
diff --git a/nucleus/preferences/src/main/java/org/eclipse/mdm/preferences/controller/PreferenceService.java b/nucleus/preferences/src/main/java/org/eclipse/mdm/preferences/controller/PreferenceService.java
index 1e8337e..9be6cbe 100644
--- a/nucleus/preferences/src/main/java/org/eclipse/mdm/preferences/controller/PreferenceService.java
+++ b/nucleus/preferences/src/main/java/org/eclipse/mdm/preferences/controller/PreferenceService.java
@@ -42,7 +42,7 @@
  *
  */
 @Stateless
-@DeclareRoles("Admin")
+@DeclareRoles(value = { "Admin", "DescriptiveDataAuthor", "Guest" })
 public class PreferenceService {
 	private static final Logger LOG = LoggerFactory.getLogger(PreferenceService.class);
 
diff --git a/nucleus/webclient/src/main/webapp/package-lock.json b/nucleus/webclient/src/main/webapp/package-lock.json
index ecbf955..784b8ad 100644
--- a/nucleus/webclient/src/main/webapp/package-lock.json
+++ b/nucleus/webclient/src/main/webapp/package-lock.json
@@ -1343,6 +1343,7 @@
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
       "integrity": "sha1-SzXClE8GKov82mZBB2A1D+nd/CE=",
       "dev": true,
+      "optional": true,
       "requires": {
         "delegates": "^1.0.0",
         "readable-stream": "^2.0.6"
@@ -2607,7 +2608,8 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "constants-browserify": {
       "version": "1.0.0",
@@ -3026,7 +3028,8 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
       "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "depd": {
       "version": "1.1.2",
@@ -4137,24 +4140,29 @@
       "dependencies": {
         "abbrev": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+          "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
           "dev": true,
           "optional": true
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true,
-          "dev": true
+          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+          "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+          "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
           "dev": true,
           "optional": true
         },
         "are-we-there-yet": {
           "version": "1.1.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+          "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4164,13 +4172,15 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+          "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
           "dev": true,
           "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+          "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4180,37 +4190,43 @@
         },
         "chownr": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
+          "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==",
           "dev": true,
           "optional": true
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+          "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
           "dev": true,
           "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+          "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
           "dev": true,
           "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+          "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
           "dev": true,
           "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
           "dev": true,
           "optional": true
         },
         "debug": {
           "version": "2.6.9",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4219,25 +4235,29 @@
         },
         "deep-extend": {
           "version": "0.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+          "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
           "dev": true,
           "optional": true
         },
         "delegates": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+          "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
           "dev": true,
           "optional": true
         },
         "detect-libc": {
           "version": "1.0.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+          "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
           "dev": true,
           "optional": true
         },
         "fs-minipass": {
           "version": "1.2.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+          "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4246,13 +4266,15 @@
         },
         "fs.realpath": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+          "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
           "dev": true,
           "optional": true
         },
         "gauge": {
           "version": "2.7.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+          "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4268,7 +4290,8 @@
         },
         "glob": {
           "version": "7.1.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+          "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4282,13 +4305,15 @@
         },
         "has-unicode": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+          "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
           "dev": true,
           "optional": true
         },
         "iconv-lite": {
           "version": "0.4.24",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+          "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4297,7 +4322,8 @@
         },
         "ignore-walk": {
           "version": "3.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+          "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4306,7 +4332,8 @@
         },
         "inflight": {
           "version": "1.0.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+          "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4316,19 +4343,22 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
           "dev": true,
           "optional": true
         },
         "ini": {
           "version": "1.3.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+          "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
           "dev": true,
           "optional": true
         },
         "is-fullwidth-code-point": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+          "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4337,13 +4367,15 @@
         },
         "isarray": {
           "version": "1.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+          "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
           "dev": true,
           "optional": true
         },
         "minimatch": {
           "version": "3.0.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+          "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4352,13 +4384,15 @@
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
           "dev": true,
           "optional": true
         },
         "minipass": {
           "version": "2.3.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+          "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4368,7 +4402,8 @@
         },
         "minizlib": {
           "version": "1.2.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+          "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4377,7 +4412,8 @@
         },
         "mkdirp": {
           "version": "0.5.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4386,13 +4422,15 @@
         },
         "ms": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
           "dev": true,
           "optional": true
         },
         "needle": {
           "version": "2.2.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
+          "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4403,7 +4441,8 @@
         },
         "node-pre-gyp": {
           "version": "0.10.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
+          "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4421,7 +4460,8 @@
         },
         "nopt": {
           "version": "4.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4431,13 +4471,15 @@
         },
         "npm-bundled": {
           "version": "1.0.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
+          "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==",
           "dev": true,
           "optional": true
         },
         "npm-packlist": {
           "version": "1.2.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.2.0.tgz",
+          "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4447,7 +4489,8 @@
         },
         "npmlog": {
           "version": "4.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+          "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4459,19 +4502,22 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+          "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
           "dev": true,
           "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
           "dev": true,
           "optional": true
         },
         "once": {
           "version": "1.4.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+          "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4480,19 +4526,22 @@
         },
         "os-homedir": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+          "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
           "dev": true,
           "optional": true
         },
         "os-tmpdir": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+          "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
           "dev": true,
           "optional": true
         },
         "osenv": {
           "version": "0.1.5",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+          "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4502,19 +4551,22 @@
         },
         "path-is-absolute": {
           "version": "1.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+          "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
           "dev": true,
           "optional": true
         },
         "process-nextick-args": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+          "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
           "dev": true,
           "optional": true
         },
         "rc": {
           "version": "1.2.8",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+          "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4526,7 +4578,8 @@
           "dependencies": {
             "minimist": {
               "version": "1.2.0",
-              "bundled": true,
+              "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+              "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
               "dev": true,
               "optional": true
             }
@@ -4534,7 +4587,8 @@
         },
         "readable-stream": {
           "version": "2.3.6",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+          "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4549,7 +4603,8 @@
         },
         "rimraf": {
           "version": "2.6.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4558,42 +4613,50 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true,
-          "dev": true
+          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+          "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
           "dev": true,
           "optional": true
         },
         "sax": {
           "version": "1.2.4",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+          "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
           "dev": true,
           "optional": true
         },
         "semver": {
           "version": "5.6.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+          "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
           "dev": true,
           "optional": true
         },
         "set-blocking": {
           "version": "2.0.0",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+          "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
           "dev": true,
           "optional": true
         },
         "signal-exit": {
           "version": "3.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+          "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
           "dev": true,
           "optional": true
         },
         "string-width": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+          "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4604,7 +4667,8 @@
         },
         "string_decoder": {
           "version": "1.1.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4613,21 +4677,25 @@
         },
         "strip-ansi": {
           "version": "3.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+          "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
         },
         "strip-json-comments": {
           "version": "2.0.1",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+          "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
           "dev": true,
           "optional": true
         },
         "tar": {
           "version": "4.4.8",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
+          "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4642,13 +4710,15 @@
         },
         "util-deprecate": {
           "version": "1.0.2",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+          "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
           "dev": true,
           "optional": true
         },
         "wide-align": {
           "version": "1.1.3",
-          "bundled": true,
+          "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+          "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
           "dev": true,
           "optional": true,
           "requires": {
@@ -4657,13 +4727,17 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true,
-          "dev": true
+          "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+          "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true,
-          "dev": true
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -4672,6 +4746,7 @@
       "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
       "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
       "dev": true,
+      "optional": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "inherits": "~2.0.0",
@@ -4684,6 +4759,7 @@
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
       "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
       "dev": true,
+      "optional": true,
       "requires": {
         "aproba": "^1.0.3",
         "console-control-strings": "^1.0.0",
@@ -4721,7 +4797,8 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
       "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "get-stream": {
       "version": "3.0.0",
@@ -4946,7 +5023,8 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "has-value": {
       "version": "1.0.0",
@@ -5765,6 +5843,11 @@
       "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
       "dev": true
     },
+    "iso-639-1": {
+      "version": "2.1.8",
+      "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.8.tgz",
+      "integrity": "sha512-Ol0R5oepQmWTMrlzSx8giSQ4hQsh2SZI/fsAy+nYc5O+RsYeixy2tsGUYJNadqqoOcbanSbFTqK17PRKpuNCAw=="
+    },
     "isobject": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
@@ -6381,6 +6464,7 @@
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
       "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
       "dev": true,
+      "optional": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "parse-json": "^2.2.0",
@@ -6393,7 +6477,8 @@
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -6666,7 +6751,8 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
-      "dev": true
+      "dev": true,
+      "optional": true
     },
     "map-visit": {
       "version": "1.0.0",
@@ -7316,6 +7402,7 @@
       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
       "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=",
       "dev": true,
+      "optional": true,
       "requires": {
         "are-we-there-yet": "~1.1.2",
         "console-control-strings": "~1.1.0",
@@ -8540,6 +8627,7 @@
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
       "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
       "dev": true,
+      "optional": true,
       "requires": {
         "load-json-file": "^1.0.0",
         "normalize-package-data": "^2.3.2",
@@ -8551,6 +8639,7 @@
           "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
           "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
           "dev": true,
+          "optional": true,
           "requires": {
             "graceful-fs": "^4.1.2",
             "pify": "^2.0.0",
@@ -8561,7 +8650,8 @@
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
           "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },
@@ -8570,6 +8660,7 @@
       "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
       "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
       "dev": true,
+      "optional": true,
       "requires": {
         "find-up": "^1.0.0",
         "read-pkg": "^1.0.0"
@@ -8580,6 +8671,7 @@
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
           "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
           "dev": true,
+          "optional": true,
           "requires": {
             "path-exists": "^2.0.0",
             "pinkie-promise": "^2.0.0"
@@ -8590,6 +8682,7 @@
           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
           "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
           "dev": true,
+          "optional": true,
           "requires": {
             "pinkie-promise": "^2.0.0"
           }
@@ -11290,6 +11383,7 @@
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
       "integrity": "sha1-rgdOa9wMFKQx6ATmJFScYzsABFc=",
       "dev": true,
+      "optional": true,
       "requires": {
         "string-width": "^1.0.2 || 2"
       }
diff --git a/nucleus/webclient/src/main/webapp/package.json b/nucleus/webclient/src/main/webapp/package.json
index f7e8f3e..5c2e4ab 100644
--- a/nucleus/webclient/src/main/webapp/package.json
+++ b/nucleus/webclient/src/main/webapp/package.json
@@ -34,14 +34,15 @@
     "core-js": "2.6.0",
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
+    "iso-639-1": "2.1.8",
     "ngx-bootstrap": "3.1.2",
-    "primeflex": "^1.0.0",
+    "primeflex": "1.0.0",
     "primeicons": "1.0.0",
     "primeng": "7.0.1",
     "rxjs": "6.3.3",
     "rxjs-compat": "6.3.3",
     "split.js": "1.5.11",
-    "tslib": "^1.10.0",
+    "tslib": "1.10.0",
     "zone.js": "0.8.26"
   },
   "devDependencies": {
diff --git a/nucleus/webclient/src/main/webapp/src/app/app.component.html b/nucleus/webclient/src/main/webapp/src/app/app.component.html
index e650581..43663a4 100644
--- a/nucleus/webclient/src/main/webapp/src/app/app.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/app.component.html
@@ -12,7 +12,6 @@
  *
  ********************************************************************************-->
 
-
 <nav class="navbar navbar-dark bg-dark fixed-top navbar-expand py-0">
   <a class="navbar-brand" routerLink="/navigator" style="cursor:pointer;">openMDM5 Web</a>
   <div class="collapse navbar-collapse" id="bs-mdm-navbar">
@@ -20,8 +19,11 @@
         <li class="nav-item" *ngFor="let m of (links | authPipe | async)" [routerLinkActive]="['active']"><a class="nav-link" routerLink="{{m.path}}" style="cursor:pointer;"> {{m.name}}</a></li>
       </ul>
       <ul class="navbar-nav ml-md-auto">
-        <li class="nav-item"><span class="navbar-text" style="padding: 0 5px; vertical-align: middle;">{{ 'app.language' | translate }}</span> <p-dropdown [options]="languages" (onChange)="selectLanguage($event)" [(ngModel)]="selectedLanguage" [style]="{ 'margin-top': '2px' }"></p-dropdown></li>
-        <li class="nav-item"><a class="nav-link" [routerLink]="" (click)="showAboutDialog()" href="#">{{ 'app.about' | translate }}</a></li>
+        <li class="nav-item">
+          <span class="navbar-text" style="padding: 0 5px; vertical-align: middle;">{{ 'app.language' | translate }}</span>
+          <p-dropdown [options]="languages" (onChange)="onSelectLanguage($event)" [(ngModel)]="selectedLanguage" [style]="{ 'margin-top': '2px', 'min-width': '100px' }"></p-dropdown>
+        </li>
+        <li class="nav-item"><a class="nav-link" [routerLink]="" (click)="onShowAboutDialog()" href="#">{{ 'app.about' | translate }}</a></li>
         <li class="nav-item"><a class="nav-link" href="mdm/logout"><span class="fa fa-sign-in"></span> {{ 'app.logout' | translate }}<span *ngIf="user?.username" title="{{ 'app.roles' | translate }}: {{ rolesTooltip }}"> ({{ user?.username }})</span></a></li>
       </ul>
     </div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/app.component.ts b/nucleus/webclient/src/main/webapp/src/app/app.component.ts
index b39bae8..8d2cb5d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/app.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/app.component.ts
@@ -13,12 +13,14 @@
  ********************************************************************************/
 
 import { Component, OnInit } from '@angular/core';
-import {DialogModule} from 'primeng/primeng';
 import { TranslateService } from '@ngx-translate/core';
 
-import {SelectItem} from 'primeng/api';
+import { SelectItem } from 'primeng/api';
 import { User, Role } from './authentication/authentication.service';
 import { AuthenticationService } from './authentication/authentication.service';
+import { NodeService } from '@navigator/node.service';
+import ISO6391 from 'iso-639-1';
+import { MdmLocalizationService } from '@localization/mdm-localization.service';
 
 @Component({
   selector: 'app-root',
@@ -26,46 +28,53 @@
 })
 export class AppComponent implements OnInit {
 
-  readonly TtlLogout = 'Logout';
-  readonly TtlAbout = 'About';
-  readonly TtlLanguage = 'Language';
+  public static readonly DEFAULT_LANG_CODE = 'en';
 
-  languages = <SelectItem[]> [
-    { label: 'English', value: 'en' },
-    { label: 'Deutsch', value: 'de' },
-  ];
+  public languages: SelectItem[] = [];
+  public selectedLanguage: string;
 
-  selectedLanguage = 'en';
-
-  links = [
+  public links = [
       { name: 'Administration', path: '/administration', roles: [ Role.Admin ] }
   ];
-  displayAboutDialog = false;
-  user: User = null;
-  rolesTooltip: string;
+  public displayAboutDialog = false;
+  public user: User = null;
+  public rolesTooltip: string;
 
-  constructor(private translate: TranslateService, private authService: AuthenticationService) {
-  }
+  constructor(private translate: TranslateService,
+              private localizationService: MdmLocalizationService,
+              private authService: AuthenticationService,
+              private nodeService: NodeService) {}
 
   ngOnInit() {
-    this.translate.langs = this.languages.map(i => i.value);
-    let browserLang = this.translate.getBrowserLang();
-    if (this.translate.langs.findIndex(l => l === browserLang) > -1) {
-      this.selectedLanguage = browserLang;
-    }
-    this.translate.setDefaultLang(this.selectedLanguage);
-    this.translate.use(this.selectedLanguage);
+    this.initLanguageSettings();
+
     this.authService.getLoginUser().subscribe(value => {
       this.user = value;
       this.rolesTooltip = value.roles.join(', ');
     });
   }
 
-  selectLanguage($event: any) {
+  private initLanguageSettings() {
+    this.localizationService.getLanguages().subscribe(langs => {
+      this.languages = Array.from(langs).map(l => {return { label: ISO6391.getNativeName(l), value: l }});
+      this.translate.langs = this.languages.map(i => i.value);
+
+      this.translate.setDefaultLang(AppComponent.DEFAULT_LANG_CODE);
+      let browserLang = this.translate.getBrowserLang();
+      if (this.translate.langs.findIndex(l => l === browserLang) > -1) {
+        this.selectedLanguage = browserLang;
+      } else {
+        this.selectedLanguage = this.translate.defaultLang;
+      }
+      this.translate.use(this.selectedLanguage);
+    });
+  }
+
+  public onSelectLanguage($event: any) {
     this.translate.use(this.selectedLanguage);
   }
 
-  showAboutDialog() {
+  public onShowAboutDialog() {
     this.displayAboutDialog = true;
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/app.module.ts b/nucleus/webclient/src/main/webapp/src/app/app.module.ts
index 4cd625e..aa9bbb7 100644
--- a/nucleus/webclient/src/main/webapp/src/app/app.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/app.module.ts
@@ -20,7 +20,7 @@
 import { HttpModule } from '@angular/http';
 import { HttpClient } from '@angular/common/http';
 
-import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';
+import { TranslateModule, TranslateLoader, TranslateService, MissingTranslationHandlerParams } from '@ngx-translate/core';
 import { TranslateHttpLoader } from '@ngx-translate/http-loader';
 
 import { DialogModule, DropdownModule } from 'primeng/primeng';
@@ -42,16 +42,33 @@
 import { FilereleaseService } from './filerelease/filerelease.service';
 import { QueryService } from './tableview/query.service';
 import { ViewService } from './tableview/tableview.service';
-import { environment } from '../environments/environment';
 import { FilesAttachableService } from './file-explorer/services/files-attachable.service';
 import { AuthenticationModule } from './authentication/authentication.module';
+import { Observable } from 'rxjs/Rx';
+import { of } from 'rxjs';
+import { catchError } from 'rxjs/operators';
+import { MdmLocalizationService } from '@localization/mdm-localization.service';
+
+class MDMTranslateHttpLoader extends TranslateHttpLoader {
+
+  constructor(http: HttpClient, prefix?: string, suffix?: string) {
+    super(http, prefix, suffix);
+  }
+
+  getTranslation(lang: string): Observable<Object> {
+    try {
+      return super.getTranslation(lang).pipe(catchError(e => super.getTranslation('en')));
+    } catch {
+      return of({});
+    }
+  }
+}
 
 // AoT requires an exported function for factories
 export function HttpLoaderFactory(http: HttpClient) {
-  return new TranslateHttpLoader(http, 'assets/i18n/', '.json');
+  return new MDMTranslateHttpLoader(http, 'assets/i18n/', '.json');
 }
 
-
 @NgModule({
   imports: [
     BrowserModule,
@@ -78,6 +95,7 @@
   providers: [
     NodeService,
     LocalizationService,
+    MdmLocalizationService,
     FilereleaseService,
     BasketService,
     NavigatorService,
diff --git a/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts b/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
index 2ef1da6..43b700b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
@@ -100,7 +100,7 @@
   }
 
   removeSelected() {
-    this._basketService.remove(this.tableViewComponent.menuSelectedRow.getItem());
+    this._basketService.remove(Row.getItem(this.tableViewComponent.menuSelectedRow));
   }
 
   ngOnInit() {
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chart-viewer.style.css b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chart-viewer.style.css
index 8ebcdef..01b362e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chart-viewer.style.css
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chart-viewer.style.css
@@ -34,23 +34,18 @@
   color: black;

 }

 

-p-listbox >>> .ui-listbox-item {

+.list-box .ui-listbox-item {

   padding: 4px 0 4px 8px !important;

 }

 

-p-listbox >>> p-header {

-  display: grid; 

-  grid-template-columns: 12% 63% 25%;

-  align-items: center;

-}

 

-p-listbox >>> .ui-listbox-header {

+.list-box .ui-listbox-header {

   border-bottom-left-radius: 0;

   border-bottom-right-radius: 0;

   /* border-bottom-color: rgb(166,166,166); */

 }

 

-p-listbox >>> .ui-listbox-header-w-checkbox {

+.list-box .ui-listbox-header-w-checkbox {

   padding: 4px 8px;

 }

 

diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
index efd8d58..10b40c5 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
@@ -40,6 +40,8 @@
 import { XyChartViewerToolbarComponent } from './components/xy-chart-viewer-toolbar/xy-chart-viewer-toolbar.component';
 import { XyChartDataSelectionPanelComponent } from './components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component';
 import { ConfirmationService } from 'primeng/api';
+import { DialogModule, TooltipModule } from 'primeng/primeng';
+import { VirtualScrollerModule } from 'primeng/virtualscroller';
 
 @NgModule({
   imports: [
@@ -59,6 +61,9 @@
     ListboxModule,
     PanelModule,
     ConfirmDialogModule,
+    TooltipModule,
+    DialogModule,
+    VirtualScrollerModule
   ],
   declarations: [
     ChartViewerNavCardComponent,
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
index fcb98ad..fe12308 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
@@ -14,7 +14,7 @@
 
 <p-chart #lineChart type="line" [data]="data" [options]="options"></p-chart>
 
-<p-accordion>
+<!-- <p-accordion>
     <p-accordionTab header="{{'chartviewer.request-options.options' | translate}}">
         
         {{'chartviewer.request-options.from' | translate}} <input type="number" numbersOnly pInputText [(ngModel)]="rangeValues[0]" />
@@ -28,4 +28,4 @@
         </div>
         <p-button label="{{'chartviewer.request-options.apply' | translate}}" (onClick)="applySettings($event)"></p-button>
     </p-accordionTab>
-</p-accordion>
+</p-accordion> -->
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.ts
index 35d3ff0..5210977 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.ts
@@ -14,7 +14,7 @@
 
 import { Component,  ViewChild, Input, SimpleChanges, OnChanges } from '@angular/core';
 import { Observable } from 'rxjs';
-import { map, tap, catchError } from 'rxjs/operators';
+import { map, catchError } from 'rxjs/operators';
 
 import { UIChart } from 'primeng/primeng';
 
@@ -24,7 +24,6 @@
 import { MeasuredValues } from '../../model/chartviewer.model';
 import { ChannelGroup } from '../../model/types/channelgroup.class';
 import { Measurement } from '../../model/types/measurement.class';
-import { Channel } from '../../model/types/channel.class';
 import { getDataArray } from '../../model/types/measured-values.class';
 
 @Component({
@@ -191,13 +190,24 @@
 
   convertToDataset(measuredValues: MeasuredValues) {
     const data = getDataArray(measuredValues);
+    const color = '#' + this.hashCode(measuredValues.name + ' [' + measuredValues.unit + ']').toString(16).substr(-6);
     return {
       label: measuredValues.name + ' [' + measuredValues.unit + ']',
       unit: measuredValues.unit,
       data: data ? data.values : [],
-      borderColor: '#' + Math.random().toString(16).substr(-6),
+      borderColor: color,
+      borderWidth: 1,
+      fill: false,
       measuredValues: map,
       channel: new Node()
     };
   }
+
+  private hashCode(s) {
+    let h : number;
+    for(let i = 0; i < s.length; i++) {
+      h = Math.imul(31, h) + s.charCodeAt(i) | 0;
+    }
+    return h;
+  }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chatviewer-nav-card/chart-viewer-nav-card.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chatviewer-nav-card/chart-viewer-nav-card.component.ts
index dd7979c..db5f19c 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chatviewer-nav-card/chart-viewer-nav-card.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chatviewer-nav-card/chart-viewer-nav-card.component.ts
@@ -16,17 +16,15 @@
 import { NavigatorService } from '../../../navigator/navigator.service';
 import { MDMNotificationService } from '../../../core/mdm-notification.service';
 import { TranslateService } from '@ngx-translate/core';
-import { NodeService } from '../../../navigator/node.service';
-import { forkJoin, Observable, of, throwError } from 'rxjs';
-import { Attribute, Node, NodeArray } from '../../../navigator/node';
+import { Node } from '../../../navigator/node';
 import { SelectItem } from 'primeng/api';
-import { PropertyService } from 'src/app/core/property.service';
 import { PreferenceService, Scope } from '../../../core/preference.service';
 import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
-import { Query, QueryService, Row, SearchResult } from '../../../tableview/query.service';
-import { map, switchMap } from 'rxjs/operators';
 import { Measurement } from '../../model/types/measurement.class';
 import { TYPE_CHANNEL, TYPE_CHANNELGROUP, TYPE_MEASUREMENT } from '../../model/constants';
+import { MDMItem } from '@core/mdm-item';
+import { Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
 
 @Component({
   selector: 'mdm5-chartViewerNavCard',
@@ -35,7 +33,8 @@
   providers: []
 })
 export class ChartViewerNavCardComponent implements OnInit, OnDestroy {
-  subscription: any;
+  private subscription = new Subscription();
+
   modes: SelectItem[] = [];
   selectedMode = 'chart';
 
@@ -56,22 +55,19 @@
   constructor(private navigatorService: NavigatorService,
     private notificationService: MDMNotificationService,
     private translateService: TranslateService,
-    private nodeService: NodeService,
-    private _prop: PropertyService,
     private preferenceService: PreferenceService,
-    private chartService: ChartViewerDataService,
-    private queryService: QueryService) {}
+    private chartService: ChartViewerDataService) {}
 
   ngOnInit() {
     this.modes = [
       { label: this.translateService.instant('chartviewer.chart'), value: 'chart' },
       { label: this.translateService.instant('chartviewer.table'), value: 'table' },
     ];
-    this.onSelectedNodeChange(this.navigatorService.getSelectedNode());
-    this.subscription = this.navigatorService.selectedNodeChanged.subscribe(
-      node => this.onSelectedNodeChange(node),
+    this.subscription.add(this.navigatorService.onSelectionChanged().pipe(filter((obj: Node | MDMItem): obj is Node => obj instanceof Node))
+      .subscribe(
+      node => this.handleSelection(node),
       error => this.notificationService.notifyError(this.translateService.instant('details.mdm-detail-view.cannot-update-node'), error)
-    );
+    ));
     this.preferenceService.getPreferenceForScope(Scope.SYSTEM, 'chart-viewer.channels.max-display').subscribe(preferences => {
       if (preferences.length > 0) {
         this.limit = parseInt(preferences[0].value, 10);
@@ -88,7 +84,7 @@
     this.subscription.unsubscribe();
   }
 
-  onSelectedNodeChange(node: Node) {
+  private handleSelection(node: Node) {
     if (!node) {
       return;
     }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
index 5101f26..3e4a3b4 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
@@ -66,10 +66,12 @@
       </div>
 
       <!-- Row 3 -->
-      <div class="p-col-12" style="text-align: right;">
-        <button class="btn btn-mdm" (click)="onApplySettings($event)">
-          <span class="fa fa-check"></span>
-          {{'chartviewer.request-options.apply' | translate}}
+      <div class="p-col-2 p-offset-8">
+        <button pButton class="btn-mdm" (click)="onResetSettings($event)" style="width: 100%;" icon="fa fa-undo" [label]="'chartviewer.request-options.reset' | translate" >
+        </button>
+      </div>
+      <div class="p-col-2">
+        <button pButton class="btn-mdm" (click)="onApplySettings($event)" style="width: 100%;" icon="fa fa-check" [label]="'chartviewer.request-options.apply' | translate" >
         </button>
       </div>
     </div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
index 556cdac..00080ff 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
@@ -34,9 +34,9 @@
   private maxRequestedValues = 10000;
 
   public numberOfRows = 0;
-  public numberOfChunks = 600;
+  public numberOfChunks: number;
   public step: number;
-  public previewEnabled = true;
+  public previewEnabled: boolean;
 
   @Input()
   public measurement: Measurement;
@@ -44,10 +44,10 @@
   @Output()
   public onRequestOptionsChanged = new EventEmitter<RequestOptions>();
 
-  constructor(
-    private chartService: ChartViewerDataService) { }
+  constructor() { }
 
   ngOnInit() {
+    this.initSettings();
     this.reload();
     this.emitRequestOptionsChanged();
   }
@@ -59,6 +59,11 @@
     }
   }
 
+  private initSettings() {
+    this.numberOfChunks = 600;
+    this.previewEnabled = true;
+  }
+
   private reload() {
     if (this.measurement != undefined && this.measurement.channelGroups.length > 0) {
       this.numberOfRows = this.measurement.getFirstChannelGroup().numberOfRows;
@@ -90,6 +95,12 @@
     this.emitRequestOptionsChanged();
   }
 
+  public onResetSettings(event: Event) {
+    this.initSettings();
+    this.reload();
+    this.emitRequestOptionsChanged();
+  }
+
   private emitRequestOptionsChanged() {
     this.onRequestOptionsChanged.emit(
       new RequestOptions(this.rangeValues[1] - this.rangeValues[0], this.rangeValues[0] - 1, this.previewEnabled, this.numberOfChunks));
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css
new file mode 100644
index 0000000..954d6ea
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css
@@ -0,0 +1,107 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+p-panel.list-box ::ng-deep .ui-panel-content {
+  padding: 0;
+}
+p-panel.list-box ::ng-deep .ui-virtualscroller-content {
+  border: none;
+}
+
+p-listbox ::ng-deep .ui-listbox{
+  border: none;
+}
+
+.channelList {
+  list-style-type: none;
+  padding: 0;
+  margin-bottom: 0;
+  min-height: 80px;
+}
+
+.list-box-item {
+  white-space: nowrap;
+  padding: 0.429em 0.857em;
+  cursor: pointer;
+}
+.list-box-item.state-highlight, .list-box-item.state-highlight:hover {
+  color: #ffffff;
+  background-color: #007ad9;
+}
+
+.list-box-item:hover {
+  color: #333333;
+  background-color: #eaeaea;
+}
+
+.list-box-item .ui-chkbox {
+  margin-right: 0.5em;
+}
+
+.filter-container2 {
+  display: grid; 
+  grid-template-columns: 2em auto;
+  align-items: center;
+  padding: 0.429em 0.429em 0.429em 0.857em;
+  border-bottom: 1px solid #a6a6a6;
+}
+.title-bar.y-channels {
+  column-gap: 6px;
+  grid-template-columns: 1rem auto 1rem 1rem;
+}
+
+
+.filter-container {
+  position: relative;
+}
+
+.filter-input-group {
+  line-height: 1;
+  position: absolute;
+  left: 2.3rem;
+  z-index: 99;
+  top: 2.4rem;
+  right: .25rem;
+}
+
+.filter-input-field {
+  width: calc(100% - 2.2rem);
+}
+
+
+p-listbox ::ng-deep .ui-listbox-header.ui-listbox-header-w-checkbox {
+  height: 2.5rem;
+  display: flex;
+  align-items: center;
+}
+
+p-listbox.hiddenList ::ng-deep .ui-listbox-header.ui-listbox-header-w-checkbox {
+  display: none;
+}
+
+p-selectButton ::ng-deep .ui-selectbutton .ui-button {
+  width: 33%;
+  padding: 6px 8px;
+  border: 1px solid#acacac;
+  border-collapse: collapse;
+  border-right: none;
+}
+
+p-selectButton ::ng-deep .ui-selectbutton .ui-button:last-child {
+  border-right: 1px solid#acacac;
+}
+
+p-selectButton ::ng-deep .ui-selectbutton .ui-button.ui-state-active {
+  border-color: #005da6;
+}
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
index d91ae6e..7e461da 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
@@ -11,60 +11,81 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************-->
-
 <div class="p-grid">
-  <div class="p-col-12" style="margin-top: 4px">
-    <p-listbox
-      [options]="measurement.channelGroups"
-      [(ngModel)]="selectedChannelGroups"
-      multiple="multiple"
-      checkbox="checkbox"
-      filter="filter"
-      optionLabel="name"
-      (onChange)="onSelectedChannelGroupsChanged($event)"
-      [ngClass]="{'hiddenList': hiddenGroups}"
-      [style]="{'width':'100%'}"
-      [listStyle]="{'height':'100px'}">
-      <p-header>
-          <span class="icon channelgroup"></span>
-          {{'SubMatrix' | mdmdatasourcetranslate}}
-          <span
-            class="fa toggler"
-            [ngClass]="{'fa-chevron-down': !hiddenGroups, 'fa-chevron-right': hiddenGroups}"
-            (click)="onToggleChannelGroupPanel($event)"></span>
+  <div class="p-col-12" style="margin-top: 4px;">
+    <p-panel class="list-box" [toggleable]="true" expandIcon="fa toggler fa-chevron-right" collapseIcon="fa toggler fa-chevron-down">
+      <p-header class="title-bar channelgroups">
+        <span class="icon channelgroup"></span>
+        &nbsp;{{'SubMatrix' | mdmdatasourcetranslate}}
       </p-header>
-    </p-listbox>
+      <div class="filter-container2">
+        <p-checkbox [(ngModel)]="selectAllChannelGroups" [binary]="true" (click)="onSelectAllChannelGroups($event)"></p-checkbox>
+        <span class="ui-inputgroup">
+          <input type="text" pInputText [(ngModel)]="channelGroupFilter" [pTooltip]="'chartviewer.xy-chart-data-selection-panel.tooltip-regexp-filter' | translate"
+          [placeholder]="'chartviewer.xy-chart-data-selection-panel.filter' | translate" class="filter-input-field"
+          (keyup)="onFilterChannelGroups($event)"/>
+          <span class="ui-inputgroup-addon">
+            <span class="fa fa-filter"></span>
+          </span>
+        </span>
+      </div>
+      <p-listbox
+        [showToggleAll]="false"
+        [options]="channelGroupOptions"
+        [(ngModel)]="selectedChannelGroups"
+        multiple="multiple"
+        checkbox="checkbox"
+        optionLabel="name"
+        (onChange)="onSelectedChannelGroupsChanged($event)"
+        [style]="{'width':'100%'}"
+        [listStyle]="{'height':'100px'}">
+      </p-listbox>
+    </p-panel>
   </div>
   <div class="p-col-12">
-    <p-listbox [options]="yChannelOptions"
-      [(ngModel)]="selectedYChannels"
-      multiple="multiple"
-      checkbox="checkbox"
-      filter="filter"
-      optionLabel="name"
-      (onChange)="onSelectedYChannelsChanged($event)"
-      [ngClass]="{'hiddenList': hiddenYChannels}"
-      [style]="{'width':'100%'}"
-      [styleClass]="'ypanel'"
-      [listStyle]="{'height':'200px'}"
-      [showToggleAll]="false">
-      <p-header>
+    <p-panel class="list-box" [toggleable]="true" expandIcon="fa toggler fa-chevron-right" collapseIcon="fa toggler fa-chevron-down">
+      <p-header class="title-bar channelgroups">
         <span class="icon channel"></span>
-        Y-{{'MeaQuantity' | mdmdatasourcetranslate}}
-        <span
-        class="fa toggler"
-        [ngClass]="{'fa-chevron-down': !hiddenYChannels, 'fa-chevron-right': hiddenYChannels}"
-        (click)="onToggleYChannelPanel($event)"></span>
+        &nbsp;Y-{{'MeaQuantity' | mdmdatasourcetranslate}}
       </p-header>
-    </p-listbox>
+      <div class="filter-container2">
+        <p-checkbox [(ngModel)]="selectAllYChannels" [binary]="true" (click)="onSelectAllYChannels($event)"></p-checkbox>
+        <span class="ui-inputgroup">
+          <input type="text" pInputText [(ngModel)]="yChannelFilter" [pTooltip]="'chartviewer.xy-chart-data-selection-panel.tooltip-regexp-filter' | translate"
+          [placeholder]="'chartviewer.xy-chart-data-selection-panel.filter' | translate" class="filter-input-field"
+          (keyup)="onFilterYChannels($event)"/>
+          <span class="ui-inputgroup-addon">
+            <span class="fa fa-filter"></span>
+          </span>
+        </span>
+      </div>
+      <p-virtualScroller 
+        [value]="yChannelOptionsFiltered" 
+        scrollHeight="200px" 
+        [itemSize]="34" 
+        [style]="{'width':'100%'}"
+        [styleClass]="'ypanel'">
+        <ng-template pTemplate="channel" let-channel>
+          <div class="list-box-item" [ngClass]="{ 'state-highlight': isYChannelSelected(channel) }" (click)="onYChannelClick($event, channel)">
+            <div class="ui-chkbox ui-widget">
+              <div class="ui-chkbox-box ui-widget ui-corner-all ui-state-default" [ngClass]="{'ui-state-active' : isYChannelSelected(channel)}">
+                <span class="ui-chkbox-icon ui-clickable" [ngClass]="{'pi pi-check' : isYChannelSelected(channel)}"></span>
+              </div>
+            </div>
+            <span>{{channel.name}}</span>
+          </div>
+        </ng-template>
+      </p-virtualScroller>
+    </p-panel>
   </div>
+
   <div class="p-col-12">
     <p-panel
       [toggleable]="true"
       [collapsed]="true"
       expandIcon="fa fa-chevron-right"
       collapseIcon="fa fa-chevron-down">
-      <p-header>
+      <p-header class="title-bar channelgroups">
         <span class="icon channel"></span>
         &nbsp;X-{{'MeaQuantity' | mdmdatasourcetranslate}}
       </p-header>
@@ -95,4 +116,45 @@
   </div>
 </div>
 
-<p-confirmDialog icon="pi pi-exclamation-triangle"></p-confirmDialog>
\ No newline at end of file
+<p-confirmDialog icon="pi pi-exclamation-triangle"></p-confirmDialog>
+
+<p-dialog [(visible)]="isYChannelQueryConfigDialogVisible" [style]="{width: '50rem'}">
+  <p-header>
+    {{'chartviewer.xy-chart-data-selection-panel.y-channel-query-config-editor' | translate}}
+  </p-header>
+  <div class="p-grid">
+    <div class="p-col-4">
+      {{'chartviewer.xy-chart-data-selection-panel.result-offset' | translate}}
+    </div>
+    <div class="p-col-8">
+      <input pInputText [(ngModel)]="dialogResultOffset" style="width: 100%;"
+      [placeholder]="'chartviewer.xy-chart-data-selection-panel.result-offset' | translate">
+    </div>
+    <div class="p-col-4">
+      {{'chartviewer.xy-chart-data-selection-panel.result-limit' | translate}}
+    </div>
+    <div class="p-col-4">
+      <p-selectButton [options]="resultLimitModeOptions" [(ngModel)]="dialogResultLimitMode" [style]="{width: '100%'}">
+        <ng-template let-item>
+          <span>{{item.label | translate}}</span>
+        </ng-template>
+      </p-selectButton>
+    </div>
+    <div class="p-col-4">
+      <input pInputText [(ngModel)]="dialogResultLimit" style="width: 100%;" [disabled]="dialogResultLimitMode !== 'MANUAL'"
+      [placeholder]="'chartviewer.xy-chart-data-selection-panel.result-limit-manual-input' | translate">
+    </div>
+  </div>
+  <p-footer>
+    <div class="p-grid">
+      <div class="p-offset-8 p-col-2">
+        <button pButton class="btn-mdm" (click)="onApplyYChannelQueryConfig($event)" style="width: 100%;" icon="fa fa-check" [label]="'chartviewer.xy-chart-data-selection-panel.apply' | translate" >
+        </button>
+      </div>
+      <div class="p-col-2 ">
+        <button pButton class="btn-mdm" (click)="onCloseYChannelConfigDialog($event)" style="width: 100%;" icon="fa fa-times" [label]="'chartviewer.xy-chart-data-selection-panel.cancel' | translate" >
+        </button>
+      </div>
+    </div>
+  </p-footer>
+</p-dialog>
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
index 31fe6da..743ae93 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
@@ -20,18 +20,19 @@
 import { ArrayUtilService } from 'src/app/core/services/array-util.service';
 import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
 import { TYPE_MEASUREMENT, TYPE_CHANNELGROUP, TYPE_CHANNEL } from '../../model/constants';
-import { tap } from 'rxjs/operators';
 import { Node } from '../../../navigator/node';
-import {ConfirmationService} from 'primeng/api';
+import {ConfirmationService, SelectItem} from 'primeng/api';
 import { TranslateService } from '@ngx-translate/core';
 import { Measurement } from '../../model/types/measurement.class';
 import { ChannelGroup } from '../../model/types/channelgroup.class';
 import { Channel } from '../../model/types/channel.class';
+import { QueryConfig } from '../../model/types/query-config.class';
+import { group } from '@angular/animations';
 
 @Component({
   selector: 'mdm5-xy-chart-data-selection-panel',
   templateUrl: './xy-chart-data-selection-panel.component.html',
-  styleUrls: ['../../chart-viewer.style.css']
+  styleUrls: ['../../chart-viewer.style.css', './xy-chart-data-selection-panel.component.css']
 })
 export class XyChartDataSelectionPanelComponent implements OnInit, OnDestroy, OnChanges {
 
@@ -48,6 +49,9 @@
   public yChannelOptions: Channel[] = [];
   public xChannelOptions: {[key: string]: Channel[]} = {};
 
+  public channelGroupOptions: ChannelGroup[] = [];
+  public yChannelOptionsFiltered: Channel[] = [];
+
   // channel(group) selection for chart
   // !! change via setter to update cache for workarround !!
   public selectedChannelGroups: ChannelGroup[] = [];
@@ -64,6 +68,25 @@
   // Toggle selection panel visibility
   public hiddenGroups = false;
   public hiddenYChannels = false;
+  public selectAllChannelGroups: boolean;
+
+  public selectAllYChannels: boolean;
+
+  // Filterstrings
+  public channelGroupFilter: string;
+  public yChannelFilter: string;
+
+  // Toggler for Dialog
+  public isYChannelQueryConfigDialogVisible = false;
+  public resultLimitModeOptions: SelectItem[] = [
+    {label: 'chartviewer.xy-chart-data-selection-panel.default-result-limit', value: 'DEFAULT'},
+    {label: 'chartviewer.xy-chart-data-selection-panel.all-results', value: 'ALL'},
+    {label: 'chartviewer.xy-chart-data-selection-panel.manual-result-limit', value: 'MANUAL'},
+  ]
+  public dialogResultLimitMode: 'DEFAULT' | 'ALL' | 'MANUAL' = 'DEFAULT';
+  public dialogResultLimit: string;
+  public dialogResultOffset: string;
+  private queryConfig: QueryConfig = { resultLimit: undefined, resultOffset: 0};
 
   // for x-channel default value
   private independentChannels = new Map<string, Channel>();
@@ -71,8 +94,8 @@
   /********** subscriptions */
   private chartViewerSub: Subscription;
 
-  constructor(private chartviewerDataService: ChartViewerDataService,
-              private chartViewerService: ChartViewerService,
+  constructor(private chartViewerService: ChartViewerService,
+              private chartViewerDataService: ChartViewerDataService,
               private arrayUtil: ArrayUtilService,
               private confirmationService: ConfirmationService,
               private notificationService: MDMNotificationService,
@@ -89,6 +112,44 @@
     this.chartViewerSub.unsubscribe();
   }
 
+  public onSelectAllChannelGroups(event: any) {
+    if (this.selectAllChannelGroups === true) {
+      this.selectedChannelGroups = this.channelGroupOptions
+      this.selectAllChannelGroups = true;
+    } else {
+      this.selectedChannelGroups = [];
+      this.selectAllChannelGroups = false;
+    }
+    this.onSelectedChannelGroupsChanged(event);
+  }
+
+  public onYChannelClick(event: any, channel: Channel) {
+    console.log("event", channel.name);
+    if (this.selectedYChannels.some(c => c.id === channel.id)) {
+      this.selectedYChannels = this.selectedYChannels.filter(c => c.id !== channel.id);
+    } else {
+      this.selectedYChannels = [...this.selectedYChannels, channel];
+    }
+    this.selectAllYChannels = this.selectedYChannels.length === this.yChannelOptionsFiltered.length;
+
+    this.onSelectedYChannelsChanged(event);
+  }
+
+  public isYChannelSelected(channel: Channel) {
+    return this.selectedYChannels.some(c => c.id === channel.id);
+  }
+
+  public onSelectAllYChannels(event: any) {
+    if (this.selectAllYChannels === true) {
+      this.selectedYChannels = this.yChannelOptionsFiltered;
+      this.selectAllYChannels = true;
+    } else {
+      this.selectedYChannels = [];
+      this.selectAllYChannels = false;
+    }
+    this.onSelectedYChannelsChanged(event);
+  }
+
   ngOnChanges(changes: SimpleChanges) {
     if (changes['xyMode']) {
       this.reloadAxisSelectOptions();
@@ -97,6 +158,9 @@
         this.selectionChanged = false;
       }
     }
+    if(changes['measurement']) {
+      this.initChannelGroupOptions();
+    }
   }
 
   /**************** Html-template listeners *****************************/
@@ -126,16 +190,17 @@
 
   // y-axis
   public onSelectedYChannelsChanged(event: any) {
-    let channel: Channel;
-    // Deselect
+    let channels: Channel[];
+    
     if (this.selectedYChannels.length < this.lastYChannelSelection.length) {
-      channel = this.lastYChannelSelection.find(yChannel => !this.selectedYChannels.some(c => yChannel.id === c.id));
-      // .forEach(yChannel => this.addOrRemoveRow(this.findChannelGroup(yChannel), yChannel));
-      // Select
+      // Deselect
+      channels = this.lastYChannelSelection.filter(yChannel => !this.selectedYChannels.some(c => yChannel.id === c.id));
     } else {
-      channel = this.selectedYChannels.find(group => !this.lastYChannelSelection.some(g => group.id === g.id));
+      // Select
+      channels = this.selectedYChannels.filter(yChannel => !this.lastYChannelSelection.some(c => yChannel.id === c.id));
     }
-    this.addOrRemoveRow(this.measurement.findChannelGroupByChannel(channel), channel);
+
+    channels.forEach(c => this.addOrRemoveRow(this.measurement.findChannelGroupByChannel(c), c));
     this.lastYChannelSelection = this.selectedYChannels;
     this.fireSelectionChanged();
   }
@@ -149,16 +214,18 @@
       // Deselect
       if (this.selectedChannelGroups.length < this.lastChannelGroupSelection.length) {
         this.lastChannelGroupSelection
+        .filter(group => group != undefined)
         .filter(group => !this.selectedChannelGroups.some(g => group.id === g.id))
         .forEach(group => this.handleDeselectChannelGroup(group));
         // Select
       } else {
         this.selectedChannelGroups
+        .filter(group => group != undefined)
         .filter(group => !this.lastChannelGroupSelection.some(g => group.id === g.id))
         .forEach(group => this.handleSelectChannelGroup(group));
       }
     }
-    this.lastChannelGroupSelection = this.selectedChannelGroups;
+    this.lastChannelGroupSelection = this.selectedChannelGroups.filter(group => group != undefined);
   }
 
   public onToggleChannelGroupPanel(event: Event) {
@@ -169,6 +236,69 @@
     this.hiddenYChannels = !this.hiddenYChannels;
   }
 
+  public onFilterChannelGroups(event: KeyboardEvent ) {
+    this.initChannelGroupOptions();
+  }
+
+  public onFilterYChannels(event: KeyboardEvent ) {
+    this.filterYChannelOptions();
+  }
+
+  /**
+   * Dislplays Editor-Dialog for y-channels and initalizes query config params
+   * @param event 
+   */
+  public onShowYChannelConfigDialog(event: Event) {
+    switch (this.dialogResultLimitMode) {
+      case 'DEFAULT':
+      case 'ALL':
+        this.dialogResultLimit = undefined;
+      break;
+      case 'MANUAL':
+        this.dialogResultLimit = this.queryConfig.resultLimit != undefined ? this.queryConfig.resultLimit.toString() : undefined;
+        break;
+    }
+
+    this.dialogResultOffset = this.queryConfig.resultOffset.toString();
+    this.isYChannelQueryConfigDialogVisible = true;
+  }
+
+  /**
+   * Applies Query-Settings from Editor-Dialog for y-channels
+   * @param event 
+   */
+  public onApplyYChannelQueryConfig(event: Event) {
+    const off = +this.dialogResultOffset;
+    this.queryConfig.resultOffset = isNaN(off) ? 0 : off;
+
+    switch (this.dialogResultLimitMode) {
+      case 'DEFAULT':
+        this.queryConfig.resultLimit = undefined;
+        break;
+      case 'ALL':
+        this.queryConfig.resultLimit = 0;
+        break;
+      case 'MANUAL':
+        const lim = +this.dialogResultLimit;
+        this.queryConfig.resultLimit = isNaN(lim) ? undefined : lim;
+        break;
+    }
+    this.isYChannelQueryConfigDialogVisible = false;
+    this.chartViewerDataService.resultLimit = this.queryConfig.resultLimit;
+    this.chartViewerDataService.resultOffset = this.queryConfig.resultOffset;
+    this.chartViewerService.sendQueryConfig(this.queryConfig);
+  }
+
+  /**
+   * Hides Query-Editor-Dialog for y-channels
+   * @param event
+   */
+  public onCloseYChannelConfigDialog(event: Event) {
+    this.isYChannelQueryConfigDialogVisible = false;
+  }
+
+  /**************** Private methods *****************************/
+
   private handleDeselectChannelGroup(channelGroup: ChannelGroup) {
     // remove rows
     if (this.arrayUtil.isNotEmpty(this.selectedChannelRows)) {
@@ -196,11 +326,16 @@
     if (node != undefined) {
       switch (node.type) {
         case TYPE_MEASUREMENT:
+          /** measurement is set via @input and thus new ChannelGroupoption are set set via ngOnChanges */
           this.resetChannelData();
           this.fireSelectionChanged();
           break;
         case TYPE_CHANNELGROUP:
-          this.cleanOldState();
+          // this.cleanOldState();
+          this.resetChannelData();
+          this.initChannelGroupOptions();
+          console.log('node ' + node.id)
+          console.log(this.measurement)
           this.addChannelGroupToSelection(node.id, this.measurement.findChannelGroup(node.id));
           break;
         case TYPE_CHANNEL:
@@ -235,7 +370,7 @@
    */
   private cleanOldState() {
     if (this.arrayUtil.isNotEmpty(this.selectedChannelGroups)
-      && this.selectedChannelGroups.some(selected => this.measurement.findChannelGroup(selected.id) === undefined)) {
+      && this.selectedChannelGroups.some(selected => selected === undefined || this.measurement.findChannelGroup(selected.id) === undefined)) {
       this.resetChannelData();
     }
   }
@@ -272,10 +407,14 @@
    * @param channelGroup
    */
   private addChannelGroupToSelection(id: string, channelGroup: ChannelGroup) {
+    console.log('addChannelGroupToSelection')
+    console.log(this.selectedChannelGroups)
+    console.log(channelGroup)
     if (this.selectedChannelGroups == undefined) {
       this.setSelectedChannelGroups([]);
     }
-    if (!this.isChannelGroupIn(channelGroup, this.selectedChannelGroups)) {
+    if (channelGroup != undefined && !this.isChannelGroupIn(channelGroup, this.selectedChannelGroups)) {
+      console.log('12')
       this.setSelectedChannelGroups(this.selectedChannelGroups.concat([channelGroup]));
       this.handleSelectChannelGroup(channelGroup);
     }
@@ -315,6 +454,7 @@
         this.yChannelOptions = this.yChannelOptions.concat(channels);
       }
     }
+    this.filterYChannelOptions()
   }
 
   /**
@@ -326,6 +466,7 @@
     this.setSelectedYChannels([]);
     this.xChannelOptions = {};
     this.yChannelOptions = [];
+    this.yChannelOptionsFiltered = [];
   }
 
     /**
@@ -362,7 +503,7 @@
     const index = this.selectedChannelRows.findIndex(row => row.yChannel.id === yChannel.id);
     if (index > -1) {
       this.selectedChannelRows.splice(index, 1);
-      this.selectedChannelRows = [].concat(this.selectedChannelRows);
+      this.selectedChannelRows = [].concat(this.selectedChannelRows).sort((cs1, cs2) => cs1.yChannel.name.localeCompare(cs2.yChannel.name));
     } else {
       this.addRow(channelGroup, yChannel);
     }
@@ -376,6 +517,7 @@
   private addRow(channelGroup: ChannelGroup, yChannel: Channel) {
     const row = new ChannelSelectionRow(channelGroup, yChannel, this.getDefaultXChannel(channelGroup));
     this.selectedChannelRows.push(row);
+    this.selectedChannelRows.sort((cs1, cs2) => cs1.yChannel.name.localeCompare(cs2.yChannel.name))
   }
 
   /**
@@ -401,7 +543,7 @@
    */
   private isChannelIn(node: Channel, array: Channel[]) {
     let response = false;
-    if (node != undefined && this.arrayUtil.isNotEmpty(array)) {
+    if (this.arrayUtil.isNotEmpty(array)) {
       response = array.findIndex(n => n.id === node.id) > -1;
     }
     return response;
@@ -434,4 +576,29 @@
     this.selectedChannelGroups = channelGroups;
     this.lastChannelGroupSelection = channelGroups;
   }
+
+  private initChannelGroupOptions() {
+    this.channelGroupOptions = this.filter(this.measurement.channelGroups, this.channelGroupFilter);
+  }
+
+  private filterYChannelOptions() {
+    this.yChannelOptionsFiltered = this.filter(this.yChannelOptions, this.yChannelFilter);
+  }
+
+  private filter<T extends {name: string}>(options: T[], filterString: string) {
+    let result: T[];
+    if(filterString != undefined && filterString !== '') {
+      try {
+        const regEx = new RegExp(filterString);
+        result = options.filter(opt => regEx != undefined ? regEx.test(opt.name) : false);
+      } catch {
+        result = options.filter(opt => opt.name.includes(filterString));
+      }
+    } else {
+      result = options;
+    }
+
+    return result.sort((c1, c2) => c1.name.localeCompare(c2.name));
+  }
+
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.html
index 3dd144b..f776cda 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.html
@@ -19,5 +19,5 @@
 </div>
 
 <div *ngIf="measurement !== undefined">
-  <app-xy-chart-viewer [measurement]="measurement"></app-xy-chart-viewer>
+  <mdm5-xy-chart-viewer [measurement]="measurement"></mdm5-xy-chart-viewer>
 </div>
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.ts
index ae565e8..9a85a25 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer-nav-card/xy-chart-viewer-nav-card.component.ts
@@ -17,20 +17,16 @@
 import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
 import { Node } from '../../../navigator/node';
 import { TYPE_CHANNEL, TYPE_CHANNELGROUP, TYPE_MEASUREMENT } from '../../model/constants';
-import { Subscription, forkJoin } from 'rxjs';
-import { NodeService } from 'src/app/navigator/node.service';
-import { groupBy, map, tap } from 'rxjs/operators';
-import { MDMItem } from 'src/app/core/mdm-item';
+import { Subscription } from 'rxjs';
 import { ChartViewerService } from '../../services/chart-viewer.service';
-import { ChannelGroup } from '../../model/types/channelgroup.class';
-import { Query, QueryService, Row } from 'src/app/tableview/query.service';
-import { Channel } from '../../model/types/channel.class';
 import { Measurement } from '../../model/types/measurement.class';
-import { nodeChildrenAsMap } from '@angular/router/src/utils/tree';
 import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
+import { MDMItem } from '@core/mdm-item';
+import { filter } from 'rxjs/operators';
+import { QueryConfig } from '../../model/types/query-config.class';
 
 @Component({
-  selector: 'app-xy-chart-viewer-nav-card',
+  selector: 'mdm5-xy-chart-viewer-nav-card',
   templateUrl: './xy-chart-viewer-nav-card.component.html'
 })
 export class XyChartViewerNavCardComponent implements OnInit, OnDestroy {
@@ -45,17 +41,17 @@
   constructor(
     private navigatorService: NavigatorService,
     private notificationService: MDMNotificationService,
-    private nodeService: NodeService,
     private chartViewerService: ChartViewerService,
-    private chartViewerDataService: ChartViewerDataService,
-    private queryService: QueryService) {}
+    private chartViewerDataService: ChartViewerDataService) {}
 
   ngOnInit() {
-    this.selectedNodeChanged(this.navigatorService.getSelectedNode());
-    this.selectedNodeSub = this.navigatorService.selectedNodeChanged.subscribe(
+    this.selectedNodeSub = this.navigatorService.onSelectionChanged().pipe(
+      filter((obj: Node | MDMItem): obj is Node => obj instanceof Node)
+    ).subscribe(
       node => this.selectedNodeChanged(node),
       error => this.notificationService.notifyError('details.mdm-detail-view.cannot-update-node', error)
     );
+    this.chartViewerService.onQueryConfigChange().subscribe(conf => this.reloadOnConfigChange(conf));
   }
 
   ngOnDestroy() {
@@ -90,9 +86,8 @@
           this.selectedChannel(node);
           break;
         default:
-          this.selectedNode = undefined;
           this.measurement = undefined;
-          this.chartViewerService.sendNodeMeta(this.selectedNode);
+          this.chartViewerService.sendNodeMeta(undefined);
       }
     }
   }
@@ -100,25 +95,25 @@
   // reload all, if channel is not present yet.
   private selectedChannel(channel: Node) {
     if (!this.isChannelPresent(channel)) {
-      this.loadMeasurement(TYPE_CHANNEL, channel);
+      this.loadMeasurement(channel);
     } else {
-      this.chartViewerService.sendNodeMeta(this.selectedNode);
+      this.chartViewerService.sendNodeMeta(channel);
     }
   }
 
   private selectedChannelGroup(channelGroup: Node) {
     if (!this.isChannelGroupPresent(channelGroup)) {
-      this.loadMeasurement(TYPE_CHANNELGROUP, channelGroup);
+      this.loadMeasurement(channelGroup);
     } else {
-      this.chartViewerService.sendNodeMeta(this.selectedNode);
+      this.chartViewerService.sendNodeMeta(channelGroup);
     }
   }
 
   private selectedMeasurement(measurement: Node) {
     if (this.measurement == undefined || this.measurement.id !== measurement.id) {
-      this.loadMeasurement(TYPE_MEASUREMENT, measurement);
+      this.loadMeasurement(measurement);
     } else {
-      this.chartViewerService.sendNodeMeta(this.selectedNode);
+      this.chartViewerService.sendNodeMeta(measurement);
     }
   }
 
@@ -134,10 +129,21 @@
     }
   }
 
-  private loadMeasurement(type: string, node: Node) {
+  private loadMeasurement(node: Node) {
     this.chartViewerDataService.loadMeasurement(node).subscribe(mea => {
       this.measurement = mea;
-      this.chartViewerService.sendNodeMeta(this.selectedNode);
+      this.chartViewerService.sendNodeMeta(node);
+    });
+  }
+  
+  private reloadOnConfigChange(conf: QueryConfig) {
+    const tmp = new Node();
+    tmp.type = this.measurement.type;
+    tmp.id = this.measurement.id;
+    tmp.sourceName = this.measurement.source;
+    this.chartViewerDataService.loadMeasurement(tmp).subscribe(mea => {
+      // this.chartViewerService.sendNodeMeta(tmp);
+      this.measurement = mea;
     });
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.ts
index 0af5b88..24ade79 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-viewer/xy-chart-viewer.component.ts
@@ -25,14 +25,14 @@
 import { ArrayUtilService } from 'src/app/core/services/array-util.service';
 import { ChartViewerService } from '../../services/chart-viewer.service';
 import { forkJoin} from 'rxjs';
-import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
 import { SelectItem } from 'primeng/api';
 import { TranslateService } from '@ngx-translate/core';
 import { Measurement } from '../../model/types/measurement.class';
 import { Channel } from '../../model/types/channel.class';
+import { Observable } from 'rxjs';
 
 @Component({
-  selector: 'app-xy-chart-viewer',
+  selector: 'mdm5-xy-chart-viewer',
   templateUrl: './xy-chart-viewer.component.html',
   styleUrls: ['../../chart-viewer.style.css']
 })
@@ -65,8 +65,7 @@
   constructor(private chartviewerDataService: ChartViewerDataService,
               private chartviewerService: ChartViewerService,
               private arrayUtil: ArrayUtilService,
-              private translateService: TranslateService,
-              private notificationService: MDMNotificationService) {
+              private translateService: TranslateService) {
   }
 
   ngOnInit() {
@@ -139,25 +138,83 @@
    */
   private doChart() {
     if (this.arrayUtil.isNotEmpty(this.selectedChannelRows) && this.requestOptions != undefined) {
-      const xChannels = this.selectedChannelRows.filter(row => row.xChannel != undefined)
-        .map(row => this.chartviewerDataService.loadValues(row.channelGroup, [row.xChannel],
-          this.requestOptions.startIndex, this.requestOptions.requestSize, this.requestOptions.getChunks()));
-      forkJoin(xChannels)
-        .pipe(
-          map(array => array.map((mea, index) => ({
-            yChannel: (this.selectedChannelRows[index] !== undefined ? this.selectedChannelRows[index].yChannel : undefined),
-            measuredValues: mea[0]
-          }))),
-          flatMap(xValues => this.loadYData(xValues)),
-          tap(x => console.log(x))
-        )
-      .subscribe(resp => this.applyData(resp));
+      const channelsByGroup = this.groupChannelRowsByChannelGroup(this.selectedChannelRows);
+
+      this.requestMeasuredValues(channelsByGroup).pipe(
+        map(mvs => this.selectedChannelRows.map(row => {
+            const xMv = mvs.find(t => t.channel.id === row.xChannel.id);            
+            const yMv = mvs.find(t => t.channel.id === row.yChannel.id);
+
+            if (!xMv) {
+              throw new Error("Cannot find measured values for Channel " + row.xChannel.name + " (Id=" + row.xChannel.id + ").");
+            }
+
+            if (!yMv) {
+              throw new Error("Cannot find measured values for Channel " + row.yChannel.name + " (Id=" + row.yChannel.id + ").");
+            }
+
+            const dataSet = this.chartviewerService.toXyDataSet(xMv.measuredValues, yMv.measuredValues);
+            return this.setChartProperties(dataSet);
+          })),
+        map(sets => new ChartData([], sets.reduce((a, b) => a.concat(b), [])))
+      ).subscribe(resp => this.applyData(resp));
     } else {
       this.initChartData();
     }
   }
 
   /**
+   * Requests the values of all Channels for all ChannelGroups. Returns an Observable containing 
+   * all Channels together with their MeasuredValues
+   * @param channelsByGroup
+   */
+  private requestMeasuredValues(channelsByGroup: Record<string, Channel[]>) {
+    let measuredValues : Observable<{ channel: Channel, measuredValues: MeasuredValues}[]>[] = []
+    for (const channelGroupId in channelsByGroup) {
+      const channelGroup = this.selectedChannelRows.find(row => row.channelGroup.id == channelGroupId).channelGroup;
+
+      let obs = this.chartviewerDataService.loadValues(channelGroup, channelsByGroup[channelGroupId],
+        this.requestOptions.startIndex, this.requestOptions.requestSize, this.requestOptions.getChunks()).pipe(
+          map(mv => this.mergeMeasuredValuesByName(channelsByGroup[channelGroupId], mv))
+        )
+
+      measuredValues.push(obs);
+    }
+    return forkJoin(measuredValues).pipe(flatMap(mv => mv));
+  }
+
+  /**
+   * Group the ChannelSelectionRows by ChannelGroup, e.g. the returned record has the ID of the 
+   * channelGroup as key and the list of corresponding Channels (X and Y Channels) as value.
+   * @param channelRows
+   */
+  private groupChannelRowsByChannelGroup(channelRows: ChannelSelectionRow[]) {
+    return channelRows.reduce((previous, currentItem) => {
+      const group = currentItem.channelGroup.id;
+      if (!previous[group]) {
+        previous[group] = [];
+      }
+      if (previous[group].findIndex(c => c.id === currentItem.xChannel.id) == -1) {
+        previous[group].push(currentItem.xChannel);
+      }
+      if (previous[group].findIndex(c => c.id === currentItem.yChannel.id) == -1) {
+        previous[group].push(currentItem.yChannel);
+      }
+      return previous;
+    }, {} as Record<string, Channel[]>);
+  }
+
+  /**
+   * Merge list of Channels with MeasuredValues by channel name. Only channels of one ChannelGroup 
+   * are allowed, because names of Channels within a ChannelGroup are unique.
+   * @param channels 
+   * @param measuredValues 
+   */
+  private mergeMeasuredValuesByName(channels: Channel[], measuredValues: MeasuredValues[]) {
+    return channels.map(c => { return { channel: c, measuredValues: measuredValues.find(mv => mv.name === c.name)}; });
+  }
+
+  /**
    * Apply the ChartData to data and options attributes of the chart component
    * @param xValues
    */
@@ -177,52 +234,8 @@
     }
   }
 
-  /**
-   * Loads measured values for y-channels, merges them with given values for x
-   * @param xValues
-   */
-  private loadYData(xValues: {yChannel: Channel; measuredValues: MeasuredValues}[]) {
-    if (xValues != undefined) {
-      const yChannels = this.groupYChannelsByChannelGroup(this.selectedChannelRows);
-      // const channelGroups = this.selectedChannelGroups.filter(cg => this.arrayUtil.isNotEmpty(yChannels[cg.id]));
-      const channelGroups = Array.from(new Set(this.selectedChannelRows.map(row => row.channelGroup)));
-
-      const obs = channelGroups.map(cg => this.chartviewerDataService.loadValues(
-        cg, yChannels[cg.id], this.requestOptions.startIndex, this.requestOptions.requestSize, this.requestOptions.getChunks()));
-      // forkJoin return observables in order of input array. Therefore channelgroup id has to be like tmpChannelGroups[index].id
-      return forkJoin(obs)
-        .pipe(
-          map(array => array.map((yData, channelGroupIndex) =>
-              yData.map((yValues, yChannelIndex) => {
-                const cgId = channelGroups[channelGroupIndex].id;
-                const xMeasuredValues = this.extractXMeasuredValues(xValues, yChannels[cgId][yChannelIndex].id);
-                const dataSet = this.chartviewerService.toXyDataSet(xMeasuredValues, yValues);
-                return this.setChartProperties(dataSet);
-              }))),
-          map(sets => new ChartData([], sets.reduce((a, b) => a.concat(b), [])))
-        );
-    }
-  }
-
-  private extractXMeasuredValues(xValues: {yChannel: Channel; measuredValues: MeasuredValues}[], channelId: string) {
-    const val = xValues.find(v => v.yChannel.id === channelId);
-    if (val != undefined ) {
-      return val.measuredValues;
-    }
-  }
-
   /******* Helping functions */
 
-  private groupYChannelsByChannelGroup(array: ChannelSelectionRow[]) {
-    return array.reduce(
-      (obj, next) => {
-        const value = next.channelGroup.id;
-        obj[value] = obj[value] || [];
-        obj[value] = obj[value].concat(next.yChannel);
-        return obj;
-      }, {});
-  }
-
   private setChartProperties(dataset: ChartXyDataSet) {
     if (!this.toolbarProperties.drawPoints) {
       dataset.pointRadius = 0;
@@ -231,10 +244,18 @@
     dataset.fill = this.toolbarProperties.fillArea;
     dataset.lineTension = this.toolbarProperties.lineTension;
     dataset.borderWidth = this.toolbarProperties.lineWidth;
-    const color = '#' + Math.random().toString(16).substr(-6);
+    const color = '#' + this.hashCode(dataset.label).toString(16).substr(-6)
     dataset.borderColor = color;
     dataset.backgroundColor = color;
 
     return dataset;
   }
+
+  private hashCode(s) {
+    let h : number;
+    for(let i = 0; i < s.length; i++) {
+      h = Math.imul(31, h) + s.charCodeAt(i) | 0;
+    }
+    return h;
+  }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/query-config.class.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/query-config.class.ts
new file mode 100644
index 0000000..ae8a653
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/query-config.class.ts
@@ -0,0 +1,4 @@
+export class QueryConfig {
+  resultOffset: number;
+  resultLimit: number;
+}
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer-data.service.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer-data.service.ts
index 83eaeef..beffd2e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer-data.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer-data.service.ts
@@ -16,18 +16,20 @@
 import { Injectable } from '@angular/core';
 
 import { plainToClass } from 'class-transformer';
-import { map, catchError } from 'rxjs/operators';
+import { map, catchError, shareReplay, tap } from 'rxjs/operators';
 
 import { HttpErrorHandler } from '../../core/http-error-handler';
 import { PropertyService } from '../../core/property.service';
 import { Node } from '../../navigator/node';
 import { MeasuredValuesResponse, MeasuredValues, PreviewValueList, NumberArray, BooleanArray, DateArray, StringArray } from '../model/chartviewer.model';
 import { QueryService, Query, Row } from '../../tableview/query.service';
-import { throwError } from 'rxjs';
+import { Observable, of, Subject, throwError } from 'rxjs';
 import { ChannelGroup } from '../model/types/channelgroup.class';
 import { Channel } from '../model/types/channel.class';
 import { Measurement } from '../model/types/measurement.class';
 import { TYPE_CHANNEL, TYPE_CHANNELGROUP, TYPE_MEASUREMENT } from '../model/constants';
+import { NavigatorService } from '@navigator/navigator.service';
+import { QueryConfig } from '../model/types/query-config.class';
 
 @Injectable({
   providedIn: 'root'
@@ -35,6 +37,9 @@
 export class ChartViewerDataService {
   private _contextUrl: string;
 
+  public resultLimit: number = undefined;
+  public resultOffset = 0;
+
   private httpOptions = {
     headers: new HttpHeaders({
       'Content-Type': 'application/json',
@@ -128,6 +133,8 @@
     query.columns = ['Measurement.Id', 'ChannelGroup.Id', 'ChannelGroup.Name', 'ChannelGroup.SubMatrixNoRows',
       'Channel.Id', 'Channel.Name', 'LocalColumn.axistype', 'LocalColumn.IndependentFlag'];
     query.resultType = 'Channel';
+    query.resultOffset = this.resultOffset;
+    query.resultLimit = 0; // request all results
 
     return this.queryService.query(query).pipe(
       map(sr => sr.rows.map(row => this.mapRow(row))),
@@ -142,24 +149,24 @@
         });
 
         return mea;
-      }),
+      })
     );
   }
 
   private mapRow(row: Row) {
     let channel = new Channel();
     channel.id = row.id;
-    channel.name = row.getColumn('Channel.Name');
-    channel.channelGroupId = row.getColumn('ChannelGroup.SubMatrixNoRows');
-    channel.axisType = row.getColumn('LocalColumn.axistype'),
-    channel.isIndependent = row.getColumn('LocalColumn.IndependentFlag') === '1';
+    channel.name = Row.getColumn(row, 'Channel.Name');
+    channel.channelGroupId = Row.getColumn(row, 'ChannelGroup.SubMatrixNoRows');
+    channel.axisType = Row.getColumn(row, 'LocalColumn.axistype'),
+    channel.isIndependent = Row.getColumn(row, 'LocalColumn.IndependentFlag') === '1';
 
     let channelGroup = new ChannelGroup(
       row.source,
-      row.getColumn('ChannelGroup.Id'),
-      parseInt(row.getColumn('ChannelGroup.SubMatrixNoRows'), 10));
+      Row.getColumn(row, 'ChannelGroup.Id'),
+      parseInt(Row.getColumn(row, 'ChannelGroup.SubMatrixNoRows'), 10));
 
-    channelGroup.name = row.getColumn('ChannelGroup.Name');
-    return { meaId: row.getColumn('Measurement.Id'), channelGroup: channelGroup, channel: channel };
+    channelGroup.name = Row.getColumn(row, 'ChannelGroup.Name');
+    return { meaId: Row.getColumn(row, 'Measurement.Id'), channelGroup: channelGroup, channel: channel };
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer.service.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer.service.ts
index 8f658ac..355237c 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/services/chart-viewer.service.ts
@@ -3,6 +3,7 @@
 import { Subject } from 'rxjs';
 import { MeasuredValues, ChartData, ChartXyDataSet, ChartPoint } from '../model/chartviewer.model';
 import { getDataArray } from '../model/types/measured-values.class';
+import { QueryConfig } from '../model/types/query-config.class';
 
 @Injectable({
   providedIn: 'root'
@@ -11,6 +12,8 @@
 
   private nodeMetaSubject = new Subject<Node>();
 
+  private queryConfigSubject = new Subject<QueryConfig>();
+
   constructor() {}
 
   public sendNodeMeta(nodeMeta: Node) {
@@ -20,6 +23,14 @@
   public onNodeMetaChange() {
     return this.nodeMetaSubject.asObservable();
   }
+  
+  public sendQueryConfig(queryConfig: QueryConfig) {
+    this.queryConfigSubject.next(queryConfig);
+  }
+
+  public onQueryConfigChange() {
+    return this.queryConfigSubject.asObservable();
+  }
 
   public toXyDataSet(xData: MeasuredValues, yData: MeasuredValues) {
     const xValues = (xData !== undefined && getDataArray(xData) !== undefined ? getDataArray(xData).values : undefined) as number[];
diff --git a/nucleus/webclient/src/main/webapp/src/app/core/mdm-core.module.ts b/nucleus/webclient/src/main/webapp/src/app/core/mdm-core.module.ts
index 36bdc62..e11865b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/core/mdm-core.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/core/mdm-core.module.ts
@@ -47,6 +47,8 @@
 import { Observable } from 'rxjs/Observable';
 import { startWith, switchMap } from 'rxjs/operators';
 import { OnlyNumberDirective } from './only-number-directive';
+import { MdmTranslatePipe } from '@localization/mdm-translate.pipe';
+import { MimeTypePipe } from '@localization/mime-type.pipe';
 
 // Marker function for ngx-translate-extract:
 export function TRANSLATE(str: string) {
@@ -84,7 +86,9 @@
     OverwriteDialogComponent,
     MDMDataSourceTranslationPipe,
     OnlyNumberDirective,
-    AttributeValuePipe
+    MdmTranslatePipe,
+    AttributeValuePipe,
+    MimeTypePipe,
   ],
   exports: [
     CommonModule,
@@ -108,6 +112,8 @@
     MDMDataSourceTranslationPipe,
     AttributeValuePipe,
     OnlyNumberDirective,
+    MdmTranslatePipe,
+    MimeTypePipe,
   ],
   providers: [
       PositioningService,
diff --git a/nucleus/webclient/src/main/webapp/src/app/core/mdm-item.ts b/nucleus/webclient/src/main/webapp/src/app/core/mdm-item.ts
index 30f0688..9b20088 100644
--- a/nucleus/webclient/src/main/webapp/src/app/core/mdm-item.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/core/mdm-item.ts
@@ -19,11 +19,15 @@
   source: string;
   type: string;
   id: string;
+  serial: string;
+  filter?: string;
 
-  constructor(source: string, type: string, id: string) {
+  constructor(source: string, type: string, id: string, serial?: string, filter?: string) {
     this.source = source;
     this.type = type;
     this.id = id;
+    this.serial = serial;
+    this.filter = filter;
   }
 
   equalsNode(node: Node) {
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/detail-panel/detail-panel.component.html b/nucleus/webclient/src/main/webapp/src/app/details/components/detail-panel/detail-panel.component.html
index 2cbf8ee..6ed8016 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/detail-panel/detail-panel.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/detail-panel/detail-panel.component.html
@@ -17,7 +17,8 @@
   <p-header >
     <span [ngClass]="showButton ? 'button-block' : ''">
       <span *ngIf="node" class="pannel-icon-header" [ngClass]="getClass()">
-        {{node.type | mdmdatasourcetranslate}} <span *ngIf="(status$ | async) as status">[{{status}}]</span>
+        <!-- {{node.type | mdmdatasourcetranslate}} <span *ngIf="(status$ | async) as status">[{{status}}]</span> -->
+        {{node | mimeType | mdmTranslate : node?.sourceName | async}} <span *ngIf="(status$ | async) as status">[{{status}}]</span>
       </span>
       <span class="btn-group" style="float: right;">
         <button *ngIf="showButton"
@@ -41,7 +42,8 @@
     </thead>
     <tbody *ngIf="node">
       <tr *ngFor="let attr of displayAttributes">
-        <td>{{attr.name}}</td>
+        <td>{{node | mimeType | mdmTranslate : node?.sourceName : attr?.name | async}}</td>
+        <!-- <td>{{attr.name}}</td> -->
         <td>{{attr | attributeValue | async}}</td>
         <td>{{attr.unit}}</td>
       </tr>
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
index 8109cc7..e20456e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.html
@@ -20,7 +20,6 @@
 </div>
 
 <div *ngIf="contextComponents" class="mdm-details-attributes">
-
   <!-- <button (click)="changeTreeEdit(true, false)" *ngIf="!editMode && canEdit" class="btn btn-mdm" style="margin-bottom: 5px;">
     <span class="fa fa-pencil"></span>
     {{ 'details.mdm-detail-descriptive-data.btn-edit' | translate }}
@@ -73,7 +72,9 @@
         <!-- name -->
         <td class="{{rowData.header ? 'mdm-component-row' : 'mdm-attribute-row'}}">
           <p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
-          <span pTooltip="{{rowData.attribute != null ? rowData.attribute.description : ''}}" tooltipPosition="bottom">{{rowData.name}}</span>
+          <span title="{{rowData.attribute != null ? rowData.attribute.description : ''}}">
+            {{rowData?.mimeType | mdmTranslate : selectedNode?.sourceName : rowData?.attribute?.name | async}}
+          </span>
         </td>
 
         <!-- ordered -->
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
index e1e4f40..754ed8d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
@@ -13,13 +13,12 @@
  ********************************************************************************/
 
 
-import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
 import { DatePipe } from '@angular/common';
 
 import { TreeNode } from 'primeng/api';
 import { TreeTable } from 'primeng/primeng';
-import { Observable } from 'rxjs';
 import { TranslateService } from '@ngx-translate/core';
 
 import { Node, Attribute, ContextGroup } from '@navigator/node';
@@ -33,6 +32,9 @@
 import { Sensor, Components, Context, ContextAttributeIdentifier} from '../../model/details.model';
 import { ContextService } from '../../services/context.service';
 import { Role } from 'src/app/authentication/authentication.service';
+import { catchError, filter, finalize, skip, take, tap } from 'rxjs/operators';
+import { of, Subscription } from 'rxjs';
+import { MDMItem } from '@core/mdm-item';
 
 @Component({
   selector: 'mdm-detail-context',
@@ -40,11 +42,12 @@
   styleUrls: ['mdm-detail-descriptive-data.component.css'],
 })
 
-export class MDMDescriptiveDataComponent implements OnInit {
+export class MDMDescriptiveDataComponent implements OnInit, OnDestroy {
 
   private readonly StatusLoading = TRANSLATE('details.mdm-detail-descriptive-data.status-loading');
   private readonly StatusSaving = TRANSLATE('details.mdm-detail-descriptive-data.status-saving');
   private readonly StatusNoNodes = TRANSLATE('details.mdm-detail-descriptive-data.status-no-nodes-available');
+  private readonly StatusVirtualNodes = TRANSLATE('details.mdm-detail-descriptive-data.status-virtual-node-selected');
   private readonly StatusNoDescriptiveData = TRANSLATE('details.mdm-detail-descriptive-data.status-no-descriptive-data-available');
 
   public treeNodes: {[key: string]: TreeNode[]};
@@ -66,6 +69,8 @@
 
   public isTreeTableExpanded = true;
 
+  private sub = new Subscription();
+
   constructor(private route: ActivatedRoute,
               private _contextService: ContextService,
               private navigatorService: NavigatorService,
@@ -80,27 +85,40 @@
 
   ngOnInit() {
     this.authenticationService.isUserInRole([Role.Admin.toString(), Role.User.toString()]).subscribe(b => this.canEdit = b);
-    this.status = this.StatusLoading;
+    this.status = this.StatusNoNodes;
     this.editMode = false;
 
-    this.refreshDetailData(this.navigatorService.getSelectedNode());
-    this.route.params
-      .subscribe(
-        params => this.refreshContextData(params['context']),
-          error => this.notificationService.notifyError(
-            this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-scope'), error));
-
+    this.sub.add(this.route.params.subscribe(
+      params => this.refreshContextData(params['context']),
+        error => this.notificationService.notifyError(
+          this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-scope'), error)
+    ));
     // node changed from the navigation tree
-    this.navigatorService.selectedNodeChanged
-        .subscribe(
-          node => this.refreshDetailData(node),
-          error => this.notificationService.notifyError(
-            this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-data'), error));
+    this.sub.add(this.navigatorService.onSelectionChanged().pipe(
+      skip(1), // skip first to avoid double loading, since path param also changes for sure on init.
+      tap(obj => this.handleSelectionChanged(obj)),
+      finalize(() => this.status = this.StatusNoDescriptiveData), 
+      catchError(error => of(this.notificationService.notifyError(
+        this.translateService.instant('details.mdm-detail-descriptive-data.err-cannot-load-data'), error)))
+    ).subscribe());
 
     // file changed
     this.fileService.onFileChanged().subscribe(link => this.handleFileChanged(link));
   }
 
+  ngOnDestroy() {
+    this.sub.unsubscribe();
+  }
+
+  private handleSelectionChanged(obj: Node | MDMItem) {
+    if (obj instanceof MDMItem) {
+      this.selectedNode = undefined;
+      this.status = this.StatusVirtualNodes;
+    } else {
+      this.refreshDetailData(obj);
+    }
+  }
+
   handleFileChanged(fileLinkContextWrapper: FileLinkContextWrapper) {
     if (this.treeNodes != undefined) {
       this.treeNodes[fileLinkContextWrapper.contextType]
@@ -116,20 +134,17 @@
   }
 
   setContext(context: string) {
-    let contextType: string;
-
     switch (context) {
       case 'uut':
-        contextType = 'UNITUNDERTEST';
+        this.contextType = 'UNITUNDERTEST';
         break;
       case 'te':
-        contextType = 'TESTEQUIPMENT';
+        this.contextType = 'TESTEQUIPMENT';
         break;
       case 'ts':
-        contextType = 'TESTSEQUENCE';
+        this.contextType = 'TESTSEQUENCE';
         break;
     }
-    this.contextType = contextType;
   }
 
   /**
@@ -139,20 +154,27 @@
    */
   refreshContextData(contextType: string) {
     // revert before changing the context
-    this.undoEditChanges(undefined, this.editMode);
+    this.reloadData();
     this.setContext(contextType);
     this.editMode = false;
   }
 
+  private reloadData() {
+    this.sub.add(this.navigatorService.onSelectionChanged().pipe(
+      take(1),
+      tap(object => this.handleSelectionChanged(object))
+    ).subscribe());
+  }
+
   /**
    * Listener method to change the node
    *
    * @param node
    */
   refreshDetailData(node: Node) {
+    this.status = this.StatusLoading;
     if (node != undefined) {
       this.selectedNode = node;
-      this.status = this.StatusLoading;
       this.editMode = false;
       this.contextComponents = undefined;
       this.treeNodes = undefined;
@@ -161,8 +183,6 @@
       } else {
         this.status = this.StatusNoDescriptiveData;
       }
-    } else {
-      this.status = this.StatusNoNodes;
     }
   }
 
@@ -258,8 +278,9 @@
       label: node.name,
       data: {
         'name': node.name,
-        'attribute': node,
-        'header': true
+        // 'attribute': node,
+        'header': true,
+        'mimeType': this.extractMimeType(node)
       },
       children: this.createTreeChildren(node, children),
       icon: this.getNodeClass(node),
@@ -274,19 +295,18 @@
    * @param contexts the complete contexts
    */
   createTreeChildren(node: Node, children: Node[]) {
-    let list = [];
-
-    for (let attribute of node.attributes) {
-      let tmp = <TreeNode>{
+    const list = node.attributes.filter(attr => attr.name !== 'MimeType' && attr.name !== 'Name' && attr.name !== 'Sortindex')
+    .map(attribute => {
+      return {
         data: {
           'name': attribute.name,
           'attribute': this.patchAttributeForDate(attribute),
-          'header': false
+          'header': false,
+          'mimeType': this.extractMimeType(node)
         },
         expanded: true
-      };
-      list.push(tmp);
-    }
+      } as TreeNode;
+    });
     
     let tplCompRel = node.relations.find(r => r.entityType === "TemplateComponent")
     if (tplCompRel && tplCompRel.ids.length > 0) {
@@ -308,6 +328,19 @@
     return nodes.filter(n => n.relations[0].parentId == tplCompId);
   }
 
+  private extractMimeType(node: Node) {
+    if (node != undefined) {
+      const attr = node.attributes.find(attr => attr.name === 'MimeType');
+      if (attr) {
+        if (typeof attr.value === 'string') {
+          return attr.value;
+        } else if (Array.isArray(attr.value) && attr.value.length > 0 && typeof attr.value[0] === 'string') {
+          return attr.value[0];
+        }
+      }
+    }
+  }
+
   /**
    * Method to create a tree structure from the flat context entity and attribute map
    *
@@ -449,7 +482,7 @@
     event.stopPropagation();
     this.editMode = false;
     this.isTreeTableExpanded = true;
-    this.undoEditChanges(this.contextType, true);
+    this.reloadData();
   }
 
   onSaveChanges(event: Event) {
@@ -460,24 +493,7 @@
     this.tmpTreeNodes = undefined;
   }
 
-  /**
-   * Method to revert table changes back to the original state
-   *
-   * @param type
-   */
-  undoEditChanges(type: string, editMode: boolean) {
-    this.refreshDetailData(this.navigatorService.getSelectedNode());
-    // if (this.contextComponents != undefined && editMode && this.tmpTreeNodes != undefined) {
-    //   if (type == undefined) {
-    //       type = this.contextType;
-    //   }
-    //   // contexts is the origin, so we revert this back as the tree attributes are just references
-    //   this.contextComponents[type] = this.tmpTreeNodes;
-    //   // revert back
-    //   this.treeNodes = this.mapComponents(this.contextComponents);
-    //   this.tmpTreeNodes = null;
-    // }
-  }
+
 
   /**
    * Get the updated nodes from the current context
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-view/mdm-detail-view.component.ts b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-view/mdm-detail-view.component.ts
index dc809fb..fb403bf 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-view/mdm-detail-view.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-view/mdm-detail-view.component.ts
@@ -15,12 +15,12 @@
 
 import { TranslateService } from '@ngx-translate/core';
 
-import { Node, Relation, Attribute } from '@navigator/node';
+import { Node } from '@navigator/node';
 import { NodeService } from '@navigator/node.service';
-import { BasketService } from '@basket/basket.service';
 import { NavigatorService } from '@navigator/navigator.service';
 import { MDMNotificationService } from '@core/mdm-notification.service';
-import { DetailViewService } from '../../services/detail-view.service';
+import { MDMItem } from '@core/mdm-item';
+import { filter, map } from 'rxjs/operators';
 
 @Component({
   selector: 'mdm-detail-view',
@@ -34,30 +34,46 @@
   subscription: any;
   shoppable = false;
 
-  public status = 'file-explorer.file-explorer-nav-card.status-no-node-selected';
+  private NO_NODE_STATUS = 'file-explorer.file-explorer-nav-card.status-no-node-selected';
+  public VIRTUAL_NODE_STATUS = 'file-explorer.file-explorer-nav-card.status-virtual-node-selected';
+  public status = this.NO_NODE_STATUS;
 
-  constructor(private basketService: BasketService,
-              private navigatorService: NavigatorService,
+  constructor(private navigatorService: NavigatorService,
               private notificationService: MDMNotificationService,
               private translateService: TranslateService,
-              private nodeService: NodeService,
-              private detailViewService: DetailViewService) { }
+              private nodeService: NodeService) { }
 
   ngOnInit() {
-    this.onSelectedNodeChange(this.navigatorService.getSelectedNode());
-    this.subscription = this.navigatorService.selectedNodeChanged.subscribe(
-          node => this.onSelectedNodeChange(node),
-          error => this.notificationService.notifyError(this.translateService.instant('details.mdm-detail-view.cannot-update-node'), error)
-        );
+    this.subscription = this.navigatorService.onSelectionChanged().subscribe(
+      node => this.handleNavigatorSelectionChanged(node),
+      error => this.notificationService.notifyError(this.translateService.instant('details.mdm-detail-view.cannot-update-node'), error)
+    );
   }
 
   ngOnDestroy() {
     this.subscription.unsubscribe();
   }
 
-  onSelectedNodeChange(node: Node) {
+  private handleNavigatorSelectionChanged(object: Node | MDMItem) {
+    this.resetState();
+    if (object instanceof Node) {
+      this.handleSelectedNodeChanged(object);
+    } else if (object instanceof MDMItem) {
+      this.status = this.VIRTUAL_NODE_STATUS;
+    } else {
+      this.status = this.NO_NODE_STATUS;
+    }
+  }
+
+  private resetState() {
+    this.selectedNode = undefined;
+    this.quantity = undefined;
+    this.unit = undefined;
+  }
+
+  private handleSelectedNodeChanged(node: Node) {
+    this.selectedNode = node;
     if (node !== undefined) {
-      this.selectedNode = node;
       if (node.type.toLowerCase() === 'channel') {
         this.loadQuantity(node);
         this.loadUnit(node);
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.html b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.html
index 51c70c9..9edc68a 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.html
@@ -19,7 +19,7 @@
   }
 </style>
 
-<nav class="navbar navbar-expand bg-light detail-nav ui-corner-all" *ngIf="isVisible()">
+<nav class="navbar navbar-expand bg-light detail-nav ui-corner-all" *ngIf="visible">
   <div class="container-fluid">
     <div class="collapse navbar-collapse" id="bs-mdm-detail-navbar">
       <ul class="nav navbar-nav mdm-link-list">
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.ts b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.ts
index 796c1b3..c0d84b2 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail/mdm-detail.component.ts
@@ -14,10 +14,12 @@
 
 
 import { Component, OnInit, OnDestroy } from '@angular/core';
-import { Router, ActivatedRoute } from '@angular/router';
+import { Router } from '@angular/router';
 
 import { Node } from '@navigator/node';
 import { NavigatorService } from '@navigator/navigator.service';
+import { Subscription } from 'rxjs';
+import { MDMItem } from '@core/mdm-item';
 
 @Component({
   selector: 'mdm-detail',
@@ -26,29 +28,26 @@
 })
 export class MDMDetailComponent implements OnInit, OnDestroy  {
 
-  visible = false;
-  subscription: any;
+  public visible = false;
+  private subscription = new Subscription();
   
-  constructor(private route: ActivatedRoute,
+  constructor(
     private router: Router,
     private navigatorService: NavigatorService) {
   }
 
   ngOnInit() {
-    this.refreshVisibility(this.navigatorService.getSelectedNode());
-    this.subscription = this.navigatorService.selectedNodeChanged.subscribe(
-      node => this.refreshVisibility(node)
-    );
+    this.subscription.add(this.navigatorService.onSelectionChanged().subscribe(node => this.refreshVisibility(node)));
   }
 
   ngOnDestroy() {
     this.subscription.unsubscribe();
   }
 
-  refreshVisibility(node: Node) {
+  refreshVisibility(object: Node | MDMItem) {
     this.visible = false;
-    if (node != undefined && node.type != undefined && (node.type.toLowerCase() === 'measurement'
-          || node.type.toLowerCase() === 'teststep' || node.type.toLowerCase() === 'test')) {
+    if (object != undefined && object.type != undefined && (object.type.toLowerCase() === 'measurement'
+          || object.type.toLowerCase() === 'teststep' || object.type.toLowerCase() === 'test')) {
        this.visible = true;
     }
 
@@ -58,9 +57,4 @@
       this.router.navigate(['/navigator/details/general']);
     }
   }
-
-  isVisible() {
-    return this.visible;
-  }
-
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.html b/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.html
index 8d1e294..7547ca6 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.html
@@ -12,7 +12,7 @@
  *
  ********************************************************************************-->
 
-<div *ngIf="!sensors">
+<div *ngIf="!(sensors?.length > 0)">
   <div class="alert alert-info" style="margin: 0;">
     <strong>{{ status | translate}}</strong>
   </div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.ts b/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.ts
index 0ce9774..4745b11 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/sensor/sensor.component.ts
@@ -11,80 +11,76 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************/
-import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute } from '@angular/router';
+import { Component, OnDestroy, OnInit } from '@angular/core';
 
 import { TranslateService } from '@ngx-translate/core';
 
 import { Node} from '@navigator/node';
 import { NavigatorService } from '@navigator/navigator.service';
-import { LocalizationService } from '@localization/localization.service';
 import { MDMNotificationService } from '@core/mdm-notification.service';
 import { TRANSLATE } from '@core/mdm-core.module';
 
 import { ContextService } from '../../services/context.service';
-import { Sensor, Context } from '../../model/details.model';
+import { Sensor } from '../../model/details.model';
+import { Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { MDMItem } from '@core/mdm-item';
 
 @Component({
   selector: 'sensors',
   templateUrl: 'sensor.component.html',
 })
-export class SensorComponent implements OnInit {
+export class SensorComponent implements OnInit, OnDestroy {
 
-  readonly StatusLoading = TRANSLATE('details.sensor.status-loading');
-  readonly StatusNoNodes = TRANSLATE('details.sensor.status-no-nodes-available');
-  readonly StatusNoDescriptiveData = TRANSLATE('details.sensor.status-no-descriptive-data-available');
+  private readonly StatusLoading = TRANSLATE('details.sensor.status-loading');
+  private readonly StatusNoNodes = TRANSLATE('details.sensor.status-no-nodes-available');
+  private readonly StatusVirtualNodes = TRANSLATE('details.sensor.status-virtual-node-selected');
+  private readonly StatusNoDescriptiveData = TRANSLATE('details.sensor.status-no-descriptive-data-available');
 
-  selectedNode: Node;
-  context: String;
+  public sensors: Sensor[];
+  public status: string;
 
-  _diff = false;
-  contexts: Context[];
-  sensors: Sensor[];
-  errorMessage: string;
-  status: string;
+  private sub = new Subscription();
 
-  uut = 'Prüfling';
-  ts = 'Testablauf';
-  te = 'Messgerät';
-  s = 'Sensoren';
-
-  constructor(private route: ActivatedRoute,
-    private localService: LocalizationService,
-              private _contextService: ContextService,
+  constructor(private _contextService: ContextService,
               private navigatorService: NavigatorService,
               private notificationService: MDMNotificationService,
               private translateService: TranslateService) {}
 
   ngOnInit() {
+    this.status = this.StatusNoNodes;
+
+    this.sub.add(this.navigatorService.onSelectionChanged().pipe(
+    ).subscribe(
+      node => this.handleNavigatorSelectionChanged(node),
+      error => this.notificationService.notifyError(this.translateService.instant('details.sensor.err-cannot-load-data'), error)
+    ));
+  }
+
+  ngOnDestroy(): void {
+    this.sub.unsubscribe();
+  }
+
+  private handleNavigatorSelectionChanged(object: Node | MDMItem) {
+    this.sensors = [];
+    if (object instanceof Node) {
+      this.loadContext(object);
+    } else if (object instanceof MDMItem) {
+      this.status = this.StatusVirtualNodes;
+    } else {
+      this.status = this.StatusNoNodes;
+    }
+  }
+
+  private loadContext(node: Node) {
     this.status = this.StatusLoading;
-    this.route.params
-        .subscribe(
-          params => this.setContext(params['context']),
-          error => this.notificationService.notifyError(this.translateService.instant('details.sensor.err-cannot-load-scope'), error)
-    );
-
-    this.navigatorService.selectedNodeChanged
-        .subscribe(node => this.loadContext(node),
-        error => this.notificationService.notifyError(this.translateService.instant('details.sensor.err-cannot-load-data'), error)
-    );
-  }
-
-  setContext(context: string) {
-    this.context = context;
-    this.loadContext(this.navigatorService.getSelectedNode());
-  }
-
-  loadContext(node: Node) {
     if (node != undefined) {
-      this.selectedNode = node;
-      this.contexts = undefined;
       if (node.name != undefined && (node.type.toLowerCase() === 'measurement' || node.type.toLowerCase() === 'teststep')) {
-        this.status = this.StatusLoading;
         this._contextService.getSensors(node).subscribe(
             sensors => this.sensors = <Sensor[]> sensors,
             error => this.notificationService.notifyError(
-              this.translateService.instant('details.sensor.err-cannot-load-descriptive-data'), error)
+              this.translateService.instant('details.sensor.err-cannot-load-descriptive-data'), error),
+            () => this.status = this.StatusNoDescriptiveData
           );
       } else {
         this.status = this.StatusNoDescriptiveData;
@@ -94,12 +90,8 @@
     }
   }
 
-  diffToggle() {
-    this._diff = !this._diff;
-  }
-
-  diff(attr1: string, attr2: string) {
-    if (attr1 !== attr2 && this._diff) {
+  public diff(attr1: string, attr2: string) {
+    if (attr1 !== attr2) {
       return 'danger';
     }
   }
diff --git a/nucleus/webclient/src/main/webapp/src/app/file-explorer/components/file-explorer-nav-card/file-explorer-nav-card.component.ts b/nucleus/webclient/src/main/webapp/src/app/file-explorer/components/file-explorer-nav-card/file-explorer-nav-card.component.ts
index f5838ed..80eac7d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/file-explorer/components/file-explorer-nav-card/file-explorer-nav-card.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/file-explorer/components/file-explorer-nav-card/file-explorer-nav-card.component.ts
@@ -13,13 +13,12 @@
  ********************************************************************************/
 
 import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute } from '@angular/router';
+import { MDMItem } from '@core/mdm-item';
 
 import { MDMNotificationService } from '@core/mdm-notification.service';
-import { MDMItem } from '@core/mdm-item';
 import { NavigatorService } from '@navigator/navigator.service';
 import { Node, Attribute } from '@navigator/node';
-import { NodeService } from '@navigator/node.service';
+import { filter } from 'rxjs/operators';
 
 import { FilesAttachableService } from '../../services/files-attachable.service';
 
@@ -40,24 +39,17 @@
 
   public status = 'file-explorer.file-explorer-nav-card.status-no-node-selected';
 
-  public constructor(
-              private route: ActivatedRoute,
-              private navigatorService: NavigatorService,
-              private nodeService: NodeService,
-              private notificationService: MDMNotificationService) {}
+  public constructor(private navigatorService: NavigatorService,
+                     private notificationService: MDMNotificationService) {}
 
   public ngOnInit() {
-    // init data on navigation via modules
-    this.route.url.subscribe(() => {
-      this.selectedNode = this.navigatorService.getSelectedNode();
-      // reload selected node to reflect fileLink changes from delete and create
-      this.reloadSelectedNode();
-    });
     // init data on selected node change via navigator
-    this.navigatorService.selectedNodeChanged
-        .subscribe(
-          node => this.init(node),
-          error => this.notificationService.notifyError('Daten können nicht geladen werden.', error));
+    this.navigatorService.onSelectionChanged().pipe(
+      filter((obj: Node | MDMItem): obj is Node => obj instanceof Node)
+    ).subscribe(
+      node => this.init(node),
+      error => this.notificationService.notifyError('Daten können nicht geladen werden.', error)
+    );
   }
 
   // initialize component data
@@ -67,17 +59,6 @@
       this.status = 'file-explorer.file-explorer-nav-card.status-node-is-no-files-attachable';
       this.filesAttachable = this.isFileAttachable(this.selectedNode);
       this.linkAttr = this.findLinkAttribute();
-      // if (linkAttr != undefined && linkAttr.value != undefined) {
-      //   this.links = linkAttr.value as MDMLink[];
-      // }
-    }
-  }
-
-  // reloads the selected node (and consequently all file data)
-  private reloadSelectedNode() {
-    if (this.selectedNode != undefined) {
-      this.nodeService.getNodeFromItem(new MDMItem(this.selectedNode.sourceName, this.selectedNode.type, this.selectedNode.id))
-        .subscribe(node => this.init(node));
     }
   }
 
diff --git a/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.spec.ts b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.spec.ts
new file mode 100644
index 0000000..8f4ffe6
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.spec.ts
@@ -0,0 +1,12 @@
+// import { TestBed } from '@angular/core/testing';
+
+// import { MdmLocalizationService } from './mdm-localization.service';
+
+// describe('MdmLocalizationService', () => {
+//   beforeEach(() => TestBed.configureTestingModule({}));
+
+//   it('should be created', () => {
+//     const service: MdmLocalizationService = TestBed.get(MdmLocalizationService);
+//     expect(service).toBeTruthy();
+//   });
+// });
diff --git a/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.ts b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.ts
new file mode 100644
index 0000000..77d0707
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-localization.service.ts
@@ -0,0 +1,145 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+import { TranslateService } from '@ngx-translate/core';
+import { Observable, ReplaySubject, Subject } from 'rxjs';
+import { catchError, filter, flatMap, map, shareReplay, tap } from 'rxjs/operators';
+
+import { HttpErrorHandler } from '@core/http-error-handler';
+import { PropertyService } from '@core/property.service';
+import { NodeService } from '@navigator/node.service';
+import { Node } from '@navigator/node';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MdmLocalizationService {
+  private contextUrl: string;
+
+  private publisher = new ReplaySubject<Object>();
+  // cache for translationMaps, maped to source name
+  private replayer: Observable<{[key: string]: Observable<Object>}>;
+  private defaultMimeTypes: Observable<{ [sourceName: string]: Observable<{[key: string]: string}> }>;
+    
+  // cache for index of language per environment
+  private languageIndices: {[env: string]: {[langCode: string]: number}} = {};
+
+  private lastEnvironment: string;
+
+  constructor(private http: HttpClient,
+    private httpErrorHandler: HttpErrorHandler,
+    private prop: PropertyService,
+    private translateService: TranslateService,
+    private nodeService: NodeService) {
+      // initialize translations for each present environment
+      this.replayer = this.nodeService.getNodes().pipe(
+        tap(envs => this.initTranslationIndices(envs)),
+        map(envs => envs.reduce((previous, current) => {
+          previous[current.sourceName] = this.loadTranslationMap(current.sourceName);
+          return previous;
+        }, {})),
+        shareReplay(1)
+      );
+      
+      this.contextUrl = this.prop.getUrl('mdm/environments');
+
+      this.defaultMimeTypes = this.nodeService.getNodes().pipe(
+        map(envs => envs.reduce((previous, current) => {
+          previous[current.sourceName] = this.http.get(this.contextUrl + '/' + current.sourceName + '/mdmlocalizations/mimetypes').pipe(shareReplay(1)) as Observable<{[key: string]: string}>;
+          return previous;
+        }, {} as { [sourceName: string]: Observable<{ [type: string]: string }>})),
+        shareReplay(1)
+      ); 
+      // listener for language change on ngx-translate.
+      this.translateService.onLangChange.subscribe(() => this.emitTranslations());
+  }
+
+  /**
+   * Reloads translation map if none is present or present translations refer to other source.
+   * Returns an Observable holding the current translations.
+   * @param environment 
+   */
+  public readTranslations(environment?: string) {
+    if (environment != undefined && environment.length > 0) {
+      this.lastEnvironment = environment;
+    }
+    this.emitTranslations();
+    return this.publisher.asObservable();
+  }
+
+  public getLanguageIndex(sourceName?: string) {
+    let src = sourceName || this.lastEnvironment;
+    if (src != undefined && src.length > 0 && this.languageIndices[src]) {
+      return this.languageIndices[src][this.translateService.currentLang]
+        || this.languageIndices[src][this.translateService.defaultLang]
+    }
+  }
+
+  public getLanguages() {
+    return this.nodeService.getNodes().pipe(
+      map(envs => Array.from(new Set(envs.map(env => this.extractLanguageCodes(env))
+        .filter(codes => codes != undefined)
+        .reduce((prev, current) => prev.concat(current), [])))
+      )
+    )
+  }
+
+  public getDefaultMimeType(type: string, sourceName?: string): Observable<string> {
+    return this.defaultMimeTypes.pipe(
+      flatMap(mimeTypesByEnv => sourceName ? mimeTypesByEnv[sourceName] : mimeTypesByEnv[Object.keys(mimeTypesByEnv)[0]]),
+      map(m => m[type] || type),
+    );
+  }
+
+  /**
+   * Last translationMap is sent again. This triggers changeDetection for Observable in async pipe.
+   * Hence the mdmTranslation pipe does not need to be impure and still reacts on language changes via ngx.
+   */
+  private emitTranslations() {
+    if (this.replayer != undefined && this.lastEnvironment != undefined) {
+      this.replayer.pipe(
+        flatMap(replay => replay[this.lastEnvironment]),
+        tap(translations => this.publisher.next(translations))
+      ).subscribe();
+    } else {
+      this.publisher.next({});
+    }
+  }
+
+  /**
+   * Dispatches network request for translation map and caches response-observable in the replayer-map.
+   * @param environment
+   */
+  private loadTranslationMap(environment: string) {
+    const path = this.contextUrl + '/' + environment + '/mdmlocalizations';
+    return this.http.get(path, { headers: { 'Accept': 'application/json+packed' }}).pipe(
+      catchError(this.httpErrorHandler.handleError),
+      shareReplay(1));
+  }
+
+  private initTranslationIndices(environments: Node[]) {
+    environments.forEach(env => this.languageIndices[env.sourceName] =
+      this.extractLanguageCodes(env).reduce((previous, current, index) => {previous[current] = index; return previous}, {}));
+  }
+
+  private extractLanguageCodes(environment: Node) {
+    const aliases = environment.attributes.find(attr => attr.name === 'MeaningOfAliases');
+    if (aliases && typeof aliases.value === 'string') {
+      return aliases.value.split(';').map(frag => frag.replace(',language=',''));
+    }
+    return [];
+  }
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/localization/mdm-translate.pipe.ts b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-translate.pipe.ts
new file mode 100644
index 0000000..a3e7a6d
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/localization/mdm-translate.pipe.ts
@@ -0,0 +1,155 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { combineLatest, forkJoin } from 'rxjs';
+import { of } from 'rxjs';
+import { map, startWith, tap } from 'rxjs/operators';
+import { MdmLocalizationService } from './mdm-localization.service';
+
+@Pipe({
+  name: 'mdmTranslate'
+})
+export class MdmTranslatePipe implements PipeTransform {
+
+  private static readonly ATTRIBUTE_KEY = 'attr';
+  private static readonly VALUE_KEY = 'val';
+  private static readonly MIME_TYPE_KEY = 'mt';
+
+  constructor(private localizationService: MdmLocalizationService) {
+  }
+
+  transform(mimeTypeOrType: string, sourceName?: string, attributeName?: string, valueName?: string) {
+    const defaultResponse = attributeName || mimeTypeOrType;
+    if (mimeTypeOrType == undefined) {
+      return of(defaultResponse);
+    } else {
+      return combineLatest(
+        this.localizationService.readTranslations(sourceName), 
+        this.localizationService.getDefaultMimeType(mimeTypeOrType))
+        .pipe(
+          map(([translationMap, mimeType]) => this.lookUpTranslations(mimeType, attributeName, valueName, translationMap)),
+          map(translations => this.getCurrent(translations, sourceName) || defaultResponse)
+        );
+    }
+  }
+
+  /**
+   * Should select the translation from array by currently selected language in ngx-translate service ( or app component)
+   * @param translations
+   */
+  private getCurrent(translations: string[], sourceName?: string) {
+    const index = this.localizationService.getLanguageIndex(sourceName);
+    if (index != undefined) {
+      return translations[index];
+    }
+  }
+
+  /**
+   * Returns translation array of all languages in given Translation map.
+   *
+   * @param mimeType        mimetype
+   * @param attributeName   optional: attribute name
+   * @param valueName       optional: value name
+   * @param translationMap  hierachiecal map with all translations
+   */
+  private lookUpTranslations(mimeType: string, attributeName: string, valueName: string, translationMap: any) {
+    let result: string[];
+    if (translationMap != undefined) {
+      if (attributeName != undefined) {
+        const attrName = attributeName.toLocaleLowerCase();
+        if (valueName != undefined) {
+          /**
+           * case: attributeName and valueName are defined, key is supposed to be of the form:
+           * val/{mimetype}/{valueName}/{attributeName}
+           * eg: "val/application%2Fx-asam.aoany.cattestequipmentattr/label/blocksize"
+           */
+          result = this.lookUpValue(valueName, translationMap, mimeType, attrName);
+        } else {
+          /**
+           * case: attributeName is defined, but valueName is not defined, key is supposed to be of the form:
+           * attr/{valueName}/{attributeName}
+           * eg: "attr/application%2Fx-asam.aotestsequencepart.to_start"
+           */
+          result = this.lookUpAttribute(translationMap, mimeType, attrName);
+        }
+      } else {
+        /**
+         * case: Neither attributeName nor valueName is defined, key is supposed to be of the form:
+         * mt/{mimetype}
+         * eg: "mt/application%2Fx-asam.aoany.cattestequipmentattr"
+         */
+        result = this.lookUpMimeType(translationMap, mimeType);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Returns mimeType translation array for given mimeType.
+   * Tries to find exact match on mimeType, if none found tries to find matching super-mimetype.
+   * @param translationMap looks up 
+   * @param mimeType 
+   */
+  private lookUpMimeType(translationMap: any, mimeType: string) {
+    const splitIndex = mimeType.lastIndexOf('.');
+    let result: string[] = [];
+    if (translationMap.hasOwnProperty(MdmTranslatePipe.MIME_TYPE_KEY)
+      && translationMap[MdmTranslatePipe.MIME_TYPE_KEY].hasOwnProperty(mimeType)) {
+      result = translationMap[MdmTranslatePipe.MIME_TYPE_KEY][mimeType];
+    } else if (splitIndex > -1) {
+      result = this.lookUpMimeType(translationMap, mimeType.substring(0, splitIndex));
+    }
+    return result;
+  }
+
+  /**
+   * Returns attribute translation array for given mimeType.
+   * Tries to find exact match on mimeType, if none found tries to find matching super-mimetype.
+   * @param translationMap 
+   * @param mimeType 
+   * @param attrName 
+   */
+  private lookUpAttribute(translationMap: any, mimeType: string, attrName: string) {
+    const splitIndex = mimeType.lastIndexOf('.');
+    let result: string[] = [];
+    if (translationMap.hasOwnProperty(MdmTranslatePipe.ATTRIBUTE_KEY)
+      && translationMap[MdmTranslatePipe.ATTRIBUTE_KEY].hasOwnProperty(mimeType)
+      && translationMap[MdmTranslatePipe.ATTRIBUTE_KEY][mimeType].hasOwnProperty(attrName)) {
+      result = translationMap[MdmTranslatePipe.ATTRIBUTE_KEY][mimeType][attrName];
+    } else if (splitIndex > -1) {
+      result = this.lookUpAttribute(translationMap, mimeType.substring(0, splitIndex), attrName);
+    }
+    return result;
+  }
+
+  /**
+   * Returns value translation array for exact given mimeType.
+   * @param valueName 
+   * @param translationMap 
+   * @param mimeType 
+   * @param attrName 
+   */
+  private lookUpValue(valueName: string, translationMap: any, mimeType: string, attrName: string) {
+    let result: string[] = [];
+    const valName = valueName.toLocaleLowerCase();
+    if (translationMap.hasOwnProperty(MdmTranslatePipe.VALUE_KEY)
+      && translationMap[MdmTranslatePipe.VALUE_KEY].hasOwnProperty(mimeType)
+      && translationMap[MdmTranslatePipe.VALUE_KEY][mimeType].hasOwnProperty(valName)
+      && translationMap[MdmTranslatePipe.VALUE_KEY][mimeType][valName].hasOwnProperty(attrName)) {
+      result = translationMap[MdmTranslatePipe.VALUE_KEY][mimeType][valName][attrName];
+    }
+    return result;
+  }
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/localization/mdmdatasourcetranslation.pipe.ts b/nucleus/webclient/src/main/webapp/src/app/localization/mdmdatasourcetranslation.pipe.ts
index 22e5b2b..732c9de 100644
--- a/nucleus/webclient/src/main/webapp/src/app/localization/mdmdatasourcetranslation.pipe.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/localization/mdmdatasourcetranslation.pipe.ts
@@ -34,7 +34,7 @@
   private getTrans(type: string, attr: string) {
     this.localService.getLocalizations()
                      .map(locs => this.getTranslation(locs, type, attr))
-                     .subscribe(t => this.translation = t);
+                     .subscribe(t => this.translation = t, error => this.translation = type);
     return this.translation;
   }
 
diff --git a/nucleus/webclient/src/main/webapp/src/app/localization/mime-type.pipe.ts b/nucleus/webclient/src/main/webapp/src/app/localization/mime-type.pipe.ts
new file mode 100644
index 0000000..01c9986
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/localization/mime-type.pipe.ts
@@ -0,0 +1,32 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { Attribute, Node } from '@navigator/node';
+
+@Pipe({
+  name: 'mimeType'
+})
+export class MimeTypePipe implements PipeTransform {
+
+  transform(node: {'attributes': Attribute[]}) {
+    if (node != undefined) {
+      const attr = node.attributes.find(attr => attr.name === 'MimeType');
+      if (attr) {
+        return attr.value as string;
+      }
+    }
+  }
+
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html b/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
index 0215a8c..e7e37ab 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.html
@@ -22,13 +22,13 @@
             <ul class="nav navbar-nav navbar-right">
               <li [ngClass]="isDropActive('Dropdown')" title="{{ 'navigator-view.mdm-navigator-view.select-node-provider' | translate }}" dropdown>
                 <a (click)="activate('Dropdown')" class="dropdown-toggle" dropdownToggle aria-haspopup="true" aria-expanded="false" style="cursor:pointer;">
-                  {{activeNodeprovider.name}}
+                  {{activeNodeprovider}}
                   <em class="caret"></em>
                 </a>
                 <ul class="dropdown-menu" *dropdownMenu>
-                  <li *ngFor="let np of getNodeproviders()">
+                  <li *ngFor="let np of nodeProviders">
                     <a class="dropdown-item" (click)="activateNodeProvider(np)" style="cursor:pointer;">
-                      {{np.name}}
+                      {{np}}
                     </a>
                   </li>
                 </ul>
@@ -39,6 +39,7 @@
         <mdm-navigator></mdm-navigator>
       </nav>
     </div>
+    
     <div id="rightsidenav" class="split">
       <div class="navigator-content" (scroll)=onScroll($event)>
         <router-outlet></router-outlet>
@@ -48,4 +49,5 @@
         </div>
       </div>
     </div>
+   
   </div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts b/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
index 10f3dc3..c05106d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
@@ -25,6 +25,8 @@
 
 import { TranslateService } from '@ngx-translate/core';
 import Split from 'split.js';
+import { Observable } from 'rxjs';
+import { NavigatorService } from '@navigator/navigator.service';
 
 @Component({
   selector: 'mdm-navigator-view',
@@ -38,8 +40,8 @@
   selectedNode = new Node;
   activeNode: Node;
   closeOther = false;
-
-  activeNodeprovider: any;
+  nodeProviders: string[];
+  activeNodeprovider: string;
   _comp = 'Navigation';
   subscription: any;
 
@@ -50,7 +52,8 @@
 
   constructor(private nodeProviderService: NodeproviderService,
               private notificationService: MDMNotificationService,
-              private translateService: TranslateService) {}
+              private translateService: TranslateService,
+              private navigatorService: NavigatorService) {}
 
   onScrollTop() {
     this.div.scrollTop = 0;
@@ -73,6 +76,7 @@
   }
   activateNodeProvider(nodeprovider: any) {
     this.nodeProviderService.setActiveNodeprovider(nodeprovider);
+    this.navigatorService.fireSelectedItem(undefined);
   }
 
   getNodeproviders() {
@@ -80,14 +84,14 @@
   }
 
   ngOnInit() {
-    this.activeNodeprovider = this.nodeProviderService.getActiveNodeprovider();
-    this.subscription = this.nodeProviderService.nodeProviderChanged
-        .subscribe(
-          np => this.activeNodeprovider = np,
-          error => this.notificationService.notifyError(
-            this.translateService.instant('navigator-view.mdm-navigator-view.err-cannot-update-node-provider'), error)
-    );
+    this.nodeProviderService.getNodeproviders().subscribe(nps => this.nodeProviders = nps)
+    this.nodeProviderService.getActiveNodeprovider().subscribe(np => this.activeNodeprovider = np);
 
+    this.subscription = this.nodeProviderService.nodeProviderChanged.subscribe(
+      np => this.activeNodeprovider = np,
+      error => this.notificationService.notifyError(
+        this.translateService.instant('navigator-view.mdm-navigator-view.err-cannot-update-node-provider'), error)
+    );
   }
 
   ngAfterViewInit(): void {
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
index 4c306b6..edca911 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.html
@@ -20,9 +20,8 @@
     selectionMode="multiple"
     [(selection)]="selectedNodes"
     (onNodeExpand)="onNodeExpand($event)"
+    (onNodeCollapse)="onNodeCollapse($event)"
     (onNodeSelect)="onNodeSelect($event)"
-    (onNodeExpand)="saveTreeInLocalStorage($event)"
-    (onNodeCollapse)="saveTreeInLocalStorage($event)"
     [contextMenu]="cm"
     >
   <ng-template let-node pTemplate="default">
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 0e7bedd..e82cae8 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
@@ -19,10 +19,9 @@
 import { map, tap, startWith, filter, flatMap } from 'rxjs/operators';
 
 import { MDMItem } from '../core/mdm-item';
-import { Node } from '../navigator/node';
 import { NodeService } from '../navigator/node.service';
 import { QueryService } from '../tableview/query.service';
-import { NodeproviderService } from './nodeprovider.service';
+import { NodeproviderService, MDMTreeNode } from './nodeprovider.service';
 import { NavigatorService } from './navigator.service';
 import { BasketService } from '../basket/basket.service';
 
@@ -31,7 +30,8 @@
 import { TranslateService } from '@ngx-translate/core';
 import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
 import { ActivatedRoute, Router } from '@angular/router';
-import { forkJoin, combineLatest } from 'rxjs';
+import { combineLatest, Observable, Subscription } from 'rxjs';
+import { PreferenceService } from '@core/preference.service';
 
 @Component({
   selector: 'mdm-navigator',
@@ -48,7 +48,9 @@
 
   selectedNodes: TreeNode[] = [];
   nodes: TreeNode[] = [];
+  restoreTreeState: Observable<boolean>;
   lastClickTime = 0;
+  nodeProviderSubscription: Subscription;
 
   loadingNode = <TreeNode>{
     label: 'Loading subordinate items...',
@@ -70,6 +72,7 @@
     private navigatorService: NavigatorService,
     private notificationService: MDMNotificationService,
     private translateService: TranslateService,
+    private preferenceService: PreferenceService,
     private router: Router,
     private route: ActivatedRoute) {
   }
@@ -86,11 +89,15 @@
       (msg: string) => this.AlrtItemTypeNotSupported = msg);
 
 
+    this.restoreTreeState = this.preferenceService.getPreference("navigator.restoreTreeState").pipe(
+        map(prefs => prefs.map(p => p.value).some(p => p === 'true'))
+    );
+
     // make sure navigate parameter is handled after nodes are loaded
     this.loadRootNodes().subscribe(
       treeNodes => {
         this.nodes = treeNodes;
-        this.restoreLastTreeState();
+        this.restoreLastNavigatorState();
         this.loadNavigateItems().subscribe(selectItems => {
           this.navigatorService.fireOnOpenInTree(selectItems);
         });
@@ -99,11 +106,7 @@
         this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
     );
 
-    this.nodeproviderService.nodeProviderChanged.subscribe(
-      np => this.reloadTree(),
-      error => this.notificationService.notifyError(
-        this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
-    );
+    this.subscribeToNodeProviderChanges();
 
     this.navigatorService.onOpenInTree
       .subscribe(
@@ -114,36 +117,47 @@
 
   }
 
-  restoreLastTreeState() {
-    const rootNodesOfTreeBefore = this.getStoredRootNodes();
+  subscribeToNodeProviderChanges() {
+    this.nodeProviderSubscription = this.nodeproviderService.nodeProviderChanged.subscribe(
+      np =>  this.reloadTree(),
+      error => this.notificationService.notifyError(
+        this.translateService.instant('navigator.mdm-navigator.err-cannot-update-navigation-tree'), error)
+    );
+  }
+  
+  restoreLastNavigatorState() {
+    this.restoreTreeState.subscribe(restoreState => {
+      if (restoreState) {
+        const navigatorState = this.getStoredNavigatorState();
 
-    if (rootNodesOfTreeBefore != null) {
-      this.restoreTreeNodes(this.nodes, rootNodesOfTreeBefore)
+        if (navigatorState != null) {
+          this.nodeProviderSubscription.unsubscribe();
+          this.nodeproviderService.setActiveNodeprovider(navigatorState.nodeprovider_id);
+          this.loadRootNodes().subscribe(rootNodes => {
+            this.nodes = rootNodes;
+            this.restoreTreeNodes(this.nodes, navigatorState.nodes);
+            this.subscribeToNodeProviderChanges();
+          });
+        }
+      }
+    });
+  }
+
+  restoreTreeNodes(treeNodes: TreeNode[], labelsToExpand: Map<string, any>) {
+    for (let nodeToRestore of Array.from(labelsToExpand.entries())) {
+      const nodeToExpand = this.findTreeNodeByLabel(treeNodes, nodeToRestore[0]);
+      this.loadNodeItemAndExpand(nodeToExpand, nodeToRestore[1]);
     }
   }
 
-  restoreTreeNodes(nodesToRestore: TreeNode[], lastNodes: TreeNode[]) {
-    for (let nodeToRestore of nodesToRestore) {
-      const lastNode = this.getTreeNode(lastNodes, nodeToRestore.data.source, nodeToRestore.data.type, nodeToRestore.data.id);
-      this.loadNodeItemAndExpand(nodeToRestore, lastNode);
-    }
-  }
-
-  loadNodeItemAndExpand(nodeOfCurrentTree, nodeOfTreeBefore) {
-    if (nodeOfTreeBefore != null && !nodeOfCurrentTree.leaf && nodeOfTreeBefore.expanded) {
-      return this.getChildren(nodeOfCurrentTree).subscribe(
+  loadNodeItemAndExpand(nodeToExpand: TreeNode, labelsToExpand: Map<string, any>) {
+    if (nodeToExpand) {
+      return this.getChildren(nodeToExpand.data).subscribe(
         nodes => {
-          nodeOfCurrentTree.children = nodes;
+          nodeToExpand.children = nodes;
+          nodeToExpand.expanded = true;
 
-          const lastChilds = nodeOfTreeBefore.children;
-          nodeOfCurrentTree.expanded = true;
-
-          for (const child of nodeOfCurrentTree.children) {
-            const lastNode = this.getTreeNode(lastChilds, child.data.source, child.data.type, child.data.id);
-            this.loadNodeItemAndExpand(child, lastNode);
-          }
-
-
+          this.restoreTreeNodes(nodeToExpand.children, labelsToExpand);
         },
         error => this.notificationService.notifyError(
           this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
@@ -151,24 +165,14 @@
     }
   }
 
-  getTreeNode(nodes: TreeNode[], source: string, type: string, id: string) {
-    let returnVal = null;
+  findTreeNodeByLabel(nodes: TreeNode[], nodeLabel: string) {
     for (const node of nodes) {
       const data = node.data;
-      if (data != null && data.source === source && data.type === type && data.id === id) {
-        returnVal = node;
-        break;
+      if (data != null && data.label === nodeLabel) {
+        return node;
       }
     }
-
-    return returnVal;
-  }
-
-  loadRootNodes() {
-    return this.nodeService.getRootNodes()
-      .pipe(
-        map(n => n.map(node => this.mapNode(node)))
-      );
+    return null;
   }
 
   loadNavigateItems() {
@@ -181,6 +185,25 @@
       );
   }
 
+
+  toMDMItem(node: TreeNode | MDMTreeNode) {
+    if ("source" in node) {
+      let i = <MDMTreeNode> node;
+      if (i.id === null || i.id === 'NO_ID') {
+        return null;
+      } else {
+        return new MDMItem(i.source, i.type, i.id, i.serial, i.filter)
+      }
+    } else {
+      let i = <MDMTreeNode> node.data;
+      if (i.id === null || i.id === 'NO_ID') {
+        return null;
+      } else {
+        return new MDMItem(i.source, i.type, i.id, i.serial, i.filter)
+      }
+    }
+  }
+
   /**
    * Copies the URL of the currently selected node to the clipboard.
    * If multiple nodes are selected only the first node is used.
@@ -211,11 +234,14 @@
 
   onNodeSelect(event) {
     if (event.node.data) {
-      this.navigatorService.setSelectedItem(event.node.data);
+      this.navigatorService.fireSelectedItem(this.toMDMItem(<TreeNode> event.node));
     }
+
     if (event.originalEvent.timeStamp - this.lastClickTime < 300) {
       if (!event.node.expanded && !event.node.children) {
-        this.loadNode(event.node as TreeNode);
+        this.onNodeExpand(event);
+      } else {
+        this.onNodeCollapse(event);
       }
       event.node.expanded = !event.node.expanded;
     }
@@ -224,29 +250,37 @@
 
   onNodeExpand(event) {
     if (event.node) {
-      this.loadNode(event.node as TreeNode);
+      this.loadChildren(event.node as TreeNode);
+      this.saveNavigatorStateInLocalStorage();
     }
   }
 
-  loadNode(node: TreeNode) {
-    if (node && node.children == undefined) {
-      return this.loadNodeItem(node);
+  onNodeCollapse(event) {
+    if (event.node) {
+      this.unloadChildren(event.node as TreeNode);
+      this.saveNavigatorStateInLocalStorage();
     }
   }
 
-  loadNodeItem(node: TreeNode) {
-    if (!node.leaf) {
-      return this.getChildren(node).pipe(
+  loadChildren(parentNode: TreeNode) {
+    if (parentNode && parentNode.children == undefined && !parentNode.leaf) {
+      return this.getChildren(parentNode.data as MDMTreeNode).pipe(
         startWith([this.loadingNode]),
-        tap(nodes => (nodes && nodes.length === 0) ? node.leaf = true : node.leaf = false))
+        tap(nodes => (nodes && nodes.length === 0) ? parentNode.leaf = true : parentNode.leaf = false))
         .subscribe(
-          nodes => node.children = nodes,
+          nodes => parentNode.children = nodes,
           error => this.notificationService.notifyError(
             this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
         );
     }
   }
 
+  unloadChildren(parentNode: TreeNode) {
+    if (parentNode) {
+      parentNode.children = undefined;
+    }
+  }
+
   reloadTree() {
     this.loadRootNodes()
       .subscribe(
@@ -257,28 +291,34 @@
   }
 
   onFocus(event: { eventName: string, node: TreeNode }) {
-    this.navigatorService.setSelectedItem(event.node.data.item);
+    this.navigatorService.fireSelectedItem(event.node.data.item);
   }
 
-  getNodeClass(item: MDMItem) {
+  getNodeClass(item: MDMTreeNode) {
     return 'icon ' + item.type.toLowerCase();
   }
 
-  mapNode(node: Node) {
-    let item = new MDMItem(node.sourceName, node.type, node.id);
-    return <TreeNode>{
-      label: node.name,
-      leaf: this.nodeproviderService.getSubNodeprovider(item) == undefined,
-      data: item,
-      icon: this.getNodeClass(item)
-    };
+  loadRootNodes() {
+    return this.nodeproviderService.getRoots().pipe(
+      map(treeNodes => treeNodes.map(treeNode => this.mapNode(treeNode)))
+    );
   }
 
-  getChildren(node: TreeNode) {
-    return this.nodeService.getNodesByUrl(this.nodeproviderService.getQueryForChildren(node.data)).pipe(
+  getChildren(node: MDMTreeNode) {
+    return this.nodeproviderService.getChildren(node).pipe(
       map(nodes => nodes.map(n => this.mapNode(n))),
       map(treenodes => treenodes.sort((n1, n2) => n1.label.localeCompare(n2.label))));
   }
+  
+  mapNode(node: MDMTreeNode) {
+    //let item = new MDMItem(node.source, node.type, node.id, node.serial);
+    return <TreeNode>{
+      label: node.label,
+      leaf: node.leaf === true, //this.nodeproviderService.getSubNodeprovider(item) == undefined,
+      data: node,
+      icon: this.getNodeClass(node)
+    };
+  }
 
   addSelectionToBasket() {
     this.basketService.addAll(this.selectedNodes.map(node => <MDMItem>node.data));
@@ -286,94 +326,86 @@
 
   refresh() {
     for (const node of this.selectedNodes) {
-      this.loadNodeItem(node);
+      this.loadChildren(node);
     }
   }
 
   refreshAll() {
-    this.restoreLastTreeState();
+    this.restoreLastNavigatorState();
   }
 
-  saveTreeInLocalStorage(event) {
-    let jsonVal = JSON.stringify(this.nodes, this.replacer);
-    localStorage.setItem('storedRootNodes', jsonVal);
-  }
-
-  getStoredRootNodes() {
-    return JSON.parse(localStorage.getItem('storedRootNodes'));
-  }
-
-  replacer(key, value) {
-    if (key == 'parent') {
-      return undefined;
-    } else {
-      return value;
-    }
-
-  }
-
-  openInTree(items: MDMItem[]) {
-    this.selectedNodes = [];
-    items.forEach(item => {
-      let pathTypes = this.nodeproviderService.getPathTypes(item.type);
-      if (pathTypes.length === 0) {
-        alert(this.AlrtItemTypeNotSupported);
-      } else {
-        let env = this.nodes.find(e => item.source === e.data.source);
-        env.expanded = true;
-        this.openChildrenRecursive(item, env, pathTypes, 1);
+  saveNavigatorStateInLocalStorage() {
+    this.restoreTreeState.subscribe(restoreState => {
+      if (restoreState) {
+        this.nodeproviderService.getActiveNodeprovider().subscribe(np => {
+          let jsonVal = JSON.stringify({ nodeprovider_id: np, nodes: this.getOpenNodes(this.nodes) }, this.replacer);
+          localStorage.setItem('storedRootNodes', jsonVal);
+        });
       }
     });
   }
 
-  openChildrenRecursive(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
-    if (current.children) {
-      this.expander(item, current, pathTypes, iii);
+  getStoredNavigatorState() : { nodeprovider_id: string, nodes: Map<string, any> }{
+    return JSON.parse(localStorage.getItem('storedRootNodes'), this.reviver);
+  }
+
+  replacer(key, value) {
+    const originalObject = this[key];
+    if(originalObject instanceof Map) {
+      return {
+        dataType: 'Map',
+        value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
+      };
     } else {
-      this.getChildren(current).pipe(
-        startWith([this.loadingNode]),
-        tap(n => current.children = n))
-        .subscribe(
-          () => this.expander(item, current, pathTypes, iii),
-          error => this.notificationService.notifyError(
-            this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
-        );
+      return value;
     }
   }
 
-  expander(item: MDMItem, current: TreeNode, pathTypes: string[], iii: number) {
-    let expandList: string[] = [];
-    this.nodeService.searchNodes('filter=' + item.type + '.Id eq "' + item.id + '"',
-      item.source, pathTypes[iii])
-      .subscribe(
+  reviver(key, value) {
+    if(typeof value === 'object' && value !== null) {
+      if (value.dataType === 'Map') {
+        return new Map(value.value);
+      }
+    }
+    return value;
+  }
+
+  getOpenNodes(treeNodes: TreeNode[]) : Map<string, any> {
+    let m = new Map<string, any>();
+
+    treeNodes.filter(n => n.children).forEach(n => m.set(n.label, this.getOpenNodes(n.children)));
+    return m;
+  }
+
+  private openInTree(items: MDMItem[]) {
+    this.selectedNodes = [];
+    items.forEach(item => this.nodeproviderService.getTreePath(item).subscribe(treepath => this.expandTreePath(treepath)));
+  }
+
+  private expandTreePath(treePath: MDMTreeNode[]) {
+    this.expandChild(this.nodes, treePath);
+  }
+
+  private 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 => {
-          expandList = nodes.map(n => n.id);
-          current.children.filter(node => expandList.findIndex(
-            i => node.data ? i === node.data.id : false) > -1
-          )
-            .forEach(node => {
-              if (++iii < pathTypes.length) {
-                node.expanded = true;
-                this.openChildrenRecursive(item, node, pathTypes, iii);
-                this.scrollToSelectionPrimeNgDataTable(node);
-              } else {
-                this.selectedNodes.push(node);
-                let length = this.selectedNodes.length;
-                if (length === 1) {
-                  this.nodeService.getNodeFromItem(this.selectedNodes[length - 1].data)
-                    .subscribe(
-                      n => this.navigatorService.setSelectedNode(n),
-                      error => this.notificationService.notifyError(
-                        this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
-                    );
-                }
-                this.scrollToSelectionPrimeNgDataTable(node);
-              }
-            });
+          nodeToExpand.children = nodes;
+          nodeToExpand.expanded = true;
+          if (treePath.length > 0) {
+            this.expandChild(nodeToExpand.children, treePath);
+          } else {
+            this.selectedNodes = [ nodeToExpand ];
+            this.navigatorService.fireSelectedItem(this.toMDMItem(nodeToExpand));
+          }
         },
         error => this.notificationService.notifyError(
-          this.translateService.instant('navigator.mdm-navigator.err-cannot-open-node'), error)
+          this.translateService.instant('navigator.mdm-navigator.err-cannot-load-nodes'), error)
       );
+    }
   }
 
   /**
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/navigator.service.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/navigator.service.ts
index f6d0938..50ebc58 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/navigator.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/navigator.service.ts
@@ -14,54 +14,59 @@
 
 
 import {Injectable, Output, EventEmitter} from '@angular/core';
-import {Http, Response, Headers, RequestOptions} from '@angular/http';
-import { Subject } from 'rxjs';
+import { ReplaySubject } from 'rxjs';
+import { startWith, tap } from 'rxjs/operators';
 
 import {MDMItem} from '../core/mdm-item';
 import {Node} from './node';
-import {PropertyService} from '../core/property.service';
 import {NodeService} from './node.service';
 
-import {MDMNotificationService} from '../core/mdm-notification.service';
-
-import { TranslateService } from '@ngx-translate/core';
-
 @Injectable()
 export class NavigatorService {
 
   @Output() onOpenInTree = new EventEmitter<MDMItem[]>();
+  private selectedNodeSubject = new ReplaySubject<Node | MDMItem>(1);
 
-  public selectedNodeChanged: Subject<Node> = new Subject <Node>();
-  private selectedNode: Node;
-
-  constructor(private nodeService: NodeService,
-              private notificationService: MDMNotificationService,
-              private translateService: TranslateService) {
-
+  constructor(private nodeService: NodeService) {
   }
 
-  setSelectedNode(node: Node) {
-    this.selectedNode = node;
-    this.fireSelectedNodeChanged(node);
+  /**
+   * Returns an Observable to listen to navigator tree selection.
+   * On subscribe the Observable replays the last selection.
+   * If selected TreeNode is a virtual node the Observable contains a MDMItem describing the selection.
+   * If selected TreeNode is a real node the Observable contains a Node describing the selection.
+   */
+  public onSelectionChanged() {
+    return this.selectedNodeSubject.asObservable();
   }
 
-  fireSelectedNodeChanged(node: Node) {
-    this.selectedNodeChanged.next(node);
+  /**
+   * Emitter for navigator tree selection.
+   * @param obj 
+   * @deprecated FireSelectedItem should be favoured over this.
+   * Should be used in navigator service internally only.
+   * 
+   * @TODO It should be private for above reasons. For now it is still used in table service.
+   */
+  public fireSelectedNodeChanged(obj: Node | MDMItem) {
+    this.selectedNodeSubject.next(obj);
   }
 
-  setSelectedItem(item: MDMItem) {
-      this.nodeService.getNodeFromItem(item)
-        .subscribe(
-          node => this.setSelectedNode(node),
-          error => this.notificationService.notifyError(this.translateService.instant('navigator.navigator.err-cannot-set-item'), error)
-        );
+  /**
+   * Emitter for navigator tree selection. If item describing a real node it is converted into a Node.
+   * @param obj 
+   */
+  public fireSelectedItem(item: MDMItem) {
+    if (item != undefined && item.id != undefined) {
+      this.nodeService.getNodeFromItem(item).pipe(
+        tap(node => this.fireSelectedNodeChanged(node))
+      ).subscribe();
+    } else {
+      this.fireSelectedNodeChanged(item);
+    }
   }
 
-  getSelectedNode(): Node {
-    return this.selectedNode;
-  }
-
-  fireOnOpenInTree(items: MDMItem[]) {
+  public fireOnOpenInTree(items: MDMItem[]) {
     this.onOpenInTree.emit(items);
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/node.service.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/node.service.ts
index f95403a..6cf46b4 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/node.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/node.service.ts
@@ -12,16 +12,14 @@
  *
  ********************************************************************************/
 
-import {Injectable, EventEmitter} from '@angular/core';
-import {Http, Response, Headers, RequestOptions} from '@angular/http';
-import {Observable} from 'rxjs';
+import {Injectable} from '@angular/core';
+import {Http, Headers, RequestOptions} from '@angular/http';
+import {Observable, of} from 'rxjs';
 import {catchError, map} from 'rxjs/operators';
 
 import {MDMItem} from '../core/mdm-item';
 import {Node, NodeArray} from './node';
 import {PropertyService} from '../core/property.service';
-import {PreferenceService, Preference} from '../core/preference.service';
-import {QueryService, Query} from '../tableview/query.service';
 import {HttpErrorHandler} from '../core/http-error-handler';
 import {plainToClass} from 'class-transformer';
 
@@ -29,6 +27,8 @@
 export class NodeService {
 
   private _nodeUrl: string;
+  private _nodeProviderEndpoint: string;
+  private _defNodeProv = 'generic';
 
   static mapSourceNameToName(environments: Node[], sourceName: string) {
     let env = environments.find(n => n.sourceName === sourceName);
@@ -37,10 +37,8 @@
 
   constructor(private http: Http,
               private httpErrorHandler: HttpErrorHandler,
-              private _prop: PropertyService,
-              private queryService: QueryService,
-              private preferenceService: PreferenceService) {
-      this._nodeUrl = _prop.getUrl('mdm/environments');
+              private _prop: PropertyService) {
+    this._nodeUrl = this._prop.getUrl('mdm/environments');
   }
 
   searchNodes(query, env, type) {
@@ -97,8 +95,17 @@
       return this.getNode(this._nodeUrl + '/' + mdmItem.source).pipe(
         map(nodes => (nodes && nodes.length > 0) ? nodes[0] : undefined));
     } else {
-      return this.getNode(this._nodeUrl + '/' + mdmItem.source + '/' + this.typeToUrl(mdmItem.type) + '/' + mdmItem.id).pipe(
-        map(nodes => (nodes && nodes.length > 0) ? nodes[0] : undefined));
+      if (mdmItem.id != undefined) {
+        //non-virtual nodes
+        return this.getNode(this._nodeUrl + '/' + mdmItem.source + '/' + this.typeToUrl(mdmItem.type) + '/' + mdmItem.id).pipe(
+          map(nodes => (nodes && nodes.length > 0) ? nodes[0] : undefined));
+      } else {
+        //virtual node
+        const node = new Node();
+        node.type = mdmItem.type;
+        node.sourceName = mdmItem.source;
+        return of(undefined)
+      }
     }
   }
 
@@ -160,6 +167,10 @@
     return mdmItem.source + '/' + this.typeToUrl(mdmItem.type) + '/' + mdmItem.id;
   }
 
+  getNodesBySerial(url: string) {
+    return this.getNode(this._nodeProviderEndpoint + '/' + this._defNodeProv + '/' + url);
+  }
+
   getNodesByAbsoluteUrl(url: string) {
     return this.getNode(url);
   }
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/node.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/node.ts
index c6154aa..936a3e8 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/node.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/node.ts
@@ -80,6 +80,13 @@
   relations: Relation[];
   active: boolean;
 
+  /* Attributes by the new nodeprovider */
+  source: string;
+  label: string;
+  filter: string;
+  serial: string;
+  /* end */
+
   getClass() {
     switch (this.type) {
       case 'StructureLevel':
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.spec.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.spec.ts
index 3f0697c..c6a073e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.spec.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.spec.ts
@@ -60,7 +60,7 @@
         NodeService]
     });
   });
-
+/*
   it('getSubNodeprovider', inject([NodeproviderService], (nodeproviderService) => {
       let item = new MDMItem('MDMNVH', 'Project', 'id1');
       let query = nodeproviderService.getSubNodeprovider(item);
@@ -81,4 +81,5 @@
 
       expect(query).toEqual('/MDMNVH/pools?filter=Project.Id eq "id1"');
   }));
+  */
 });
diff --git a/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.ts
index 5ba4f4b..81909c8 100644
--- a/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/navigator/nodeprovider.service.ts
@@ -14,41 +14,65 @@
 
 
 import {Injectable, EventEmitter} from '@angular/core';
-import {Http} from '@angular/http';
-import {map} from 'rxjs/operators';
+import { HttpClient, HttpResponse, HttpHeaders } from '@angular/common/http';
+import {map, catchError, flatMap, tap} from 'rxjs/operators';
 
-import {MDMNotificationService} from '../core/mdm-notification.service';
-import {PreferenceService, Scope} from '../core/preference.service';
-import {QueryService} from '../tableview/query.service';
 import {PropertyService} from '../core/property.service';
-import {MDMItem} from '../core/mdm-item';
 
-import { TranslateService } from '@ngx-translate/core';
-import { TRANSLATE } from '../core/mdm-core.module';
+import { HttpErrorHandler } from '@core/http-error-handler';
+import { Observable, of } from 'rxjs';
+import { plainToClass } from 'class-transformer';
+import { MDMItem } from '@core/mdm-item';
+import { Http } from '@angular/http';
 
-declare function require(path: string): any;
-const defaultNodeProvider = require('./defaultnodeprovider.json');
+export class MDMTreeNode {
+  source: string;
+  type: string;
+  id: string;
+  label: string;
+  filter: string;
+  serial: string;
+  leaf: boolean;
+
+  getClass() {
+    switch (this.type) {
+      case 'StructureLevel':
+        return 'pool';
+      case 'MeaResult':
+        return 'measurement';
+      case 'SubMatrix':
+        return 'channelgroup';
+      case 'MeaQuantity':
+        return 'channel';
+      default:
+        return this.type.toLowerCase();
+    }
+  }
+}
 
 @Injectable()
 export class NodeproviderService {
 
-  public nodeProviderChanged: EventEmitter<any> = new EventEmitter<any>();
-  private nodeproviders = [defaultNodeProvider];
+  private _nodeProviderEndpoint: string;
 
-  private activeNodeprovider: any = this.nodeproviders[0];
+  public nodeProviderChanged: EventEmitter<string> = new EventEmitter<string>();
 
-  constructor(private http: Http,
-              private _prop: PropertyService,
-              private queryService: QueryService,
-              private preferenceService: PreferenceService,
-              private notificationService: MDMNotificationService,
-              private translateService: TranslateService) {
-      this.loadNodeproviders();
+  private nodeproviders : Observable<string[]>;
+  
+  private activeNodeprovider: Observable<string>;
+
+  constructor(private http: HttpClient,
+              private httpErrorHandler: HttpErrorHandler,
+              private _prop: PropertyService) {
+
+      this._nodeProviderEndpoint = _prop.getUrl('mdm/nodeprovider');
+      this.nodeproviders = this.loadNodeproviders();
+      this.activeNodeprovider = this.nodeproviders.pipe(map(n => n[0]));
   }
 
-  setActiveNodeprovider(nodeprovider: any) {
-    this.activeNodeprovider = nodeprovider;
-    this.nodeProviderChanged.emit(this.activeNodeprovider);
+  setActiveNodeprovider(nodeprovider: string) {
+    this.activeNodeprovider = of(nodeprovider);
+    this.nodeProviderChanged.emit(nodeprovider);
   }
 
   getActiveNodeprovider() {
@@ -60,63 +84,35 @@
   }
 
   loadNodeproviders() {
-   this.preferenceService.getPreferenceForScope(Scope.SYSTEM, 'nodeprovider.').pipe(
-      map(prefs => prefs.map(p => JSON.parse(p.value))))
-      .subscribe(
-        nodeproviders => this.setNewNodeproviders(nodeproviders),
-        error => this.notificationService.notifyError(
-          this.translateService.instant('navigator.nodeprovider.err-cannot-load-node-provider-from-settings'), error)
-      );
+    return this.http.get(this._nodeProviderEndpoint).pipe(
+      map(res => <string[]> (res as any)),
+      catchError(this.httpErrorHandler.handleError));
   }
 
-  setNewNodeproviders(nodeproviders) {
-    this.nodeproviders = nodeproviders;
-    this.nodeproviders.unshift(defaultNodeProvider);
+  getRoots() {
+    return this.activeNodeprovider.pipe(
+      flatMap(np => this.http.get(this._nodeProviderEndpoint + '/' + np).pipe(
+     	map(res => (res as any).hasOwnProperty('data') ? (res as any).data as [] : []),
+        map(res => plainToClass(MDMTreeNode, res)),
+        catchError(this.httpErrorHandler.handleError))
+      ));
+  }
+  
+  getChildren(item: MDMTreeNode) {
+    return this.activeNodeprovider.pipe(
+      flatMap(np => this.http.get(this._nodeProviderEndpoint + '/' + np + '/' + item.serial).pipe(
+        map(res => (res as any).hasOwnProperty('data') ? (res as any).data as [] : []),
+        map(res => plainToClass(MDMTreeNode, res)),
+        catchError(this.httpErrorHandler.handleError))
+      ));
   }
 
-  getQueryForChildren(item: MDMItem) {
-    return this.replace(this.getSubNodeprovider(item), item);
-  }
-
-  getSubNodeprovider(item: MDMItem) {
-    let current = this.activeNodeprovider;
-    do {
-      if (current.type === item.type && current.children) {
-        return current.children.query;
-      } else {
-        current = current.children;
-      }
-    }
-    while (current);
-
-    return current;
-  }
-
-replace(query: string, item: MDMItem) {
-    return '/' + item.source + query.replace(/{(\w+)\.(\w+)}/g, function(match, type, attr) {
-
-      if (type !== item.type) {
-        this.notificationService.notifyWarn(
-          this.translateService.instant(TRANSLATE('navigator.nodeprovider.err-unsupported-type'),
-          <any>{'type': type, 'typeToUse': item.type}));
-      }
-
-      if (attr === 'Id') {
-        return item.id;
-      }
-    });
-}
-
-  getPathTypes(type: string): string[] {
-    let current = this.activeNodeprovider;
-    let ancestorList: string[] = [];
-    while (current) {
-        ancestorList.push(current.type);
-        if (current.type === type) {
-          return ancestorList;
-        }
-        current = current.children;
-   }
-    return [];
+  getTreePath(item: MDMItem): Observable<MDMTreeNode[]> {
+    return this.activeNodeprovider.pipe(
+      flatMap(np => this.http.post(this._nodeProviderEndpoint + '/' + np, item).pipe(
+        map(res => (res as any).hasOwnProperty('data') ? (res as any).data as [] : []),
+        map(res => plainToClass(MDMTreeNode, res)),
+        catchError(this.httpErrorHandler.handleError))
+      ));
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html b/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
index 15cad3e..915e70c 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
@@ -57,7 +57,8 @@
                   typeaheadOptionsLimit="15"
                   placeholder="{{ 'search.edit-searchfields.enter-search-text' | translate }}"
                   class="form-control"
-                  style="margin-bottom: 15px;">
+                  style="margin-bottom: 15px;"
+                  [title]="'search.edit-searchfields.tooltip-no-wildcards' | translate">
               <searchattribute-tree [searchAttributes]="searchAttributes" [environments]="environments"></searchattribute-tree>
             </div>
             <div class="col-md-8" style="min-height: 50vh; max-height: 77vh; overflow-y:auto;">
@@ -77,8 +78,8 @@
                     <col style="width: 4%;">
                   </colgroup>
                   <tr *ngFor="let condition of layout.getConditions(env)">
-                    <td>{{condition?.type}}</td>
-                    <td>{{condition?.attribute}}</td>
+                    <td>{{condition?.type | mdmTranslate : env?.sourceName | async }}</td>
+                    <td>{{condition?.type | mdmTranslate : env?.sourceName : condition?.attribute | async }}</td>
                     <td style="width: 30px; text-align: center; vertical-align: middle;">
                       <span class="fa fa-chevron-up" [ngStyle]="{'visibility': isFirst(condition) ? 'hidden': 'visible'}" style="cursor: pointer; margin-bottom: 0;" (click)="moveUp(condition)"></span>
                     </td>
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts b/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
index 64bef6b..c8e3f00 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
@@ -24,25 +24,37 @@
 
 export enum Operator {
   EQUALS,
+  NOT_EQUALS,
   LESS_THAN,
   GREATER_THAN,
-  LIKE
+  LIKE,
+  GREATER_THAN_OR_EQUAL,
+  LESS_THAN_OR_EQUAL,
+  BETWEEN
 }
 
 export namespace OperatorUtil {
   export function values() {
-    return [ Operator.EQUALS, Operator.LESS_THAN, Operator.GREATER_THAN, Operator.LIKE ];
+    return Object.keys(Operator).filter(k => isNaN(+k)).map(key => Operator[key]);
   }
   export function toString(operator: Operator) {
       switch (operator) {
           case Operator.EQUALS:
             return '=';
+          case Operator.NOT_EQUALS:
+            return '!=';
           case Operator.LESS_THAN:
             return '<';
           case Operator.GREATER_THAN:
             return '>';
           case Operator.LIKE:
             return 'like';
+          case Operator.GREATER_THAN_OR_EQUAL:
+            return '≥';
+          case Operator.LESS_THAN_OR_EQUAL:
+            return '≤';
+          case Operator.BETWEEN:
+            return 'between';
           default:
             return undefined;
       }
@@ -51,12 +63,20 @@
       switch (operator) {
           case Operator.EQUALS:
             return 'eq';
+          case Operator.NOT_EQUALS:
+            return 'ne';
           case Operator.LESS_THAN:
             return 'lt';
           case Operator.GREATER_THAN:
             return 'gt';
           case Operator.LIKE:
             return 'lk';
+          case Operator.GREATER_THAN_OR_EQUAL:
+            return 'ge';
+          case Operator.LESS_THAN_OR_EQUAL:
+            return 'le';
+          case Operator.BETWEEN:
+            return 'bw';
           default:
             return undefined;
       }
@@ -71,18 +91,27 @@
   valueType: string;
 
   @Exclude()
+  isCurrentlyLinked: boolean;
+  @Exclude()
+  isCreatedViaLink: boolean;
+  @Exclude()
   sortIndex: number;
 
-  constructor(type: string, attribute: string, operator: Operator, value: string[], valueType?: string) {
+  constructor(type: string, attribute: string, operator: Operator, value: string[], valueType = 'string', isCurrentlyLinked = false, isCreatedViaLink = false) {
     this.type = type;
     this.attribute = attribute;
     this.operator = operator;
     this.value = value;
-    if (valueType) {
-      this.valueType = valueType.toLowerCase();
-    } else {
-      this.valueType = 'string';
-    }
+    this.valueType = valueType.toLowerCase();
+    this.isCreatedViaLink = isCreatedViaLink;
+    this.isCurrentlyLinked = isCurrentlyLinked;
+  }
+
+  public deserialize(obj: any) {
+    Object.assign(this, obj);
+    this.operator = Operator[obj.operator as keyof typeof Operator];
+    this.valueType = obj.valueType.toLowerCase();
+    return this;
   }
 }
 
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
index 5252208..a33b8d5 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
@@ -18,6 +18,40 @@
     outline: 0;
     box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 0, 0,.6);
 }
+p-togglebutton >>> .ui-button {
+  font-size: 14px;
+  margin: 0 3px;
+  color: #6c757d;
+  background-color: transparent;
+  background-image: none;
+  border-color: #6c757d;
+  height: 100%;
+  width: 2.4rem;
+  margin-top: -2px;
+}
+p-togglebutton ::ng-deep .ui-button-icon-only .ui-button-icon-left {
+  margin-top: -.45em;
+}
+p-togglebutton >>> .ui-button.ui-state-active {
+  /* color: #333333!important; */
+  color: #fff!important;
+  background-color: #a0a0a0;
+}
+
+p-togglebutton >>> .ui-button.ui-state-active >>> .ui-button-icon-left {
+  /* color: #333333!important; */
+  color: #fff!important;
+  background-color: #a0a0a0;
+}
+
+p-togglebutton >>> .ui-button:not(.ui-state-active).ui-state-focus {
+  border-color: #6c757d;
+  background-color: transparent!important;
+}
+
+p-togglebutton >>> .ui-button:not(.ui-state-active).ui-state-focus >>> .ui-button-icon-left {
+  color: #6c757d!important;
+}
 
 /* :host /deep/ .ui-multiselect .ui-multiselect-label {
   padding: .25em 2em .25em .25em;
@@ -36,7 +70,20 @@
     </div>
     <div class="col-sm">
       <label for="search-resulttype" class="col-form-label">{{ 'search.mdm-search.lbl-result-type' | translate }}</label>
-      <p-dropdown [options]="definitions" optionLabel="label" (onChange)="onResultTypeChane($event)" [style]="{'width': '100%'}"></p-dropdown>
+      <p-dropdown [options]="definitions" (onChange)="onResultTypeChanged($event)" [style]="{'width': '100%'}">
+        <ng-template let-option pTemplate="selectedItem">
+          <div style="display: flex; align-items: center;">
+            <img [src]="option?.type | typeIcon" style="padding-right: .5rem;"/>
+            <span>{{option?.type | mdmTranslate : (environments?.length > 0 ? environments[0].sourceName : undefined) | async}}</span>
+          </div>
+        </ng-template>
+        <ng-template let-option pTemplate="item">
+          <div style="display: flex; align-items: center;">
+            <img [src]="option?.type | typeIcon" style="padding-right: .5rem;"/>
+            <span>{{option?.type | mdmTranslate: (environments?.length > 0 ? environments[0].sourceName : undefined) | async}}</span>
+          </div>
+        </ng-template>
+      </p-dropdown>
     </div>
   </div>
   <div class="row" style="margin-top: 5px; margin-bottom: 10px;">
@@ -62,6 +109,9 @@
           <button type="button" class="btn btn-mdm" (click)="deleteFilter($event)" title="{{ 'search.mdm-search.tooltip-delete-search-filter' | translate }}"><span class="fa fa-times"></span></button>
         </div>
         <div class="col text-right">
+          <p-toggleButton [(ngModel)]="isSearchLinkedToNavigator" [onIcon]="'fa fa-link'" offIcon="fa fa-unlink" onLabel="" offLabel="" (onChange)="onToggleSearchLinkedToNavigator($event)"
+            [title]="(isSearchLinkedToNavigator ? 'search.mdm-search.tooltip-unlink-search-attributes-from-navigator' : 'search.mdm-search.tooltip-link-search-attributes-to-navigator')| translate">
+          </p-toggleButton>
           <button type="button" class="btn btn-mdm" (click)="resetConditions($event)" title="{{ 'search.mdm-search.tooltip-reset-search-conditions' | translate }}" [disabled]=!isAdvancedSearchActive><span class="fa fa-eraser"></span></button>
           <span class="fa" style="color: #333 !important; padding-left: 15px;" [ngClass]="{'fa-chevron-down': advancedSearch?.isOpen, 'fa-chevron-right': !advancedSearch?.isOpen}"></span>
         </div>
@@ -94,6 +144,7 @@
                     [condition]="condition"
                     [disabled]="!isAdvancedSearchActive"
                     [selectedEnvs]="selectedEnvironments"
+                    [defaultSourceName]="env === 'Global' ? environments[0].sourceName : env"
                     (onRemove)="removeCondition($event)">
                 </tr>
               </table>
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
index ca98675..4c16059 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
@@ -12,39 +12,31 @@
  *
  ********************************************************************************/
 
-
 import {Component, ViewChild, OnInit, OnDestroy} from '@angular/core';
 
-import {SearchService, SearchDefinition, SearchAttribute, SearchLayout} from './search.service';
-import {of as observableOf, combineLatest as observableCombineLatest} from 'rxjs';
-
+import { combineLatest, forkJoin, of, Subscription } from 'rxjs';
 import {defaultIfEmpty, mergeMap, tap} from 'rxjs/operators';
-
-import {FilterService, SearchFilter, Condition} from './filter.service';
-import {NodeService} from '../navigator/node.service';
-import {BasketService} from '../basket/basket.service';
-import {QueryService, SearchResult } from '../tableview/query.service';
-
-import {LocalizationService} from '../localization/localization.service';
-import {MDMNotificationService} from '../core/mdm-notification.service';
-
-import {Node} from '../navigator/node';
-
-import {TableviewComponent} from '../tableview/tableview.component';
-import {ViewComponent} from '../tableview/view.component';
-
 import {ModalDirective} from 'ngx-bootstrap';
 import {AccordionPanelComponent} from 'ngx-bootstrap/accordion';
-
-import {MenuItem} from 'primeng/primeng';
-import {EditSearchFieldsComponent} from './edit-searchFields.component';
-import {OverwriteDialogComponent} from '../core/overwrite-dialog.component';
-
-import {classToClass, serialize, deserialize} from 'class-transformer';
-import {SelectItem} from 'primeng/primeng';
-
 import { TranslateService } from '@ngx-translate/core';
+import {MenuItem, SelectItem} from 'primeng/primeng';
+import {classToClass, serialize, deserialize} from 'class-transformer';
+
+import { MDMNotificationService } from '../core/mdm-notification.service';
+import { OverwriteDialogComponent } from '../core/overwrite-dialog.component';
 import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
+import { MDMItem } from '@core/mdm-item';
+import {SearchService, SearchDefinition, SearchAttribute, SearchLayout} from './search.service';
+import {FilterService, SearchFilter, Condition, Operator} from './filter.service';
+import { NavigatorService } from '@navigator/navigator.service';
+import { NodeService} from '../navigator/node.service';
+import { Node } from '../navigator/node';
+import { BasketService} from '../basket/basket.service';
+import { QueryService, Row, SearchResult } from '../tableview/query.service';
+import { TableviewComponent } from '../tableview/tableview.component';
+import { ViewComponent } from '../tableview/view.component';
+import { EditSearchFieldsComponent } from './edit-searchFields.component';
+
 
 @Component({
   selector: 'mdm-search',
@@ -78,7 +70,6 @@
 
   searchFields: { group: string, attribute: string }[] = [];
 
-  subscription: any;
   searchExecuted = false;
 
   selectedRow: SearchFilter;
@@ -89,6 +80,9 @@
     { label: 'Add to shopping basket', icon: 'fa fa-shopping-cart', command: (event) => this.addSelectionToBasket() }
   ];
 
+  public isSearchLinkedToNavigator: false;
+  private linkedToNavigatorSub = new Subscription();
+
   @ViewChild(ViewComponent)
   viewComponent: ViewComponent;
 
@@ -114,10 +108,10 @@
     private queryService: QueryService,
     private filterService: FilterService,
     private nodeService: NodeService,
-    private localService: LocalizationService,
     private notificationService: MDMNotificationService,
     private basketService: BasketService,
-    private translateService: TranslateService) { }
+    private translateService: TranslateService,
+    private navigatorService: NavigatorService) { }
 
   ngOnInit() {
     this.currentFilter = this.filterService.EMPTY_FILTER;
@@ -126,8 +120,8 @@
             (msg: string) => this.contextMenuItems[0].label = msg);
 
     this.nodeService.getNodes().pipe(
-      mergeMap(envs => observableCombineLatest([
-        observableOf(envs),
+      mergeMap(envs => combineLatest([
+        of(envs),
         this.searchService.loadSearchAttributesStructured(envs.map(env => env.sourceName)),
         this.filterService.getFilters().pipe(defaultIfEmpty([this.currentFilter])),
         this.searchService.getDefinitionsSimple()
@@ -146,6 +140,7 @@
 
   ngOnDestroy() {
      this.saveState();
+     this.linkedToNavigatorSub.unsubscribe();
   }
 
   init(envs: Node[], attrs: { [type: string]: { [env: string]: SearchAttribute[] }},
@@ -231,13 +226,12 @@
     this.searchFields.splice(index, 1);
   }
 
-  onResultTypeChane(e: any) {
-    this.selectResultType(e.value);
-  }
-
-  selectResultType(type: any) {
-    this.currentFilter.resultType = type.type;
-    this.updateSearchAttributesForCurrentResultType();
+  onResultTypeChanged(event: {value: string, originalEvent: Event}) {
+    const searchDef = this.definitions.find(sd => sd.value === event.value);
+    if (searchDef) {
+      this.currentFilter.resultType = searchDef.type;
+      this.updateSearchAttributesForCurrentResultType();
+    }
   }
 
   updateSearchAttributesForCurrentResultType() {
@@ -387,7 +381,7 @@
 
   selected2Basket(e: Event) {
     e.stopPropagation();
-    this.tableViewComponent.selectedViewRows.forEach(row => this.basketService.add(row.getItem()));
+    this.tableViewComponent.selectedViewRows.forEach(row => this.basketService.add(Row.getItem(row)));
   }
 
   showSaveModal(e: Event) {
@@ -418,7 +412,7 @@
   }
 
   addSelectionToBasket() {
-    this.basketService.add(this.tableViewComponent.menuSelectedRow.getItem());
+    this.basketService.add(Row.getItem(this.tableViewComponent.menuSelectedRow));
   }
 
   mapSourceNameToName(sourceName: string) {
@@ -437,15 +431,6 @@
       : TRANSLATE('search.mdm-search.tooltip-enable-advanced-search');
   }
 
-/*
-  private loadSearchAttributes(environments: string[]) {
-    this.searchService.loadSearchAttributesStructured(environments)
-      .subscribe(
-        attrs => { this.allSearchAttributes = attrs; this.updateSearchAttributesForCurrentResultType(); },
-        error => this.notificationService.notifyError(
-          this.translateService.instant('search.mdm-search.err-cannot-load-attributes'), error));
-  }
-*/
   onRowSelect(e: any) {
     if (this.lazySelectedRow !== e.data) {
       this.selectedRow = e.data;
@@ -456,4 +441,83 @@
     }
     this.lazySelectedRow = this.selectedRow;
   }
+
+  public onToggleSearchLinkedToNavigator(event: {checked: boolean, originalEvent: MouseEvent}) {
+    // prevent advanced-search-tab to toggle.
+    event.originalEvent.stopPropagation();
+
+    if(event.checked) {
+      this.linkedToNavigatorSub.add(this.navigatorService.onSelectionChanged().pipe(tap(obj => this.handleSelectedTreeNodeChanged(obj))).subscribe());
+    } else {
+      this.linkedToNavigatorSub.unsubscribe();
+      this.unlinkConditions();
+    }
+  }
+
+  private handleSelectedTreeNodeChanged(object: Node | MDMItem) {
+    // unlink other conditions (the ones that where not created via link but got linked)
+    this.unlinkConditions();
+    if (object != undefined) {
+      // remove other conditions created via link
+      const oldIndex = this.currentFilter.conditions.findIndex(cond => cond.isCreatedViaLink);
+      if (oldIndex > -1) {
+        this.currentFilter.conditions.splice(oldIndex, 1);
+      }
+      // add conditions and recalculate search
+      this.addLinkedConditions(object).pipe(tap(() => this.calcCurrentSearch())).subscribe();
+    }
+  }
+
+  /**
+   * Links the related condition or create linked condition if none exists.
+   * @param object is Node if selected TreeNode is real node, is MDMItem if selected TreeNode is virtual node.
+   */
+  private addLinkedConditions(object: Node | MDMItem) {
+    let response = of(undefined);
+    if (object.type != 'Environment') {
+      if (object instanceof Node) {
+        this.addLinkedConditionForNode(object);
+      } else if (object.filter) {
+        response = this.addLinkedConditionsforVirtualNode(object);
+      }
+    }
+    return response;
+  }
+
+  /**
+   * Converts Item filter into Conditions via REST-Service. The Conditions are set to the
+   * linked condition or added as new if none exists
+   * @param item the virtual Node
+   */
+  private addLinkedConditionsforVirtualNode(item: MDMItem) {
+    return this.searchService.convertToCondition(item.source, item.filter).pipe(tap(conditions =>
+        conditions.forEach(condition => {
+          condition.isCurrentlyLinked = true
+          let linkedIndex = this.currentFilter.conditions.findIndex(cond => cond.type === condition.type && cond.attribute === condition.attribute);
+          if (linkedIndex > -1) {
+            this.currentFilter.conditions.splice(linkedIndex, 1, condition);
+          } else {
+            condition.isCreatedViaLink = true;
+            this.currentFilter.conditions.push(condition);
+          }
+        })));
+  }
+
+  /**
+   * Links the related condition or create linked condition if none exists.
+   * @param node  the Node
+   */
+  private addLinkedConditionForNode(node: Node) {
+    const linkedCondition = this.currentFilter.conditions.find(cond => cond.type === node.type && cond.attribute === 'Id');
+    if (linkedCondition) {
+      linkedCondition.value = [node.id];
+      linkedCondition.isCurrentlyLinked = true;
+    } else {
+      this.currentFilter.conditions.push(new Condition(node.type, 'Id', Operator.EQUALS, [node.id], 'string', true, true));
+    }
+  }
+
+  private unlinkConditions() {
+    this.currentFilter.conditions.forEach(cond => cond.isCurrentlyLinked = false);
+  }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
index 760c623..4473b42 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
@@ -27,19 +27,25 @@
 import {FilterService} from './filter.service';
 
 import {SearchattributeTreeModule} from '../searchattribute-tree/searchattribute-tree.module';
-import {AutoCompleteModule} from 'primeng/primeng';
+import {AutoCompleteModule, InputTextModule, ToggleButtonModule} from 'primeng/primeng';
+import {TooltipModule} from 'primeng/tooltip';
+import { TypeIconPipe } from './pipes/type-icon.pipe';
 
 @NgModule({
   imports: [
     MDMCoreModule,
     TableViewModule,
     SearchattributeTreeModule,
-    AutoCompleteModule
+    AutoCompleteModule,
+    TooltipModule,
+    ToggleButtonModule,
+    InputTextModule
   ],
   declarations: [
     MDMSearchComponent,
     SearchConditionComponent,
     EditSearchFieldsComponent,
+    TypeIconPipe,
   ],
   exports: [
     MDMSearchComponent,
@@ -48,7 +54,8 @@
   providers: [
     SearchService,
     FilterService,
-    DatePipe
+    DatePipe,
+    TypeIconPipe
   ],
 })
 export class MDMSearchModule {
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts
new file mode 100644
index 0000000..fb1779a
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts
@@ -0,0 +1,22 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+ 
+ import { TypeIconPipe } from './type-icon.pipe';
+
+describe('TypeIconPipe', () => {
+  it('create an instance', () => {
+    const pipe = new TypeIconPipe();
+    expect(pipe).toBeTruthy();
+  });
+});
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts
new file mode 100644
index 0000000..fd2ae37
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts
@@ -0,0 +1,65 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { SearchDefinition } from '../search.service';
+
+type nodeType = 'Environment' | 'Pool' | 'Project' | 'Test' | 'TestStep' | 'Measurement' | 'ChannelGroup' | 'Channel' | 'Quantity' | 'Unit';
+
+@Pipe({
+  name: 'typeIcon'
+})
+export class TypeIconPipe implements PipeTransform {
+
+  transform(value: nodeType | SearchDefinition): any {
+    let type: string;
+    if (value instanceof SearchDefinition) {
+      type = value.type
+    } else {
+      type = value;
+    }
+    let imagePath = 'assets/famfamfam_icons/';
+
+    switch (type as nodeType) {
+      case 'Environment':
+        imagePath += 'database.png';
+        break;
+      case 'Project':
+        imagePath += 'house.png';
+        break;
+      case 'Pool':
+        imagePath += 'paste_plain.png';
+        break;
+      case 'Test':
+        imagePath += 'brick_add.png';
+        break;
+      case 'TestStep':
+        imagePath += 'brick.png';
+        break;
+      case 'Measurement':
+        imagePath += 'chart_curve.png';
+        break;
+      case 'ChannelGroup':
+        imagePath += 'calendar.png';
+        break;
+      case 'Channel':
+      case 'Unit':
+      case 'Quantity':
+        imagePath += 'chart_curve_go.png';
+        break;
+    }
+    return imagePath;
+  }
+
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
index 7cda7f7..b3da560 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
@@ -15,37 +15,42 @@
 
 table.searchdefinition td { vertical-align: middle; padding: 4px 8px; }
 
-p-autoComplete >>> .ui-inputtext.ui-state-focus {
-  border: 1px solid #66afe9 !important;
-  outline: 0 !important;
-  box-shadow: 0 0 8px rgba(102,175,233,.6) !important;
-}
-
-p-autoComplete >>> .ui-autocomplete.ui-autocomplete-multiple .ui-autocomplete-multiple-container {
-  padding: 2.5px 8px !important;
-  border: 1px solid #ccc;
-  color: #555;
-  box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-  width: 100%;
-}
-
-p-autoComplete >>> .ui-autocomplete-multiple-container.ui-inputtext {
+p-autoComplete ::ng-deep .ui-autocomplete-multiple-container.ui-inputtext {
   overflow: inherit !important;
 }
 
-p-autoComplete >>> .ui-autocomplete {
+p-autoComplete ::ng-deep ul {
   width: 100%;
 }
 
-p-autoComplete >>> .ui-widget:disabled{
-  cursor: not-allowed !important;
-  background-color: #eee;
-  color: #555 !important;
-  opacity: 1;
+p-autoComplete ::ng-deep .ui-widget:disabled,
+p-autoComplete ::ng-deep .ui-state-disabled,
+p-autoComplete ::ng-deep input {
+  opacity: .5;
 }
-p-autoComplete >>> .ui-state-disabled{
-  cursor: not-allowed !important;
-  background-color: #eee !important;
-  border-color: #e3e3e3 !important;
-  opacity: 1;
+
+::ng-deep .ui-tooltip-text {
+  width: 20rem;
+}
+
+.linked-icon, .unlinked-icon {
+  margin-right: 2px;
+  float: right;
+  padding: 4px;
+  border-radius: 40px;
+  border: 1px solid #007ad9;
+}
+.linked-icon {
+  color: white;
+  background: #007ad9;
+}
+.unlinked-icon {
+  color: #007ad9;
+}
+
+.linked-container {
+  display: inline-grid;
+  grid-template-columns: 1.8rem auto;
+  align-items: center;
+  width: 100%;
 }
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
index deccb96..bca6d1b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
@@ -12,10 +12,14 @@
  *
  ********************************************************************************-->
 
-<td style="vertical-align: middle">{{condition.type | mdmdatasourcetranslate}}</td> <!-- | translate -->
-<td style="vertical-align: middle">{{condition.type | mdmdatasourcetranslate: condition.attribute }}</td> <!--  | translate: condition.attribute -->
 <td style="vertical-align: middle">
-  <div class="btn-group" dropdown style="width: 66px;">
+  <span>{{condition.type | mdmTranslate : defaultSourceName | async}}</span>
+  <span *ngIf="condition?.isCurrentlyLinked" class="fa fa-link linked-icon" pTooltip="{{ 'search.search-condition.condition-is-linked' | translate }}"></span>
+  <span *ngIf="!condition?.isCurrentlyLinked && condition?.isCreatedViaLink" class="fa fa-unlink unlinked-icon" pTooltip="{{ 'search.search-condition.condition-created-via-link' | translate }}"></span>
+</td> <!-- | translate -->
+<td style="vertical-align: middle">{{condition.type | mdmTranslate : defaultSourceName : condition.attribute | async }}</td> <!--  | translate: condition.attribute -->
+<td style="vertical-align: middle">
+  <div class="btn-group" dropdown style="width: 6rem;">
     <button id="operator" type="button" title="{{ 'search.search-condition.tooltip-select-search-operator' | translate }}" class="btn btn-default btn-sm dropdown-toggle" dropdownToggle aria-haspopup="true" aria-expanded="false" [disabled]="disabled">
       {{getOperatorName(condition.operator)}} <span class="caret"></span>
     </button>
@@ -26,30 +30,115 @@
     </ul>
   </div>
 </td>
+<!-- <td style="vertical-align: middle">
+  <span *ngIf="condition?.isCurrentlyLinked" class="fa fa-link" pTooltip="{{ 'search.search-condition.condition-is-linked' | translate }}"></span>
+</td> -->
 <td [ngSwitch]="condition.valueType">
-  <p-autoComplete *ngSwitchCase="'string'"
-    [(ngModel)]="condition.value"
-    [suggestions]="displayedSuggestions"
-    (completeMethod)="updateSuggestions($event)"
-    [multiple]="true"
-    [delay]="0"
-    [size]="500"
-    [scrollHeight]="'50vh'"
-    placeholder="Wert"
-    [disabled]="disabled"
-    (keyup.enter)="onEnter($event)">
-  </p-autoComplete>
-  <p-calendar *ngSwitchCase="'date'" 
-    (ngModelChange)="setDateValue($event)" 
-    [ngModel]="dateValue" 
-    dateFormat="dd.mm.yy" 
-    showTime="true" 
-    hourFormat="24" 
-    hideOnDateTimeSelect="true" 
-    [showIcon]="true" 
-    [disabled]="disabled"
-    [ngStyle]="{ 'width': '100%' }">
-  </p-calendar>
-  <input *ngSwitchDefault type="text" class="form-control input-sm" placeholder="Wert" [value]="condition.value" (input)="setValue($event.target.value)" [disabled]="disabled">
+  <ng-container *ngSwitchCase="'string'">
+    <div [ngClass]="{'linked-container': condition?.isCurrentlyLinked}">
+      <span *ngIf="condition?.isCurrentlyLinked" class="fa fa-link linked-icon" pTooltip="{{ 'search.search-condition.condition-is-linked' | translate }}"></span>
+      <p-autoComplete #multi *ngIf="isHandlingMultipleValues"
+        [(ngModel)]="condition.value"
+        [suggestions]="displayedSuggestions"
+        (completeMethod)="updateSuggestions($event)"
+        [multiple]="true"
+        [delay]="0"
+        [size]="500"
+        [scrollHeight]="'50vh'"
+        placeholder="Wert"
+        [disabled]="disabled"
+        (keyup.enter)="onEnter($event, multi)"
+        (onBlur)="onAutocompleteBlur($event, multi)"
+        [style]="{ 'width': '100%'}"
+        [inputStyle]="{ 'width': '100%' }"
+        pTooltip="{{ 'search.search-condition.multi-value-input' | translate }}" tooltipPosition="top">
+      </p-autoComplete>
+    </div>
+    <p-autoComplete #first *ngIf="!isHandlingMultipleValues"
+      [(ngModel)]="stringValueStart"
+      [suggestions]="displayedSuggestions"
+      (completeMethod)="updateSuggestions($event)"
+      [multiple]="false"
+      [delay]="0"
+      [size]="500"
+      [scrollHeight]="'50vh'"
+      placeholder="Wert"
+      [disabled]="disabled"
+      (keyup.enter)="onEnter($event, first)"
+      (onBlur)="onAutocompleteBlur($event, first)"
+      [style]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%'}"
+      [inputStyle]="{ 'width': '100%' }"
+      pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+    </p-autoComplete>
+    <ng-container *ngIf="isBinaryOperator">
+      <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+      <p-autoComplete #second
+        [(ngModel)]="stringValueEnd"
+        [suggestions]="displayedSuggestions"
+        (completeMethod)="updateSuggestions($event)"
+        [multiple]="false"
+        [delay]="0"
+        [size]="500"
+        [scrollHeight]="'50vh'"
+        placeholder="Wert"
+        [disabled]="disabled"
+        (keyup.enter)="onEnter($event, second)"
+        (onBlur)="onAutocompleteBlur($event, second)"
+        [style]="{ 'width': 'calc(50% - 1.5rem)'}"
+        [inputStyle]="{ 'width': '100%' }"
+        pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+      </p-autoComplete>
+    </ng-container>
+  </ng-container>
+
+  <ng-container *ngSwitchCase="'date'" >
+    <p-calendar
+      (ngModelChange)="setDateValue($event, 0)" 
+      [ngModel]="dateValue" 
+      dateFormat="dd.mm.yy" 
+      showTime="true" 
+      hourFormat="24" 
+      hideOnDateTimeSelect="true" 
+      [showIcon]="true" 
+      [disabled]="disabled"
+      [style]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%' }"
+      [inputStyle]="{ 'width': 'calc(100% - 2.2rem)' }"
+      pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+    </p-calendar>
+    <ng-container *ngIf="isBinaryOperator">
+      <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+      <p-calendar *ngSwitchCase="'date'" 
+        (ngModelChange)="setDateValue($event, 1)" 
+        [ngModel]="dateValueEnd" 
+        dateFormat="dd.mm.yy" 
+        showTime="true" 
+        hourFormat="24" 
+        hideOnDateTimeSelect="true" 
+        [showIcon]="true" 
+        [disabled]="disabled"
+        [style]="{ 'width': 'calc(50% - 1.5rem)' }"
+        [inputStyle]="{ 'width': 'calc(100% - 2.2rem)' }"
+        pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+      </p-calendar>
+    </ng-container>
+  </ng-container>
+
+  <ng-container *ngSwitchDefault>
+    <input pInputText type="text" placeholder="Wert"
+      [disabled]="disabled"
+      [(ngModel)]="stringValueStart"
+      (input)="setValue($event)"
+      pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top"
+      [ngStyle]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%' }">
+    <ng-container *ngIf="isBinaryOperator">
+      <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+      <input pInputText type="text" placeholder="Wert"
+        [disabled]="disabled"
+        [(ngModel)]="stringValueEnd"
+        (input)="setValue($event)"
+        pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top"
+        [ngStyle]="{'width':'calc(50% - 1.5rem)'}">
+    </ng-container>
+  </ng-container>
 </td>
 <td style="vertical-align: middle"><button id="remove" type="button" class="btn btn-default btn-sm fa fa-times remove" (click)="remove()" [disabled]="disabled"></button></td>
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
index 67e1a0c..e3f4c74 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
@@ -13,13 +13,11 @@
  ********************************************************************************/
 
 
-import {Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, ViewChild, AfterViewInit} from '@angular/core';
+import {Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnInit } from '@angular/core';
 import {FormGroup} from '@angular/forms';
-import {LocalizationService} from '../localization/localization.service';
 import {Condition, Operator, OperatorUtil} from './filter.service';
 import {Node} from '../navigator/node';
 
-import {PropertyService} from '../core/property.service';
 import {QueryService} from '../tableview/query.service';
 import {AutoComplete} from 'primeng/primeng';
 
@@ -27,36 +25,50 @@
 
 import {TranslateService} from '@ngx-translate/core';
 import { DatePipe } from '@angular/common';
+import { NavigatorService } from '@navigator/navigator.service';
+
 
 @Component({
   selector: '[search-condition]',
   templateUrl: 'search-condition.component.html',
   styleUrls: ['search-condition.component.css'],
 })
-export class SearchConditionComponent implements OnChanges, AfterViewInit {
+export class SearchConditionComponent implements OnChanges, OnInit {
+
+  private readonly multiValueOperators = [Operator.LIKE, Operator.EQUALS, Operator.NOT_EQUALS];
 
   @Input() env: string;
   @Input() condition: Condition;
   @Input() form: FormGroup;
   @Input() disabled: boolean;
   @Input() selectedEnvs: Node[];
+  @Input() defaultSourceName: string;
   @Output() onRemove = new EventEmitter<Condition>();
 
   suggestions: string[];
   displayedSuggestions: string[] = [];
   lastQuery: string;
+
+  // date values for two input fields
   dateValue: Date;
+  dateValueEnd: Date;
 
-  @ViewChild(AutoComplete) primeAutoCompleteComponent: AutoComplete;
+  // string values for two input fields
+  stringValueStart: string = '';
+  stringValueEnd: string = '';
 
-  constructor(private localservice: LocalizationService,
-              private prop: PropertyService,
-              private queryService: QueryService,
+  public isBinaryOperator = false;
+  public isHandlingMultipleValues = false;
+
+  constructor(private queryService: QueryService,
               private notificationService: MDMNotificationService,
               private translateService: TranslateService,
-              private datePipe: DatePipe) { }
+              private datePipe: DatePipe,
+              private navigatorService: NavigatorService) { }
 
-  ngAfterViewInit() {
+  ngOnInit() {
+    // set default operator if empty and evaluates operator properties like multiple input, binary eg.
+    this.setOperator(this.condition.operator === undefined ? Operator.EQUALS : this.condition.operator);
   }
 
   ngOnChanges(changes: SimpleChanges) {
@@ -64,27 +76,46 @@
       this.setAutoCompleteValues();
     }
 
-    if (this.condition.valueType === 'date') {
-      console.log(this.condition.value[0]);
-      if (this.condition.value === undefined || this.condition.value[0] === undefined) {
-        this.dateValue = new Date();
-        (<Date> this.dateValue).setHours(0, 0, 0, 0);
-      } else {
-        this.dateValue = new Date(this.condition.value[0]);
+    if (changes['condition']) {
+      // set initial date to today, otherwise primeNg calender breaks on undefined input.
+      if (this.condition.valueType === 'date') {
+        if (this.condition.value === undefined || this.condition.value[0] === undefined) {
+          this.dateValue = new Date();
+          this.dateValue.setHours(0, 0, 0, 0);
+        } else {
+          this.dateValue = new Date(this.condition.value[0]);
+        }
+        this.setDateValue(this.dateValue, 0);
+        if (this.condition.value === undefined || this.condition.value[1] === undefined) {
+          this.dateValueEnd = new Date();
+          this.dateValueEnd.setHours(0, 0, 0, 0);
+          this.dateValueEnd.setDate(this.dateValueEnd.getDate() + 1)
+        } else {
+          this.dateValueEnd = new Date(this.condition.value[1]);
+        }
+        this.setDateValue(this.dateValue, 1);
       }
     }
   }
 
-  onEnter(e: Event) {
-    let hl = this.primeAutoCompleteComponent.highlightOption;
-    if (hl == undefined && this.lastQuery !== '' || hl != undefined && this.displayedSuggestions.find(s => s === hl) == undefined) {
-      if (this.primeAutoCompleteComponent.value[this.primeAutoCompleteComponent.value.length - 1] === hl) {
-        this.primeAutoCompleteComponent.value.pop();
+  onAutocompleteBlur(event: Event, htmlElement: AutoComplete) {
+    this.onEnter(undefined, htmlElement);
+  }
+
+  onEnter(e: Event, autoComplete: AutoComplete) {
+    if(this.isHandlingMultipleValues) {
+      let hl = autoComplete.highlightOption;
+      if (hl == undefined && this.lastQuery != undefined  && this.lastQuery !== '' || hl != undefined && this.displayedSuggestions.find(s => s === hl) == undefined) {
+        if (autoComplete.value[autoComplete.value.length - 1] === hl) {
+          autoComplete.value.pop();
+        }
+        autoComplete.selectItem(this.lastQuery);
+        this.lastQuery = '';
       }
-      this.primeAutoCompleteComponent.selectItem(this.lastQuery);
-      this.lastQuery = '';
+      autoComplete.highlightOption = undefined;
+    } else {
+      this.condition.value = [this.stringValueStart, this.stringValueEnd];
     }
-    this.primeAutoCompleteComponent.highlightOption = undefined;
     this.displayedSuggestions = [];
   }
 
@@ -117,6 +148,20 @@
 
   setOperator(operator: Operator) {
     this.condition.operator = operator;
+    this.isBinaryOperator = operator === Operator.BETWEEN;
+    this.isHandlingMultipleValues = this.multiValueOperators.some(op => op === operator);
+    this.adjustInput();
+  }
+
+  private adjustInput() {
+    if (this.condition.valueType === 'string') {
+      if (!this.isHandlingMultipleValues) {
+        this.stringValueStart = this.condition.value[0];
+        this.stringValueEnd = this.condition.value[1];
+      } else {
+        this.condition.value = this.condition.value.filter(v => v != undefined);
+      }
+    }
   }
 
   setValue(value: string) {
@@ -127,9 +172,11 @@
     this.condition.value = value;
   }
 
-  setDateValue(value: Date) {
-    console.log(value);
-    this.condition.value = [this.datePipe.transform(value, 'yyyy-MM-dd' + 'T' + 'HH:mm:ss')];
+  setDateValue(value: Date, index: number) {
+    if (this.condition.value == undefined) {
+      this.condition.value = [];
+    }
+    this.condition.value[index] = this.datePipe.transform(value, 'yyyy-MM-dd' + 'T' + 'HH:mm:ss');
   }
 
   remove() {
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts b/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
index 77c1c18..d43f278 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
@@ -145,7 +145,7 @@
       let filter = service.convertEnv('env1', [cond1, cond2, cond3], attributes, 'test');
 
       expect(filter.sourceName).toEqual('env1');
-      expect(filter.filter).toEqual('Test.Name ci_lk "PBN*" and Vehicle.Number eq 12 and Vehicle.Created eq "2017-07-17T12:13:14"');
+      expect(filter.filter).toEqual('( Test.Name ci_lk "PBN*" ) and ( Vehicle.Number eq 12 ) and ( Vehicle.Created eq "2017-07-17T12:13:14" )');
       expect(filter.searchString).toEqual('test');
 
     })));
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts b/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
index 1a461e0..0c9ef1d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
@@ -25,7 +25,7 @@
 import { Preference, PreferenceService, Scope } from '../core/preference.service';
 import {HttpErrorHandler} from '../core/http-error-handler';
 
-import {SearchFilter, Condition, OperatorUtil} from './filter.service';
+import {SearchFilter, Condition, OperatorUtil, Operator} from './filter.service';
 import {Query, Filter} from '../tableview/query.service';
 import {View} from '../tableview/tableview.service';
 
@@ -95,7 +95,7 @@
   }
 
   getConditions(sourceName: string) {
-    return this.map[sourceName].sort((c1, c2) => c1.sortIndex - c2.sortIndex) || [];
+    return (this.map[sourceName] || []).sort((c1, c2) => c1.sortIndex - c2.sortIndex);
   }
 
   getSourceName(condition: Condition) {
@@ -180,7 +180,6 @@
       catchError(this.httpErrorHandler.handleError));
   }
 
-// TODO TRANSLATION
   getDefinitionsSimple() {
     return observableOf([
       <SearchDefinition>{ key: '1', value: 'tests', type: 'Test', label: 'Versuche' },
@@ -253,14 +252,33 @@
   convertEnv(env: string, conditions: Condition[], attrs: SearchAttribute[], fullTextQuery: string): Filter {
 
     let filterString = conditions
-      .map(c => c.value.map(value => c.type + '.' + c.attribute + ' ' + this.adjustOperator(OperatorUtil.toFilterString(c.operator),
-         this.getValueType(c, attrs)) + ' ' + this.quoteValue(value, this.getValueType(c, attrs))).join(' or '))
+      .map(c => {
+        if (c.operator === Operator.BETWEEN) {
+          if (c.value[0] != undefined && c.value[0].length > 0 && c.value[1] != undefined && c.value[1].length > 0) {
+            return c.type + '.' + c.attribute + ' ' + OperatorUtil.toFilterString(c.operator)
+            + ' (' +this.quoteValue(c.value[0], this.getValueType(c, attrs)) + ', ' + this.quoteValue(c.value[1], this.getValueType(c, attrs)) +')'
+          } else {
+            return '';
+          }
+        } else {
+          return c.value.map(value => c.type + '.' + c.attribute + ' ' + this.adjustOperator(OperatorUtil.toFilterString(c.operator),
+            this.getValueType(c, attrs)) + ' ' + this.quoteValue(value, this.getValueType(c, attrs))).join(c.operator === Operator.NOT_EQUALS ? ' and ' : ' or ')
+        }
+      })
       .filter(c => c.length > 0)
+      .map(c => '( ' + c + ' )')
       .join(' and ');
 
     return new Filter(env, filterString, fullTextQuery);
   }
 
+  public convertToCondition(source: string, filter: string) {
+    return this.http.get(this._prop.getUrl('mdm/environments/' + source + '/conditions/filter/' + filter)).pipe(
+      map(response => (response.json().data as any[]).map(cond => new Condition(undefined, undefined, undefined, undefined).deserialize(cond))),
+      catchError(this.httpErrorHandler.handleError)
+    );
+  }
+
   quoteValue(value: string, valueType: string) {
     if (valueType.toLowerCase() === 'string' || valueType.toLowerCase() === 'date') {
       return '"' + value + '"';
diff --git a/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.html b/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.html
index 80dd927..5f54c8e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.html
@@ -19,6 +19,14 @@
         (onNodeSelect)="nodeSelect($event)">
 
     <ng-template let-node pTemplate="default">
-        <span [title]="node.label" >{{ node.label }}</span>
+        <span [title]="node.label" >{{ node.data.label }}</span>
+    </ng-template>
+
+    <ng-template let-node pTemplate="group">
+        <span [title]="node.label" >{{ node.data.boType | mdmTranslate : node.data.attributes[0].source | async }}</span>
+    </ng-template>
+
+    <ng-template let-node pTemplate="attribute">
+        <span [title]="node.label" >{{ node.data.boType | mdmTranslate : node.data.source : node.data.attrName | async }}</span>
     </ng-template>
 </p-tree>
diff --git a/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.ts b/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.ts
index e04f2dd..2d80c6a 100644
--- a/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/searchattribute-tree/searchattribute-tree.component.ts
@@ -68,7 +68,7 @@
       label: group.boType,
       leaf: false,
       type: 'group',
-      data: group.attributes
+      data: group
     };
   }
 
@@ -87,7 +87,7 @@
         .sort((a, b) => a.boType.localeCompare(b.boType))
         .map(g => this.mapType(g));
     } else if (node.type === 'group') {
-      return (<SearchAttribute[]> node.data)
+      return (<SearchAttribute[]> node.data.attributes)
         .sort((a, b) => a.attrName.localeCompare(b.attrName))
         .map(a => this.mapAttribute(a));
     } else {
diff --git a/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html b/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
index 76148ed..7be933c 100644
--- a/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
@@ -61,7 +61,8 @@
                       typeaheadOptionsLimit="15"
                       placeholder="{{ 'tableview.editview.enter-search-text' | translate }}"
                       class="form-control"
-                      style="margin-bottom: 15px;">
+                      style="margin-bottom: 15px;"
+                      [title]="'tableview.editview.tooltip-no-wildcards' | translate">
               <searchattribute-tree [searchAttributes]="searchAttributes" [environments]="environments"></searchattribute-tree>
             </div>
             <div class="col-md-8" style="min-height: 50vh; max-height: 72vh; overflow-y:auto;">
@@ -72,10 +73,10 @@
             <table class="table table-bordered" style="padding: 0; overflow-y:auto; table-layout: fixed;">
               <tr *ngFor="let col of currentView.columns">
                 <td style="width: 50%;" >
-                  {{col.type}}
+                  {{col.type | mdmTranslate | async}}
                 </td>
                 <td style="width: 50%;" >
-                   {{col.name}}
+                   {{col.type | mdmTranslate : undefined : col.name | async}}
                 </td>
                 <td style="width: 30px; text-align: center; vertical-align: middle;">
                   <span class="fa fa-chevron-up" [ngStyle]="{'visibility': isFirst(col) ? 'hidden': 'visible'}" style="cursor: pointer; margin-bottom: 0;" (click)="moveUp(col)"></span>
diff --git a/nucleus/webclient/src/main/webapp/src/app/tableview/query.service.ts b/nucleus/webclient/src/main/webapp/src/app/tableview/query.service.ts
index abf5e97..14f1d72 100644
--- a/nucleus/webclient/src/main/webapp/src/app/tableview/query.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/tableview/query.service.ts
@@ -21,7 +21,7 @@
 import {PropertyService} from '../core/property.service';
 import {MDMItem} from '../core/mdm-item';
 import {HttpErrorHandler} from '../core/http-error-handler';
-import {Type, deserialize} from 'class-transformer';
+import {Type, deserialize, plainToClass} from 'class-transformer';
 
 export class Filter {
   sourceName: string;
@@ -39,6 +39,8 @@
   @Type(() => Filter)
   filters: Filter[] = [];
   columns: String[] = [];
+  resultOffset: number;
+  resultLimit: number;
 
   addFilter(sourceName: string, filter: string) {
     let f = this.filters.find(i => i.sourceName === sourceName);
@@ -63,8 +65,8 @@
   @Type(() => Columns)
   columns: Columns[] = [];
 
-  getColumn(col: string) {
-    let column = this.columns.find(c => c.type + '.' + c.attribute === col);
+  public static getColumn(row: Row, col: string) {
+    let column = row.columns.find(c => c.type + '.' + c.attribute === col);
     if (column) {
       return column.value;
     } else {
@@ -72,12 +74,12 @@
     }
   }
 
-  equals (row: Row) {
-    return this.source === row.source && this.type === row.type && this.id === row.id;
+  public static equals (a: Row, b: Row) {
+    return a.source === b.source && a.type === b.type && a.id === b.id;
   }
 
-  public getItem() {
-    return new MDMItem(this.source, this.type, this.id);
+  public static getItem(row: Row) {
+    return new MDMItem(row.source, row.type, row.id);
   }
 }
 
@@ -98,7 +100,7 @@
 
   query(query: Query): Observable<SearchResult> {
     return this.http.post(this.queryUrl, query).pipe(
-               map(res => deserialize(SearchResult, res.text())),
+               map(res => <SearchResult> res.json()),
                catchError(this.httpErrorHandler.handleError));
   }
 
diff --git a/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.html b/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.html
index d1305d7..8a2f571 100644
--- a/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.html
@@ -47,29 +47,7 @@
            (onColResize)="onColResize($event)"
            (onSort)="onSort($event)"
            (onContextMenuSelect)="onContextMenuSelect($event)"
-           (onRowClick)="onRowClick($event)"
->
-           <!--
-           [scrollable]="true"
-           [style]="{width:'200px;'}"
-
-           [sortField]="view?.getSortField()"
-           [sortOrder]="view?.getSortOrder()"
-           (sortFunction)="onSort($event)"
-
-           (onColReorder)="onColReorder($event)"
-
-
-           stateStorage="local"
-           stateKey="state01" -->
-    <!-- <ng-template pTemplate="colgroup" let-columns>
-          <colgroup>
-            <col style="width: 3em">
-            <col style="width: 3em">
-            <col style="width: 3em">
-            <col *ngFor="let col of columns" [style]="{'width': col.width +'px;'}">
-          </colgroup>
-    </ng-template> -->
+           (onRowClick)="onRowClick($event)">
     <ng-template pTemplate="header" let-columns>
       <tr>
         <th style="width: 3em">
@@ -77,13 +55,11 @@
         </th>
         <th style="width: 3em"></th>
         <th style="width: 3em"></th>
-        <!--
-            [style]="{'width': col.width +'px;'}" -->
         <th *ngFor="let col of columns"
             [pSortableColumn]="col.field"
             pReorderableColumn
             pResizableColumn>
-          {{col.header}}
+          {{col.type | mdmTranslate : undefined : col.attribute | async}} ({{col.type | mdmTranslate | async}})
           <p-sortIcon [field]="col.field"></p-sortIcon>
         </th>
       </tr>
diff --git a/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.ts b/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.ts
index d1ccecc..d1fd36b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/tableview/tableview.component.ts
@@ -33,20 +33,25 @@
 
 export class TurboViewColumn {
 
-  field: string;
-  header: string;
+  type: string;
+  attribute: string;
   width: number;
   sortOrder: number;
 
-  constructor (field: string, header: string, width?: number, sortOrder?: number) {
-    this.field = field;
-    this.header = header;
+  field: string;
+  header: string;
+
+  constructor (type: string, attribute: string, width?: number, sortOrder?: number) {
+    this.type = type;
+    this.attribute = attribute;
     this.sortOrder = sortOrder != undefined ? sortOrder : 0;
     this.width = width;
+
+    this.field = this.type + "_" + this.attribute;
+    this.header = this.type + " " + this.attribute;
   }
 }
 
-
 @Component({
   selector: 'mdm-tableview',
   templateUrl: 'tableview.component.html',
@@ -133,7 +138,7 @@
   }
 
   mapColumn2Turbo(viewCol: ViewColumn) {
-    return new TurboViewColumn(viewCol.type + '_' + viewCol.name, viewCol.type + ' ' + viewCol.name, viewCol.width);
+    return new TurboViewColumn(viewCol.type, viewCol.name, viewCol.width);
   }
 
   /** @TODO: update for viewRows/columns
@@ -219,7 +224,7 @@
   }
 
   functionalityProvider(row: Row) {
-    let item = row.getItem();
+    let item = Row.getItem(row);
     if (this.isShopable) {
       this.basketService.add(item);
     } else {
@@ -267,22 +272,22 @@
   **/
   onRowClick(e: any) {
     let row: Row = e.data;
-    this.nodeService.getNodeFromItem(row.getItem()).subscribe(
+    this.nodeService.getNodeFromItem(Row.getItem(row)).subscribe(
       node => this.navigatorService.fireSelectedNodeChanged(node),
       error => this.notificationService.notifyError(this.translateService.instant('tableview.tableview.err-cannot-calculate-node'), error)
     );
     let event: MouseEvent = e.originalEvent;
     if (event.shiftKey && this.selectedRows.length > 0) {
       let lastRow = this.selectedRows[this.selectedRows.length - 1];
-      let lastIndex = this.results.rows.findIndex(r => r.equals(lastRow));
-      let thisIndex = this.results.rows.findIndex(r => r.equals(row));
-      if (this.selectedRows.findIndex(sr => sr.equals(row)) > -1) {
+      let lastIndex = this.results.rows.findIndex(r => Row.equals(r, lastRow));
+      let thisIndex = this.results.rows.findIndex(r => Row.equals(r, row));
+      if (this.selectedRows.findIndex(sr => Row.equals(sr, row)) > -1) {
       } else {
         let min = Math.min(lastIndex, thisIndex);
         let max = Math.max(lastIndex, thisIndex);
         this.results.rows.slice(min, max + 1)
           .forEach(r => {
-            if (this.selectedRows.findIndex(sr => sr.equals(r)) === -1) {
+            if (this.selectedRows.findIndex(sr => Row.equals(sr, r)) === -1) {
               this.selectedRows.push(r);
             }
           });
@@ -290,7 +295,7 @@
     } else if (event.ctrlKey) {
       this.selectRow(row);
     } else {
-      if (this.selectedRows.length > 1 || (this.selectedRows.length !== 0 && !row.equals(this.selectedRows[0]))) {
+      if (this.selectedRows.length > 1 || (this.selectedRows.length !== 0 && !Row.equals(row, this.selectedRows[0]))) {
         this.selectedRows = [];
       }
       this.selectRow(row);
@@ -303,7 +308,7 @@
   *   @deprecated
   **/
   selectRow(row: Row) {
-    let index = this.selectedRows.findIndex(ai => ai.equals(row));
+    let index = this.selectedRows.findIndex(ai => Row.equals(ai, row));
     if (index === -1) {
       this.selectedRows.push(row);
     } else {
@@ -316,11 +321,11 @@
       this.selectedViewRows = [row];
     }
     if (this.selectedViewRows != undefined && this.selectedViewRows.length === 0 && this.menuSelectedRow != undefined) {
-      this.navigatorService.fireOnOpenInTree([this.menuSelectedRow.getItem()]);
+      this.navigatorService.fireOnOpenInTree([Row.getItem(this.menuSelectedRow)]);
     } else if (row) {
-      this.navigatorService.fireOnOpenInTree([row.getItem()]);
+      this.navigatorService.fireOnOpenInTree([Row.getItem(row)]);
     } else {
-      this.navigatorService.fireOnOpenInTree(this.selectedRows.map(r => r.getItem()));
+      this.navigatorService.fireOnOpenInTree(this.selectedRows.map(r => Row.getItem(r)));
     }
   }
 }
diff --git a/nucleus/webclient/src/main/webapp/src/assets/famfamfam_icons/information.png b/nucleus/webclient/src/main/webapp/src/assets/famfamfam_icons/information.png
new file mode 100644
index 0000000..12cd1ae
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/assets/famfamfam_icons/information.png
Binary files differ
diff --git a/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json b/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
index 0917129..39e2ae8 100644
--- a/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
+++ b/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
@@ -125,6 +125,7 @@
       "status-saving": "Speichert...",
 			"status-no-descriptive-data-available": "Keine beschriebenden Daten verfügbar.",
 			"status-no-nodes-available": "Keine Knoten verfügbar.",
+			"status-virtual-node-selected": "Virtueller Knoten ausgewählt. Keine beschriebenden Daten verfügbar.",
 			"tblhdr-measured": "Gemessen",
 			"tblhdr-name": "Name",
       "tblhdr-ordered": "Beauftragt",
@@ -158,6 +159,7 @@
 			"status-loading": "Lädt...",
 			"status-no-descriptive-data-available": "Keine beschreibenden Daten verfügbar.",
 			"status-no-nodes-available": "Keine Knoten verfügbar.",
+			"status-virtual-node-selected": "Virtueller Knoten ausgewählt. Keine beschreibenden Daten verfügbar.",
 			"tblhdr-measured": "Gemessen",
 			"tblhdr-name": "Name",
 			"tblhdr-ordered": "Beauftragt"
@@ -312,6 +314,7 @@
 	},
 	"search": {
 		"edit-searchfields": {
+			"tooltip-no-wildcards": "Im Suchtext sind keine Wildcards erlaubt.",
 			"btn-apply-changes": "Änderungen übernehmen",
 			"enter-search-text": "Suchtext eingeben",
 			"err-cannot-update-selected-node": "Ausgewählter Knoten kann nicht aktualisiert werden.",
@@ -362,15 +365,22 @@
 			"tooltip-edit-search-filter": "Suchfilter bearbeiten",
 			"tooltip-enable-advanced-search": "Erweiterte Suche aktivieren",
 			"tooltip-global-search-attributes": "Globale Suchattribute sind Attribute, die in allen ausgewählten Datenquellen vorhanden sind. Global Suchattribute werden nur einmal dargestellt und die definierte Bedingung wird in allen Datenquellen angewendet.",
+			"tooltip-link-search-attributes-to-navigator": "Suchattribute an den Navigator koppeln.",
 			"tooltip-no-name-set": "Name nicht gesetzt!",
 			"tooltip-reset-search-conditions": "Suchkriterien zurücksetzen",
 			"tooltip-save-search-filter": "Suchfilter speichern",
-			"tooltip-search-text": "Mögliche Operatoren:\n+ steht für einen UND Operator\n| steht für einen ODER Operator\n- negiert einen einzelnen Term\n\" fasst einen Menge von Termen zu einer Phrase zusammen\n* am Ende eines Suchterms beschreibt eine Präfixsuche\n( und ) beschreibt die Operatorpräferenz\n~N nach einem Wort beschreibt den Editierabstand (Unschärfe)\n~N nach einer Phrase beschreibt den Wortabstand\nWenn nach einem der Spezialsymbole gesucht werden soll, müssen diese mit \\ escaped werden.",
-			"tooltip-select-sources": "Quellen auswählen"
+			"tooltip-search-text": "Mögliche Operatoren:\n+ steht für einen UND Operator\n| steht für einen ODER Operator\n- negiert einen einzelnen Term\n\" fasst einen Menge von Termen zu einer Phrase zusammen\n* am Ende eines Suchterms beschreibt eine Präfixsuche\n( und ) beschreibt die Operatorpräferenz\n~N nach einem Wort beschreibt den Editierabstand (Unschärfe)\n~N nach einer Phrase beschreibt den Wortabstand\n\nWenn nach einem der Spezialsymbole gesucht werden soll, müssen diese mit \\ escaped werden.",
+			"tooltip-select-sources": "Quellen auswählen",
+			"tooltip-unlink-search-attributes-from-navigator": "Suchattributen vom Navigator entkoppeln."
 		},
 		"search-condition": {
+			"and": "und",
+			"condition-is-linked": "Dieses Suchbedingung ist an den Navigator gekoppelt. Die Kopplung kann über den entsprechenden Knopf (gleiches Symbol) in der Kopfzeile 'Erweiterte Suche' gelöst werden.",
+			"condition-created-via-link": "Dieses Suchbedingung wurde durch die Kopplung an den Navigator erzeugt. Momentan ist diese Bedingung aber nicht gekoppelt. Bei erneuter Kopplung wird diese Bedingung überschrieben.",
 			"err-cannot-initialize-autocompletion": "Autotvervollständigung kann nicht initialisiert werden.",
-			"tooltip-select-search-operator": "Suchoperator auswählen"
+			"tooltip-select-search-operator": "Suchoperator auswählen",
+			"multi-value-input": "Bitte Werte für Suchattribute eingeben. Die Eingabe von mehreren Werten ist möglich. Für die Operatoren 'like' und '=' werden diese logisch mit einem 'ODER' verknüpft. Für den Operator '!=' werden diese logisch mit einem 'UND' verknüpft.\n\nMögliche Wildcards sind '?' für genau ein beliebiges Zeichen und '*' für eine beliebigen Zeichenkette.",
+			"single-value-input": "Bitte einen Wert für Suchattribute eingeben. Die Eingabe von mehreren Werten ist nicht möglich.\n\nMögliche Wildcards sind '?' für genau ein beliebiges Zeichen und '*' für eine beliebigen Zeichenkette."
 		},
 		"search-datepicker": {
 			"placeholder-dateformat": "TT.MM.JJJJ HH:MM",
@@ -391,7 +401,8 @@
 			"err-cannot-select-node": "Knoten kann nicht ausgewählt werden.",
 			"lbl-selected-attributes": "Ausgewählte Attribute",
 			"no-attributes-selected": "Keine Attribute ausgewählt.",
-			"title-view-editor": "Ansichtseditor"
+			"title-view-editor": "Ansichtseditor",
+			"tooltip-no-wildcards": "Im Suchtext sind keine Wildcards erlaubt."
 		},
 		"tableview": {
 			"err-cannot-calculate-node": "Knoten konnte nicht berechnet werden.",
@@ -437,15 +448,27 @@
       "overall": "Gesamt",
       "step-size": "Schrittweite",
       "preview-values": "Vorschauwerte",
-      "apply": "Übernehmen"
+      "apply": "Übernehmen",
+      "reset": "Zurücksetzen"
     },
     "chart-viewer-nav-card": {
       "load-missing-channels": "{{limit}} Kanäle nachladen ({{offset}}/{{total}})"
     },
     "xy-chart-data-selection-panel": {
-      "select-channel-placeholder": "Kanal wählen",
+			"all-results": "Alle",
+			"apply": "Übernehmen",
+			"cancel": "Abbrechen",
+			"default-result-limit": "Standard",
+			"filter": "Filter",
+			"manual-result-limit": "Maunell",
       "no-x-channel-dialog-header": "Information",
-      "no-x-channel-dialog-message": "Die aktuelle Kanalgruppe hat keine Kanäle die als X-Achse bzw. XY-Achse definiert sind. Wenn Sie auch als Y-Achse definierte Kanäle zulassen möchten, deaktivieren Sie bitte den Filter in der Werkzeugleiste."
+			"no-x-channel-dialog-message": "Die aktuelle Kanalgruppe hat keine Kanäle die als X-Achse bzw. XY-Achse definiert sind. Wenn Sie auch als Y-Achse definierte Kanäle zulassen möchten, deaktivieren Sie bitte den Filter in der Werkzeugleiste.",
+			"result-limit": "Maximale Anzahl Ergebnisse",
+			"result-limit-manual-input": "Maunelle Eingabe max. Anzahl Ergebnisse",
+			"result-offset": "Versatz",
+      "select-channel-placeholder": "Kanal wählen",
+			"tooltip-regexp-filter": "Reguläre Ausdrücke als Filtereingabe möglich.",
+			"y-channel-query-config-editor": "Sucheinstellungen für Y-Kanäle"
     },
     "xy-chart-viewer": {},
     "xy-chart-viewer-nav-card": {
@@ -495,7 +518,8 @@
 		},
 		"file-explorer-nav-card": {
 			"status-node-is-no-files-attachable": "An {{type}}s können keine Dateien angehangen werden.",
-			"status-no-node-selected": "Kein Knoten ausgewählt."
+			"status-no-node-selected": "Kein Knoten ausgewählt.",
+			"status-virtual-node-selected": "Virtueller Knoten ausgewählt."
 		},
 		"file-link-editor-dialog": {
 			"btn-cancel": "Abbrechen",
diff --git a/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json b/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
index 7f59191..7496dc0 100644
--- a/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
+++ b/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
@@ -125,6 +125,7 @@
       "status-saving": "Saving...",
       "status-no-descriptive-data-available": "No descriptive data available.",
       "status-no-nodes-available": "No nodes available.",
+			"status-virtual-node-selected": "Virtual node selected. No descriptive data available.",
       "tblhdr-measured": "Measured",
       "tblhdr-name": "Name",
       "tblhdr-ordered": "Ordered",
@@ -158,6 +159,7 @@
 			"status-loading": "Loading...",
 			"status-no-descriptive-data-available": "No descriptive data available.",
 			"status-no-nodes-available": "No nodes available.",
+			"status-virtual-node-selected": "Virtual node selected. No descriptive data available.",
 			"tblhdr-measured": "Measured",
 			"tblhdr-name": "Name",
 			"tblhdr-ordered": "Ordered"
@@ -312,6 +314,7 @@
 	},
 	"search": {
 		"edit-searchfields": {
+			"tooltip-no-wildcards": "No wildcards allowed in search text.",
 			"btn-apply-changes": "Apply changes",
 			"enter-search-text": "Enter search text",
 			"err-cannot-update-selected-node": "Cannot update the selected node.",
@@ -362,15 +365,22 @@
 			"tooltip-edit-search-filter": "Edit search filter",
 			"tooltip-enable-advanced-search": "Enable advanced search",
 			"tooltip-global-search-attributes": "Global search attributes are attributes present in all data sources selected. Global search attributes are displayed only once and the condition defined in them is applied to all data sources.",
+			"tooltip-link-search-attributes-to-navigator": "Link search attribtues to the navigator.",
 			"tooltip-no-name-set": "No name set!",
 			"tooltip-reset-search-conditions": "Reset search conditions",
 			"tooltip-save-search-filter": "Save search filter",
-			"tooltip-search-text": "Operators available:\n+ represents an AND operator\n| represents an OR operator\n- negates a single term\n\" combines a set of terms into a phrase\n* at the end of a search term describes a prefix search\n( and ) describe the operator preference\n~N after a word describes the edit distance (fuzziness)\n~N after a phrase describes the word distance\nIf one of the special symbols is to be included in a search, they must be escaped with \\.",
-			"tooltip-select-sources": "Select sources"
+			"tooltip-search-text": "Operators available:\n+ represents an AND operator\n| represents an OR operator\n- negates a single term\n\" combines a set of terms into a phrase\n* at the end of a search term describes a prefix search\n( and ) describe the operator preference\n~N after a word describes the edit distance (fuzziness)\n~N after a phrase describes the word distance\n\nIf one of the special symbols is to be included in a search, they must be escaped with \\.",
+			"tooltip-select-sources": "Select sources",
+			"tooltip-unlink-search-attributes-from-navigator": "Unlink search attribtues from the navigator."
 		},
 		"search-condition": {
+			"and": "and",
+			"condition-is-linked": "This search condition is linked to the navigator. It can be unliked via the related button (same symbol) in the 'Advanced Search' header.",
+			"condition-created-via-link": "This search condition was created by the linkage to the navigator. Currently it is not linked to the navigator. If search conditions are linked to navigator again, this condition will be overwritten.",
 			"err-cannot-initialize-autocompletion": "Cannot initialize autocompletion.",
-			"tooltip-select-search-operator": "Select search operator"
+			"tooltip-select-search-operator": "Select search operator",
+			"multi-value-input": "Please enter search values. Multiple input values are allowed. For operators 'like' and '=' the values are connected by a logical 'OR'. However, for operator '!=' the values are connected by a logical 'AND'.\n\nPossible wildcards are '?' for one matching character and '*' for a sequence of matching characters.",
+			"single-value-input": "Please enter a search value. Multiple input values are not allowed.\n\nPossible wildcards are '?' for one matching character and '*' for a sequence of matching characters."
 		},
 		"search-datepicker": {
 			"placeholder-dateformat": "DD.MM.YYYY HH:MM",
@@ -391,7 +401,8 @@
 			"err-cannot-select-node": "Cannot select node.",
 			"lbl-selected-attributes": "Selected attributes",
 			"no-attributes-selected": "No attributes selected.",
-			"title-view-editor": "View editor"
+			"title-view-editor": "View editor",
+			"tooltip-no-wildcards": "No wildcards allowed in search text."
 		},
 		"tableview": {
 			"err-cannot-calculate-node": "Cannot calculate node.",
@@ -437,15 +448,27 @@
       "overall": "overall",
       "step-size": "step size",
       "preview-values": "preview values",
-      "apply": "Apply"
+      "apply": "Apply",
+      "reset": "Reset"
     },
     "chart-viewer-nav-card": {
       "load-missing-channels": "Load {{limit}} channels ({{offset}}/{{total}})"
     },
     "xy-chart-data-selection-panel": {
-      "select-channel-placeholder": "Select Channel",
+			"all-results": "All",
+			"apply": "Apply",
+			"cancel": "Cancel",
+			"default-result-limit": "Default",
+			"filter": "Filter",
+			"manual-result-limit": "Maunal",
       "no-x-channel-dialog-header": "Information",
-      "no-x-channel-dialog-message": "The current ChannelGroup has no Channels defined as X-axis or XY-axis. If you want to also allow Channels marked as Y-axis, toggle the corresponding filter in the toolbar."
+      "no-x-channel-dialog-message": "The current ChannelGroup has no Channels defined as X-axis or XY-axis. If you want to also allow Channels marked as Y-axis, toggle the corresponding filter in the toolbar.",
+			"result-limit": "Maximum number of results",
+			"result-limit-manual-input": "Maunal Input max. no. of results",
+			"result-offset": "Offset",
+			"select-channel-placeholder": "Select Channel",
+			"tooltip-regexp-filter": "Regular expressions are enabled for filtering.",
+			"y-channel-query-config-editor": "Query config for Y-Channels"
     },
     "xy-chart-viewer": {},
     "xy-chart-viewer-nav-card": {
@@ -494,7 +517,8 @@
 		},
 		"file-explorer-nav-card": {
 			"status-node-is-no-files-attachable": "It is not possible to attach files to {{type}}s.",
-			"status-no-node-selected": "No node selected."
+			"status-no-node-selected": "No node selected.",
+			"status-virtual-node-selected": "Virtual node selected."
 		},
 		"file-link-editor-dialog": {
 			"btn-cancel": "Cancel",
diff --git a/nucleus/webclient/src/main/webapp/src/styles.css b/nucleus/webclient/src/main/webapp/src/styles.css
index d95fce8..9774092 100644
--- a/nucleus/webclient/src/main/webapp/src/styles.css
+++ b/nucleus/webclient/src/main/webapp/src/styles.css
@@ -66,6 +66,7 @@
   padding-left: 18px;
   height: 1em;
   margin: 0;
+  background-image: url("assets/famfamfam_icons/information.png");
 }
 .environment {
   background-image: url("assets/famfamfam_icons/database.png");