Merge branch 'mkoller/nodeprovider' into sskoczylas/nodeprovider-select

Change-Id: I606867f02a49538101bfec2cd81d13ec56f3a226
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 aa513ec..c0a9e5c 100644
--- a/api/atfxadapter/openatfx/build.gradle
+++ b/api/atfxadapter/openatfx/build.gradle
@@ -19,7 +19,7 @@
 
 import de.undercouch.gradle.tasks.download.Download
 
-def atfxVersion  = '0.7.4'
+def atfxVersion  = '0.8.10'
 description = 'Downloads openATFX and publishes it to the local maven repository'
 
 dependencies {
@@ -31,6 +31,9 @@
 	compile project(":api:odsadapter")
 	testCompile 'junit:junit:4.12'
 	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 {
@@ -62,9 +65,15 @@
 	outputs.dir file("${buildDir}/openatfx-${atfxVersion}")
 }
 
+
 task copySource(dependsOn: unzipOpenATFX, type: Copy) {
 	from file("${buildDir}/openatfx-${atfxVersion}/src")
 	into 'src'
+	/*
+	 * openATFX is implemented against the ODS API compiled by JacORB. The JacORB IDL compiler generates SelItem#operator() while SunORB IDL compiler generates SelItem#_operator().
+	 * Since openMDM uses SunORB to generate the ODS API from IDL, openATFX source code is missing SelItem#operator(). Therefore we change it with a simple replace and add the underscore.
+	 */
+	filter { line -> line.replaceAll('SelOperator.AND.equals\\(condition.operator\\(\\)', 'SelOperator.AND.equals\\(condition._operator\\(\\)').replaceAll('return new TS_ValueSeq\\(new TS_UnionSeq\\(\\), new short\\[0\\]\\)', 'return ODSHelper.tsValue2tsValueSeq\\(new TS_Value\\[0\\], dt\\)') }
 }
 
 compileJava.dependsOn copySource
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..04a3d5d 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,8 +146,9 @@
 				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());
@@ -152,15 +157,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"));
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/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/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/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/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
index 36b686f..a40ad9e 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
@@ -190,7 +190,7 @@
 		} else if (constant instanceof ScalarType) {
 			return toODSScalarType((ScalarType) constant);
 		} else if (constant instanceof VersionState || constant instanceof Interpolation || constant instanceof AxisType
-				|| constant instanceof TypeSpecification) {
+				|| constant instanceof TypeSpecification || constant instanceof ODSEnum) {
 			// NOTE: Ordinal numbers map directly to the corresponding ODS
 			// enumeration constant value.
 			return constant.ordinal();
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/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 1c93e87..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;
 
@@ -66,17 +66,21 @@
 
 	@POST
 	@Consumes(MediaType.APPLICATION_XML)
-	@Produces(MediaType.APPLICATION_XML)
+	@Produces({ MediaType.APPLICATION_XML, "application/zip" })
 	public Response exportAtfx(ShoppingBasket basket) throws IOException {
 
 		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) {
@@ -96,16 +100,16 @@
 				}
 			};
 
-			return Response.ok(output, MediaType.APPLICATION_XML).build();
+			return Response.ok(output, MediaType.APPLICATION_XML)
+					.header("content-disposition", "attachment; filename = export.atfx").build();
 		} else {
 			StreamingOutput out = new StreamingOutput() {
 				@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];
 
@@ -114,8 +118,6 @@
 								}
 							}
 						}
-					} catch (Exception e) {
-						e.printStackTrace();
 					} finally {
 						if (exportAtfx != null) {
 							try {
@@ -129,7 +131,8 @@
 					}
 				}
 			};
-			return Response.ok(out, "application/zip").build();
+			return Response.ok(out, "application/zip")
+					.header("content-disposition", "attachment; filename = export.zip").build();
 		}
 	}
 
@@ -140,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/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/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/main/resources/emptyAtfx.xml b/nucleus/apicopy/src/main/resources/emptyAtfx.xml
index a6a14e7..a40e264 100644
--- a/nucleus/apicopy/src/main/resources/emptyAtfx.xml
+++ b/nucleus/apicopy/src/main/resources/emptyAtfx.xml
@@ -4338,7 +4338,7 @@
 			<application_attribute>
 				<name>FlagsStartOffset</name>
 				<base_attribute>flags_start_offset</base_attribute>
-				<datatype>DT_LONG</datatype>
+				<datatype>DT_LONGLONG</datatype>
 				<autogenerate>false</autogenerate>
 				<obligatory>false</obligatory>
 				<unique>false</unique>
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/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ContextFilesSubresource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ContextFilesSubresource.java
index 07ffb6c..fd4d49c 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ContextFilesSubresource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ContextFilesSubresource.java
@@ -113,8 +113,8 @@
 
 		return entityService.find(V(sourceName), contextDescribableClass, V(contextDescribableId))
 				.map(contextDescribable -> fileLinkActivity.createFile(sourceName, contextDescribable,
-						ServiceUtils.convertIso8859toUtf8(cdh.getFileName()), fileInputStream, description, mimeType, contextType, contextComponentName,

-						attributeName))
+						ServiceUtils.convertIso8859toUtf8(cdh.getFileName()), fileInputStream, description, mimeType,
+						contextType, contextComponentName, attributeName))
 				.map(fileLink -> ServiceUtils.toResponse(Serializer.serializeFileLink(fileLink), Status.OK)).get();
 	}
 
@@ -135,7 +135,7 @@
 						fileLinkActivity.findFileLinkInContext(remotePath, sourceName, contextDescribable, contextType,
 								contextComponentName, attributeName)))
 				.map(tuple -> Tuple.of(fileLinkActivity.toStreamingOutput(sourceName, tuple._1, tuple._2),
-						tuple._2.getMimeType().toString()))
+						fileLinkActivity.toMediaType(tuple._2)))
 				.map(tuple -> Response.ok(tuple._1, tuple._2).build()).get();
 	}
 
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/FilesAttachableSubresource.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/FilesAttachableSubresource.java
index a75ebc6..2253bee 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/FilesAttachableSubresource.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/FilesAttachableSubresource.java
@@ -109,7 +109,7 @@
 				.map(filesAttachable -> Tuple.of(filesAttachable,
 						fileLinkActivity.findFileLinkAtFileAttachable(remotePath, filesAttachable)))
 				.map(tuple -> Tuple.of(fileLinkActivity.toStreamingOutput(sourceName, tuple._1, tuple._2),
-						tuple._2.getMimeType().toString()))
+						fileLinkActivity.toMediaType(tuple._2)))
 				.map(tuple -> Response.ok(tuple._1, tuple._2).build()).get();
 	}
 
@@ -160,8 +160,8 @@
 			@Parameter(description = "Mimetype of the file", required = true) @FormDataParam("mimeType") MimeType mimeType) {
 
 		return entityService.find(V(sourceName), fileAttachableClass, V(id))
-				.map(fileAttachable -> fileLinkActivity.createFile(sourceName, fileAttachable, ServiceUtils.convertIso8859toUtf8(cdh.getFileName()),

-						fileInputStream, description, mimeType))
+				.map(fileAttachable -> fileLinkActivity.createFile(sourceName, fileAttachable,
+						ServiceUtils.convertIso8859toUtf8(cdh.getFileName()), fileInputStream, description, mimeType))
 				.map(fileLink -> ServiceUtils.toResponse(Serializer.serializeFileLink(fileLink), Status.OK)).get();
 	}
 
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/control/FileLinkActivity.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FileLinkActivity.java
index 8faad3a..c9a3252 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FileLinkActivity.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/control/FileLinkActivity.java
@@ -29,6 +29,7 @@
 import javax.ejb.Stateless;
 import javax.inject.Inject;
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.StreamingOutput;
 
 import org.eclipse.mdm.api.base.Transaction;
@@ -567,4 +568,28 @@
 		return context.getEntityManager()
 				.orElseThrow(() -> new MDMEntityAccessException("Entity manager not present in '" + sourceName + "'."));
 	}
+
+	/**
+	 * Returns the {@link MediaType} for a FileLink. In case of error
+	 * {@link MediaType#APPLICATION_OCTET_STREAM_TYPE} is returned
+	 * 
+	 * @param fileLink
+	 * @return MediaType corresponding to the FileLink's mimetype
+	 */
+	public MediaType toMediaType(FileLink fileLink) {
+		String mimeType = fileLink.getMimeType().toString();
+
+		if (mimeType == null || mimeType.isEmpty()) {
+			return MediaType.APPLICATION_OCTET_STREAM_TYPE;
+		}
+
+		try {
+			return MediaType.valueOf(mimeType);
+		} catch (IllegalArgumentException e) {
+			// if the mimeType is not parsable to a MediaType, just application/octet-stream
+			// is returned
+			return MediaType.APPLICATION_OCTET_STREAM_TYPE;
+		}
+
+	}
 }
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 92ff08e..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,12 +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;
@@ -254,10 +257,9 @@
 			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!"))
+			return availableEntityTypes.stream().filter(filterByEntityTypeName).findAny()
+					.orElseThrow(
+							() -> new IllegalArgumentException("Entity " + typeName + " not found in data source!"))
 					.getAttribute(attributeName);
 		}
 
@@ -501,6 +503,9 @@
 		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() + "\"");
 			}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
index 3eaf779..f41fb33 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
@@ -58,6 +58,7 @@
 import io.vavr.collection.HashMap;
 import io.vavr.collection.List;
 import io.vavr.collection.Map;
+import io.vavr.control.Option.Some;
 import io.vavr.control.Try;
 
 /**
@@ -400,26 +401,38 @@
 		return ec;
 	}
 
+	@SuppressWarnings("unchecked")
 	private java.util.Map<ContextType, ContextRoot> updateContextDescribableContext(
 			ContextDescribable contextDescribable, Map<ContextType, Object> rootMap, ContextType[] contextTypes) {
 		java.util.Map<ContextType, ContextRoot> existingRootMap = getEntityManager(contextDescribable.getSourceName())
 				.loadContexts(contextDescribable, contextTypes);
 
 		for (ContextType contextType : contextTypes) {
-			ContextRoot existingRoot = existingRootMap.computeIfAbsent(contextType,
-					ct -> createContextRoot(contextDescribable, ct));
-
-			rootMap.get(contextType).forEach(newContextRoot -> updateContextRoot(existingRoot, newContextRoot));
+			boolean hasValues;
+			Object obj = rootMap.get(contextType);
+			if (obj instanceof Some && ((Some) obj).get() instanceof ArrayList && !((Some<ArrayList<?>>) obj).get().isEmpty()) {
+				hasValues = true;
+			} else {
+				hasValues = false;
+			}
+		    ContextRoot existingRoot = existingRootMap.computeIfAbsent(contextType,
+				ct -> createContextRoot(contextDescribable, ct, hasValues));
+		    rootMap.get(contextType).forEach(newContextRoot -> updateContextRoot(existingRoot, newContextRoot));
 		}
 		return existingRootMap;
 	}
 
-	private ContextRoot createContextRoot(ContextDescribable contextDescribable, ContextType contextType) {
+	private ContextRoot createContextRoot(ContextDescribable contextDescribable, ContextType contextType, boolean hasValues) {
 		EntityFactory factory = getEntityFactory(contextDescribable.getSourceName());
 
 		TemplateRoot templateRoot = findTemplateRoot(contextDescribable, contextType);
 
-		if (contextDescribable instanceof Measurement) {
+	    if (templateRoot == null) {
+	    	if (hasValues) {
+	    		throw new MDMEntityAccessException("Found measurements for '" + contextType + "' without a Template defined!");
+	    	}
+	    	return null;
+	    } else if (contextDescribable instanceof Measurement) {
 			return factory.createContextRoot((Measurement) contextDescribable, templateRoot);
 		} else if (contextDescribable instanceof TestStep) {
 			return factory.createContextRoot((TestStep) contextDescribable, templateRoot);
@@ -456,8 +469,7 @@
 		TemplateTestStep tpl = TemplateTestStep.of(testStep).orElseThrow(
 				() -> new MDMEntityAccessException("Cannot find TemplateTestStep for TestStep: " + testStep));
 
-		return tpl.getTemplateRoot(contextType).orElseThrow(() -> new MDMEntityAccessException(
-				"Cannot find TemplateRoot for ContextType " + contextType + " on Template TestStep " + tpl));
+		return tpl.getTemplateRoot(contextType).orElse(null);
 	}
 
 	public TestStep getTestStep(ContextDescribable contextDescribable) {
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
index 9b65df3..d85c578 100644
--- 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
@@ -1,36 +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;
 
-class AttributeContainer {
+public class AttributeContainer {
 
-    private ContextState contextState;
-    private Attribute attribute;
-    private boolean valid;
-    private Object value;
+	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 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 ContextState getContextState() {
+		return contextState;
+	}
 
-    public Attribute getAttribute() {
-        return attribute;
-    }
+	public Attribute getAttribute() {
+		return attribute;
+	}
 
-    public boolean isValid() {
-        return valid;
-    }
+	public boolean isValid() {
+		return valid;
+	}
 
-    public Object getValue() {
-        return value;
-    }
+	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
index 7062bfd..7c93adb 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/DefaultNodeProvider.java
@@ -1,3 +1,17 @@
+/********************************************************************************
+ * 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;
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
index 7664d24..8d55b15 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProvider.java
@@ -1,5 +1,21 @@
+/********************************************************************************

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

@@ -8,6 +24,8 @@
 import java.util.Set;

 import java.util.stream.Collectors;

 

+import javax.el.ValueExpression;

+

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

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

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

@@ -42,6 +60,7 @@
 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;

 

@@ -60,6 +79,8 @@
 

 	private NodeProviderRoot root;

 

+	private MDMExpressionLanguageService elService;

+

 	/**

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

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

@@ -67,9 +88,11 @@
 	 * @param connectorService

 	 * @param root

 	 */

-	public GenericNodeProvider(ConnectorService connectorService, NodeProviderRoot root) {

+	public GenericNodeProvider(ConnectorService connectorService, NodeProviderRoot root,

+			MDMExpressionLanguageService expressionLanguageService) {

 		this.connectorService = connectorService;

 		this.root = root;

+		this.elService = expressionLanguageService;

 	}

 

 	/**

@@ -113,13 +136,10 @@
 	@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))) {

@@ -155,7 +175,8 @@
 

 		if (isEnvironment(childNodeLevel.getEntityType())) {

 			return createQueryForNodeLevel(context, childNodeLevel).fetch().stream()

-					.map(result -> convertNode(result, sourceName, childNodeLevel, filter, Aggregation.NONE)).collect(Collectors.toList());

+					.map(result -> convertNode(result, sourceName, childNodeLevel, filter, Aggregation.NONE))

+					.collect(Collectors.toList());

 		}

 

 		if (!sourceName.equalsIgnoreCase(parent.getSource()) || !sourceNameMatches(filter, sourceName)) {

@@ -164,12 +185,15 @@
 

 		if (childNodeLevel.isVirtual()) {

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

-			// TODO: What about contextSate of childNode?

-			List<Result> results = getSearchService(context).getFilterResults(TestStep.class, childNodeLevel.getLabelAttribute(), filter, childNodeLevel.getContextState());

+			List<Result> results = getSearchService(context).getFilterResults(TestStep.class,

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

 

 			// Filter values are always in measured

 			return results.stream()

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

+					.map(result -> convertLabelAttributesToContainers(result, Aggregation.DISTINCT, childNodeLevel))

+					.collect(toMap(v -> createBasicLabel(childNodeLevel.getContextState(), v), v -> v, (v1, v2) -> v1))

+					.values().stream()

+					.map(containers -> convertNodeByLabel(containers, sourceName, childNodeLevel, filter))

 					.collect(Collectors.toList());

 		} else {

 			return fetchNodes(context, childNodeLevel, filter);

@@ -209,11 +233,11 @@
 		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.getLabelAttribute(), childFilter, "");

+			List<Result> results = getSearchService(context).fetchResults(TestStep.class,

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

 

 			for (Result r : results) {

-				Node newNode = convertNode(r, node.getSource(), parentLevel, childFilter, Aggregation.NONE);

-				nodes.add(newNode);

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

 				break;

 			}

 		} else {

@@ -285,7 +309,7 @@
 		List<Attribute> attributes = getAttributesFromNodeLevel(nodeLevel);

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

 

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

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

 				.collect(Collectors.toList());

 	}

 

@@ -372,45 +396,86 @@
 	/**

 	 * Converts a Result to a Node

 	 * 

-	 * @param result            Result to convert

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

-		});

+	private Node convertNode(Result result, String sourceName, NodeLevel nodeLevel, Filter parentFilter,

+			Aggregation aggregation) {

+

+		ContextState contextState = nodeLevel.getContextState();

+

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

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

+

+		String label = createLabel(nodeLevel, labelAttrContainers);

+

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

+		String id = idValue.extract();

+

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

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

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

+

+		return convertNode(id, label, sourceName, nodeLevel.getEntityType(), getFilterAttribute(nodeLevel).getName(),

+				newFilter);

 	}

 

 	/**

 	 * Converts a Result to a Node

 	 *

-	 * @param result            Result to convert

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

+	private Node convertNodeByLabel(List<AttributeContainer> labelAttrContainers, String sourceName,

+			NodeLevel nodeLevel, Filter parentFilter) {

 		ContextState contextState = nodeLevel.getContextState();

 

-		List<AttributeContainer> labelAttrContainers = convertToAttributeContainers(contextState, result, aggregation, nodeLevel.getLabelAttribute());

+		String label = createLabel(nodeLevel, labelAttrContainers);

 

-		String label = crateNodeLabel(nodeLevel.getContextState(), labelAttrContainers);

+		/**

+		 * TODO jst, 05.02.2021: This is counterintuitive. LabelAttributes are used for

+		 * filtering..

+		 */

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

 

-		List<AttributeContainer> filterAttrContainers = convertToAttributeContainers(contextState, result, aggregation, nodeLevel.getFilterAttribute());

-

-		Filter newFilter = getNewNodeFilter(contextState, nodeLevel.getEntityType(), parentFilter, filterAttrContainers);

-

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

+		return convertNode(null, label, sourceName, nodeLevel.getEntityType(), getFilterAttribute(nodeLevel).getName(),

+				newFilter);

 	}

 

-	private String crateNodeLabel(ContextState contextState, List<AttributeContainer> attributeContainers) {

-		String labelValue = attributeContainers.stream()

-				.map(this::mapAttributeContainerValue)

+	/**

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

@@ -420,9 +485,17 @@
 		}

 	}

 

-	private List<AttributeContainer> convertToAttributeContainers(ContextState contextState, Result result, Aggregation aggregation, List<Attribute> attributes) {

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

@@ -448,33 +521,32 @@
 	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);

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

-		attributes.addAll(nodeLevel.getLabelAttribute());

-		

+		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 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, EntityType entityType, Filter parentFilter, List<AttributeContainer> attributeContainers) {

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

+			List<AttributeContainer> attributeContainers) {

 		Filter newFilter = null;

 

-		if (isEnvironment(entityType)) {

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

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

 			newFilter = Filter.and();

 		} else if (parentFilter != null) {

@@ -482,10 +554,11 @@
 

 			for (AttributeContainer attributeContainer : attributeContainers) {

 				if (attributeContainer.isValid()) {

-					newFilter.add(ComparisonOperator.EQUAL

-							.create(contextState, attributeContainer.getAttribute(), attributeContainer.getValue()));

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

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

 				} else {

-					newFilter.add(ComparisonOperator.IS_NULL.create(contextState, attributeContainer.getAttribute(), null));

+					newFilter.add(

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

 				}

 			}

 		}

@@ -515,11 +588,13 @@
 	}

 

 	private Attribute getFilterAttribute(NodeLevel nodeLevel) {

-		return nodeLevel.getFilterAttribute().stream().findFirst().orElseThrow(() -> new IllegalStateException("woops?!"));

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

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

 	}

 

 	private Attribute getLabelAttribute(NodeLevel nodeLevel) {

-		return nodeLevel.getLabelAttribute().stream().findFirst().orElseThrow(() -> new IllegalStateException("woops?!"));

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

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

 	}

 

 	/**

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..b6e56d2
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/MDMExpressionLanguageService.java
@@ -0,0 +1,94 @@
+/********************************************************************************
+ * 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.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.entity.NodeLevel;
+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 ELProcessor processor;
+	private 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.toString());
+			}
+		}
+		;
+	}
+
+	/**
+	 * Parses {@link ValueExpression} of given {@link NodeLevel} if present.
+	 *
+	 * @param nodeLevel the node level
+	 * @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);
+		}
+		return valueExpression.getValue(context).toString();
+	}
+}
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
index f182944..0a258ee 100644
--- 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
@@ -1,3 +1,17 @@
+/********************************************************************************

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

 

 /**

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
index 4fbd8ff..5920a4a 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/control/NodeProviderRepository.java
@@ -24,7 +24,6 @@
 import javax.enterprise.context.SessionScoped;

 import javax.inject.Inject;

 

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

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

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

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

@@ -43,7 +42,7 @@
 

 	private static final long serialVersionUID = -3081666937067348673L;

 

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

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

 

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

 

@@ -86,11 +85,14 @@
 		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));

+				putNodeProvider(root.getId(),

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

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

 			} catch (RuntimeException e) {

 				e.printStackTrace();

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..c20fa2e
--- /dev/null
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/AttributeContainerListResovler.java
@@ -0,0 +1,77 @@
+/********************************************************************************
+ * 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);
+		if (resolved.isPresent()) {
+			context.setPropertyResolved(base, property);
+		}
+		return resolved.orElse(null);
+	}
+
+	@Override
+	public Class<?> getType(ELContext context, Object base, Object property) {
+		Optional<Object> resolved = getValue(context, property);
+		if (resolved.isPresent()) {
+			return resolved.get().getClass();
+		} else {
+			return 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(property.toString()))
+				.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
index 614617a..36706c4 100644
--- 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
@@ -1,9 +1,26 @@
+/********************************************************************************

+ * 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 java.util.stream.Collectors;

+

+import javax.el.ValueExpression;

 

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

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

@@ -26,8 +43,10 @@
 

 	private EntityType type;

 

-	private List<Attribute> filterAttribute;

-	private List<Attribute> labelAttribute;

+	private List<Attribute> filterAttributes;

+	private List<Attribute> labelAttributes;

+	private ValueExpression labelExpression;

+	private ValuePrecision valuePrecision;

 	private List<SortAttribute> orderAttributes;

 

 	private ContextState contextState = null;

@@ -68,7 +87,7 @@
 	 * @param orderAttributes

 	 */

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

-	                 List<SortAttribute> orderAttributes) {

+			List<SortAttribute> orderAttributes) {

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

 	}

 

@@ -77,14 +96,12 @@
 	 * label attributes with the given list of order attributes.

 	 *

 	 * @param type

-	 * @param filterAttribute

-	 * @param labelAttribute

+	 * @param filterAttributes

+	 * @param labelAttributes

 	 */

-	public NodeLevel(EntityType type, List<Attribute> filterAttribute, List<Attribute> labelAttribute) {

-		this.type = type;

-		this.filterAttribute = filterAttribute;

-		this.labelAttribute = labelAttribute;

-		this.orderAttributes = labelAttribute.stream().map(SortAttribute::new).collect(Collectors.toList());

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

+		this(type, filterAttributes, labelAttributes,

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

 	}

 

 	/**

@@ -97,11 +114,12 @@
 	 * @param orderAttributes

 	 */

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

-	                 List<SortAttribute> orderAttributes) {

+			List<SortAttribute> orderAttributes) {

 		this.type = type;

-		this.filterAttribute = filterAttribute;

-		this.labelAttribute = labelAttribute;

+		this.filterAttributes = filterAttribute;

+		this.labelAttributes = labelAttribute;

 		this.orderAttributes = Lists.newArrayList(orderAttributes);

+		this.valuePrecision = ValuePrecision.EXACT;

 	}

 

 	/**

@@ -149,15 +167,15 @@
 	/**

 	 * @return the ID attribute

 	 */

-	public List<Attribute> getFilterAttribute() {

-		return filterAttribute;

+	public List<Attribute> getFilterAttributes() {

+		return filterAttributes;

 	}

 

 	/**

 	 * @return the label attribute

 	 */

-	public List<Attribute> getLabelAttribute() {

-		return labelAttribute;

+	public List<Attribute> getLabelAttributes() {

+		return labelAttributes;

 	}

 

 	/**

@@ -186,14 +204,37 @@
 	 */

 	@Override

 	public String toString() {

-		return MoreObjects.toStringHelper(NodeLevel.class)

-				.add("type", type)

-				.add("filterAttribute", filterAttribute)

-				.add("labelAttribute", labelAttribute)

-				.add("orderAttribute", orderAttributes)

-				.add("contextState", contextState)

-				.add("isVirtual", isVirtual)

-				.add("child", child)

-				.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
index b66da44..60c3274 100644
--- 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
@@ -1,3 +1,17 @@
+/********************************************************************************

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

diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
index aa88e70..7d7ba72 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/NodeProviderRoot.java
@@ -1,3 +1,17 @@
+/********************************************************************************

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

@@ -163,8 +177,18 @@
 	 * @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.getFilterAttribute().stream().findFirst().map(Attribute::getName).orElse(null);

+		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
index b85e2fa..b3c9cc7 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/entity/Order.java
@@ -1,3 +1,17 @@
+/********************************************************************************

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

 

 /**

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
index 18abe11..0819a37 100644
--- 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
@@ -1,3 +1,17 @@
+/********************************************************************************

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

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
index c846336..5592838 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/nodeprovider/utils/SerializationUtil.java
@@ -1,3 +1,17 @@
+/********************************************************************************

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

@@ -18,6 +32,7 @@
 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;

@@ -226,8 +241,11 @@
 	public static NodeLevelDTO convert(NodeLevel nl) {

 		NodeLevelDTO nld = new NodeLevelDTO();

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

-		nld.setFilterAttribute(Arrays.asList(getFilterAttribute(nl)));

-		nld.setLabelAttribute(Arrays.asList(getLabelAttribute(nl)));

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

@@ -235,6 +253,7 @@
 		if (nl.getChild() != null) {

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

 		}

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

 		return nld;

 	}

 

@@ -250,12 +269,10 @@
 		ModelManager mm = context.getModelManager().get();

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

 

-		List<Attribute> filterAttributes = nld.getFilterAttribute().stream()

-				.map(e::getAttribute)

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

 				.collect(Collectors.toList());

 

-		List<Attribute> labelAttributes = nld.getLabelAttribute().stream()

-				.map(e::getAttribute)

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

 				.collect(Collectors.toList());

 

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

@@ -264,7 +281,9 @@
 		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()));

 		}

@@ -272,10 +291,12 @@
 	}

 

 	private static String getFilterAttribute(NodeLevel nodeLevel) {

-		return nodeLevel.getFilterAttribute().stream().map(Attribute::getName).findFirst().orElseThrow(() -> new IllegalStateException("woops?!"));

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

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

 	}

 

 	private static String getLabelAttribute(NodeLevel nodeLevel) {

-		return nodeLevel.getLabelAttribute().stream().map(Attribute::getName).findFirst().orElseThrow(() -> new IllegalStateException("woops?!"));

+		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
index 62d5cf4..498bdda 100644
--- 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
@@ -1,3 +1,17 @@
+/********************************************************************************

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

@@ -7,6 +21,7 @@
 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;

@@ -19,8 +34,10 @@
 public class NodeLevelDTO {

 	private String type;

 

-	private List<String> filterAttribute;

-	private List<String> labelAttribute;

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

@@ -46,29 +63,29 @@
 	/**

 	 * @return the idAttribute

 	 */

-	public List<String> getFilterAttribute() {

-		return filterAttribute;

+	public List<String> getFilterAttributes() {

+		return filterAttributes;

 	}

 

 	/**

-	 * @param filterAttribute the idAttribute to set

+	 * @param filterAttributes the idAttribute to set

 	 */

-	public void setFilterAttribute(List<String> filterAttribute) {

-		this.filterAttribute = filterAttribute;

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

+		this.filterAttributes = filterAttributes;

 	}

 

 	/**

 	 * @return the labelAttribute

 	 */

-	public List<String> getLabelAttribute() {

-		return labelAttribute;

+	public List<String> getLabelAttributes() {

+		return labelAttributes;

 	}

 

 	/**

-	 * @param labelAttribute the labelAttribute to set

+	 * @param labelAttributes the labelAttribute to set

 	 */

-	public void setLabelAttribute(List<String> labelAttribute) {

-		this.labelAttribute = labelAttribute;

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

+		this.labelAttributes = labelAttributes;

 	}

 

 	/**

@@ -126,4 +143,34 @@
 	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
index f75f1dd..1d23d2b 100644
--- 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
@@ -1,3 +1,17 @@
+/********************************************************************************

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

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/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/nodeprovider/control/GenericNodeProviderTest.java b/nucleus/businessobjects/src/test/java/org/eclipse/mdm/nodeprovider/control/GenericNodeProviderTest.java
index 6c50e63..a737579 100644
--- 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
@@ -1,5 +1,6 @@
 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;

@@ -109,8 +110,10 @@
 		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);

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

 	}

 

 	@Test

@@ -318,31 +321,31 @@
 

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

 		NodeLevel env = new NodeLevel(etEnv);

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

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

 

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

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

+		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().add(new SortAttribute(vehicleModel.getLabelAttribute()));

+		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().add(new SortAttribute(test.getLabelAttribute()));

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

 

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

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

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

 

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

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

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

 

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

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

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

 

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

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

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

 

 		env.setChild(project);

 		project.setChild(vehicleModel);

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
index b7ce35a..3aca80c 100644
--- 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
@@ -6,6 +6,7 @@
 

 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;

@@ -57,49 +58,49 @@
 		NodeLevel nl0 = np.getNodeLevel(context, "Environment", null);

 

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

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

+		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.getIdAttribute().getName()).isEqualTo("Id");

+		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.getIdAttribute().getName()).isEqualTo("model");

+		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.getIdAttribute().getName()).isEqualTo("Id");

+		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.getIdAttribute().getName()).isEqualTo("Id");

+		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.getIdAttribute().getName()).isEqualTo("Id");

+		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.getIdAttribute().getName()).isEqualTo("Id");

+		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.getIdAttribute().getName()).isEqualTo("Id");

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

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

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

 	}

diff --git a/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql b/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql
index 25fac8d..41bca31 100644
--- a/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql
+++ b/nucleus/businessobjects/src/test/resources/nodeprovider_example.sql
@@ -1,61 +1,68 @@
 INSERT INTO openmdm.preference (keycol,"source",username,valuecol) VALUES 

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

-  "id" : "generic_measured",

-  "name" : "Measured",

-  "contexts" : {

-    "*" : {

-      "type" : "Environment",

-      "filterAttribute" : ["Id"],

-      "labelAttribute" : ["Name"],

-      "child" : {

-        "type" : "Project",

-        "filterAttribute" : ["Id"],

-        "labelAttribute" : ["Name", "Id"],

-        "child" : {

-          "type" : "vehicle",

-          "filterAttribute" : ["model"],

-          "labelAttribute" : ["model", "Id"],

-          "orderAttributes" : {

-            "model" : "ASCENDING"

-          },

-          "contextState" : "MEASURED",

-          "child" : {

-            "type" : "Test",

-            "filterAttribute" : ["Id"],

-            "labelAttribute" : ["Name"],

-            "child" : {

-              "type" : "TestStep",

-              "filterAttribute" : ["Id"],

-              "labelAttribute" : ["Name"],

-              "child" : {

-                "type" : "Measurement",

-                "filterAttribute" : ["Id"],

-                "labelAttribute" : ["Name"],

-                "child" : {

-                  "type" : "ChannelGroup",

-                  "filterAttribute" : ["Id"],

-                  "labelAttribute" : ["Name"],

-                  "child" : {

-                    "type" : "Channel",

-                    "filterAttribute" : ["Id"],

-                    "labelAttribute" : ["Name"],

-                    "virtual" : false

-                  },

-                  "virtual" : false

-                },

-                "virtual" : false

-              },

-              "virtual" : false

-            },

-            "virtual" : false

-          },

-          "virtual" : true

-        },

-        "virtual" : false

-      },

-      "virtual" : false

-    }

-  }

+	"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
index 4cb4274..894972c 100644
--- a/nucleus/businessobjects/src/test/resources/nodeprovider_generic.json
+++ b/nucleus/businessobjects/src/test/resources/nodeprovider_generic.json
@@ -1,53 +1,63 @@
 {

-	"id" : "generic",

-	"name" : "Generic Default",

+	"id" : "generic_measured",

+	"name" : "Generic Measured",

 	"contexts" : {

 	    "*" : {

 		    "type" : "Environment",

-		    "filterAttribute" : ["Id"],

-		    "labelAttribute" : ["Name"],

+		    "filterAttributes" : ["Id"],

+		    "labelAttributes" : ["Name"],

 		    "virtual" : false,

 		    "child" : {

 		        "type" : "Project",

-		        "filterAttribute" : ["Id"],

-		        "labelAttribute" : ["Name", "Id"],

+		        "filterAttributes" : ["Id"],

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

 		        "virtual" : false,

 		        "child" : {        

 			        "type" : "vehicle",

-			        "filterAttribute" : ["model"],

-			        "labelAttribute" : ["model"],

+			        "filterAttributes" : ["model"],

+			        "labelAttributes" : ["model"],

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

 			        "virtual" : true,

 			        "contextState": "MEASURED",

 			        "child" : {

-			            "type" : "Test",

-			            "filterAttribute" : ["Id"],

-			            "labelAttribute" : ["Name"],

-			            "virtual" : false,

-			            "child" : {

-			                "type" : "TestStep",

-			                "filterAttribute" : ["Id"],

-			                "labelAttribute" : ["Name"],

-			                "virtual" : false,

-			                "child" : {

-			                    "type" : "Measurement",

-			                    "filterAttribute" : ["Id"],

-			                    "labelAttribute" : ["Name"],

-			                    "virtual" : false,

-			                    "child" : {

-			                        "type" : "ChannelGroup",

-			                        "filterAttribute" : ["Id"],

-			                        "labelAttribute" : ["Name"],

-			                        "virtual" : false,

-			                        "child" : {

-			                            "type" : "Channel",

-			                            "filterAttribute" : ["Id"],

-			                            "labelAttribute" : ["Name"],

-			                            "virtual" : false

-			                        }

-			                    }

-			                }

-			            }

-		            }

+						"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/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 2e8d4a1..942238e 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
@@ -124,9 +124,8 @@
 			em.flush();
 			return convert(pe);
 		} else {
-			throw new PreferenceException(
-					"Only users with role " + ADMIN_ROLE
-							+ " are allowed to save Preferences outside of the USER scope!");
+			throw new PreferenceException("Only users with role " + ADMIN_ROLE
+					+ " are allowed to save Preferences outside of the USER scope!");
 		}
 
 	}
@@ -145,9 +144,8 @@
 	}
 
 	private boolean isAllowed(Preference preference) {
-		return sessionContext.isCallerInRole(ADMIN_ROLE)
-				|| (preference.getUser() != null
-						&& preference.getUser().equalsIgnoreCase(sessionContext.getCallerPrincipal().getName()));
+		return sessionContext.isCallerInRole(ADMIN_ROLE) || (preference.getUser() != null
+				&& preference.getUser().equalsIgnoreCase(sessionContext.getCallerPrincipal().getName()));
 	}
 
 	private PreferenceMessage convert(Preference pe) {
diff --git a/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts b/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
index 783a868..d9cc26b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
@@ -14,7 +14,7 @@
 
 
 import {Injectable, Output, EventEmitter} from '@angular/core';
-import { Http, Response, Headers, RequestOptions } from '@angular/http';
+import { Http, Response, Headers, RequestOptions, ResponseContentType } from '@angular/http';
 
 import {of as observableOf, Observable} from 'rxjs';
 import {defaultIfEmpty, catchError, mergeMap, map} from 'rxjs/operators';
@@ -116,8 +116,7 @@
     headers.append('Content-Type', 'application/xml');
 
     return this.getBasketAsXml(basket)
-      .flatMap(b => this.http.post(this._prop.getUrl('mdm/export'), b, { headers: headers }))
-      .map(r => r.text());
+      .flatMap(b => this.http.post(this._prop.getUrl('mdm/export'), b, { responseType: ResponseContentType.Blob, headers: headers }));
   }
 
   getFileExtensions() {
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 40fa2ba..2ef1da6 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
@@ -309,9 +309,13 @@
   exportItemsToAtfx(basket: MDMItem[]) {
     let downloadContent = new Basket(this.basketName, basket);
 
-    this._basketService.getBasketAsAtfx(downloadContent)
-      .map(atfx => new Blob([atfx], { type: 'application/xml' }))
-      .subscribe(atfx => this.saveAsFile(atfx, '.atfx'));
+    this._basketService.getBasketAsAtfx(downloadContent).pipe(
+      map(resp => { 
+        var contentDisposition = resp.headers.get('content-disposition');
+        var filename = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
+        return { filename: filename, data: resp.blob() }; })
+    )
+    .subscribe( (r: { filename: string, data: Blob }) => FileSaver.saveAs(r.data, r.filename));
   }
 
   saveAsFile(blob: Blob, fileExtension: string, defaultFilename = 'export') {
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..849fce2 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,7 @@
 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 { TooltipModule } from 'primeng/primeng';
 
 @NgModule({
   imports: [
@@ -59,6 +60,7 @@
     ListboxModule,
     PanelModule,
     ConfirmDialogModule,
+    TooltipModule
   ],
   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 48dd678..729dae5 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,17 +14,17 @@
 
 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';
 
 import { HttpErrorHandler } from '../../../core/http-error-handler';
 import { Node } from '../../../navigator/node';
-import { ChartViewerDataService, getDataArray } from '../../services/chart-viewer-data.service';
+import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
 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({
   selector: 'mdm5-chartViewer',
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/datatable/data-table.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/datatable/data-table.component.ts
index df8dec9..09a26bc 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/datatable/data-table.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/datatable/data-table.component.ts
@@ -18,9 +18,10 @@
 import { Table } from 'primeng/table';
 
 import { Node } from '../../../navigator/node';
-import { ChartViewerDataService, getDataArray } from '../../services/chart-viewer-data.service';
+import { ChartViewerDataService } from '../../services/chart-viewer-data.service';
 import { MeasuredValues } from '../../model/chartviewer.model';
 import { Measurement } from '../../model/types/measurement.class';
+import { getDataArray } from '../../model/types/measured-values.class';
 
 @Component({
   selector: 'mdm5-dataTable',
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..53ee8a2 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;
@@ -48,6 +48,7 @@
     private chartService: ChartViewerDataService) { }
 
   ngOnInit() {
+    this.initSettings();
     this.reload();
     this.emitRequestOptionsChanged();
   }
@@ -59,6 +60,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 +96,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..692ef09
--- /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,36 @@
+/********************************************************************************
+ * 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
+ *
+ ********************************************************************************/
+ 
+.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;
+}
\ 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..ffabd1f 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,15 +11,26 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************-->
-
 <div class="p-grid">
-  <div class="p-col-12" style="margin-top: 4px">
+  <div class="p-col-12" style="margin-top: 4px;">
+    <!-- Filter for Listbox. Is overlay via absolute positioning. Enables regex filter, which is not available in prime component -->
+    <div class="filter-container">
+      <span class="filter-input-group">
+        <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>
+      </span>
+    </div>
     <p-listbox
-      [options]="measurement.channelGroups"
+      [options]="channelGroupOptions"
       [(ngModel)]="selectedChannelGroups"
       multiple="multiple"
       checkbox="checkbox"
-      filter="filter"
       optionLabel="name"
       (onChange)="onSelectedChannelGroupsChanged($event)"
       [ngClass]="{'hiddenList': hiddenGroups}"
@@ -36,18 +47,30 @@
     </p-listbox>
   </div>
   <div class="p-col-12">
-    <p-listbox [options]="yChannelOptions"
+    <!-- Filter for Listbox. Is overlay via absolute positioning. Enables regex filter, which is not available in prime component -->
+    <div class="filter-container">
+      <span class="filter-input-group">
+        <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>
+      </span>
+    </div>
+    <p-listbox [options]="yChannelOptionsFiltered"
       [(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">
+      [showToggleAll]="true">
       <p-header>
         <span class="icon channel"></span>
         Y-{{'MeaQuantity' | mdmdatasourcetranslate}}
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..663a013 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,7 +20,6 @@
 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 { TranslateService } from '@ngx-translate/core';
@@ -31,7 +30,7 @@
 @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 +47,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[] = [];
@@ -65,6 +67,10 @@
   public hiddenGroups = false;
   public hiddenYChannels = false;
 
+  // Filterstrings
+  public channelGroupFilter: string;
+  public yChannelFilter: string;
+
   // for x-channel default value
   private independentChannels = new Map<string, Channel>();
 
@@ -97,6 +103,9 @@
         this.selectionChanged = false;
       }
     }
+    if(changes['measurement']) {
+      this.initChannelGroupOptions();
+    }
   }
 
   /**************** Html-template listeners *****************************/
@@ -169,6 +178,18 @@
     this.hiddenYChannels = !this.hiddenYChannels;
   }
 
+  public onFilterChannelGroups(event: KeyboardEvent ) {
+    this.initChannelGroupOptions();
+  }
+
+  public onFilterYChannels(event: KeyboardEvent ) {
+    this.filterYChannelOptions();
+  }
+
+  
+
+  /**************** Private methods *****************************/
+
   private handleDeselectChannelGroup(channelGroup: ChannelGroup) {
     // remove rows
     if (this.arrayUtil.isNotEmpty(this.selectedChannelRows)) {
@@ -196,11 +217,13 @@
     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.initChannelGroupOptions();
           this.addChannelGroupToSelection(node.id, this.measurement.findChannelGroup(node.id));
           break;
         case TYPE_CHANNEL:
@@ -315,6 +338,7 @@
         this.yChannelOptions = this.yChannelOptions.concat(channels);
       }
     }
+    this.filterYChannelOptions()
   }
 
   /**
@@ -326,6 +350,7 @@
     this.setSelectedYChannels([]);
     this.xChannelOptions = {};
     this.yChannelOptions = [];
+    this.yChannelOptionsFiltered = [];
   }
 
     /**
@@ -362,7 +387,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 +401,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))
   }
 
   /**
@@ -434,4 +460,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.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..7b7cff0 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,17 +17,12 @@
 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';
 
 @Component({
   selector: 'app-xy-chart-viewer-nav-card',
@@ -45,14 +40,13 @@
   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)
     );
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/chartviewer.model.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/chartviewer.model.ts
index 82089da..3b10b16 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/chartviewer.model.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/chartviewer.model.ts
@@ -15,6 +15,7 @@
 export { BooleanArray } from './types/boolean-array.class';

 export { DateArray } from './types/date-array.class';

 export { NumberArray } from './types/number-array.class';

+export { StringArray } from './types/string-array.class';

 export { MeasuredValues } from './types/measured-values.class';

 export { MeasuredValuesResponse } from './types/measured-values-response.class';

 export { PreviewValueList } from './types/preview-value-list.class';

diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/measured-values.class.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/measured-values.class.ts
index 5f3ab7c..8616179 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/measured-values.class.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/measured-values.class.ts
@@ -15,6 +15,7 @@
 import { NumberArray } from './number-array.class';

 import { BooleanArray } from './boolean-array.class';

 import { DateArray } from './date-array.class';

+import { StringArray } from './string-array.class';

 

 export class MeasuredValues {

   name: string;

@@ -23,7 +24,7 @@
   independent: boolean;

   axisType: string;

   scalarType: string;

-  stringArray: string[];

+  stringArray: StringArray;

   dateArray: DateArray;

   booleanArray: BooleanArray;

   // byteArray: ByteArray;

@@ -36,21 +37,51 @@
   // floatComplexArrray: FloatComplexArray;

   // doubleComplexArrray: DoubleComplexArray;

   flags: boolean[];

-  getDataArray() {

-    if (this.scalarType === 'INTEGER') {

-      return this.integerArray;

-    } else if (this.scalarType === 'FLOAT') {

-      return this.floatArray;

-    } else if (this.scalarType === 'DOUBLE') {

-      return this.doubleArray;

-    } else if (this.scalarType === 'DATE') {

-      return this.dateArray;

-    } else if (this.scalarType === 'SHORT') {

-      return this.shortArray;

-    } else if (this.scalarType === 'BOOLEAN') {

-      return this.booleanArray;

-    } else if (this.scalarType === 'STRING') {

-      return this.stringArray;

-    }

+}

+

+/**

+ * Extracts the data array from MeasuredValues und replaces invalid values with null

+ * @param m MeasuredValues

+ * @returns NumberArray | DateArray | BooleanArray | StringArray

+ */

+export function getDataArray(m: MeasuredValues) {

+  if (m.scalarType === 'INTEGER') {

+    return numberApplyFlags(m.integerArray, m.flags);

+  } else if (m.scalarType === 'FLOAT') {

+    return numberApplyFlags(m.floatArray, m.flags);

+  } else if (m.scalarType === 'DOUBLE') {

+    return numberApplyFlags(m.doubleArray, m.flags);

+  } else if (m.scalarType === 'DATE') {

+    return dateApplyFlags(m.dateArray, m.flags);

+  } else if (m.scalarType === 'SHORT') {

+    return numberApplyFlags(m.shortArray, m.flags);

+  } else if (m.scalarType === 'BOOLEAN') {

+    return booleanApplyFlags(m.booleanArray, m.flags);

+  } else if (m.scalarType === undefined && m.stringArray !== undefined) {

+    m.scalarType = 'STRING';

+    return stringApplyFlags(m.stringArray, m.flags);

   }

 }

+

+function numberApplyFlags(n: NumberArray, flags: boolean[]) {

+  let a = new NumberArray();

+  a.values = n.values.map((n, i) => flags[i] ? n : null);

+  return a;

+}

+

+function dateApplyFlags(n: DateArray, flags: boolean[]) {

+  let a = new DateArray();

+  a.values = n.values.map((n, i) => flags[i] ? n : null);

+  return a;

+}

+function booleanApplyFlags(n: BooleanArray, flags: boolean[]) {

+  let a = new BooleanArray();

+  a.values = n.values.map((n, i) => flags[i] ? n : null);

+  return a;

+}

+

+function stringApplyFlags(s: StringArray, flags: boolean[]) {

+  let a = new StringArray();

+  a.values = s.values.map((s, i) => flags[i] ? s : null);

+  return a;

+}
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/string-array.class.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/string-array.class.ts
new file mode 100644
index 0000000..ca0d452
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/model/types/string-array.class.ts
@@ -0,0 +1,17 @@
+/********************************************************************************

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

+ *

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

+export class StringArray {

+    values: string[];

+  }

+  
\ 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 7bf05df..83eaeef 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
@@ -21,33 +21,14 @@
 import { HttpErrorHandler } from '../../core/http-error-handler';
 import { PropertyService } from '../../core/property.service';
 import { Node } from '../../navigator/node';
-import { MeasuredValuesResponse, MeasuredValues, PreviewValueList } from '../model/chartviewer.model';
-import { QueryService, Query, SearchResult, Row } from '../../tableview/query.service';
+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 { 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';
 
-export function getDataArray(m: MeasuredValues) {
-  if (m.scalarType === 'INTEGER') {
-    return m.integerArray;
-  } else if (m.scalarType === 'FLOAT') {
-    return m.floatArray;
-  } else if (m.scalarType === 'DOUBLE') {
-    return m.doubleArray;
-  } else if (m.scalarType === 'DATE') {
-    return m.dateArray;
-  } else if (m.scalarType === 'SHORT') {
-    return m.shortArray;
-  } else if (m.scalarType === 'BOOLEAN') {
-    return m.booleanArray;
-  } else if (m.scalarType === undefined && m.stringArray !== undefined) {
-    m.scalarType = 'STRING';
-    return m.stringArray;
-  }
-}
-
 @Injectable({
   providedIn: 'root'
 })
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 ef7b6ee..8f658ac 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
@@ -2,8 +2,7 @@
 import { Node } from '../../navigator/node';
 import { Subject } from 'rxjs';
 import { MeasuredValues, ChartData, ChartXyDataSet, ChartPoint } from '../model/chartviewer.model';
-import { getDataArray } from './chart-viewer-data.service';
-import { zip } from 'rxjs/operators';
+import { getDataArray } from '../model/types/measured-values.class';
 
 @Injectable({
   providedIn: 'root'
@@ -23,7 +22,7 @@
   }
 
   public toXyDataSet(xData: MeasuredValues, yData: MeasuredValues) {
-    const xValues = (xData !== undefined && xData.getDataArray() !== undefined ? xData.getDataArray().values : undefined) as number[];
+    const xValues = (xData !== undefined && getDataArray(xData) !== undefined ? getDataArray(xData).values : undefined) as number[];
     const yDataArray = getDataArray(yData);
     const yValues = (yDataArray !== undefined ? yDataArray.values : undefined) as number[];
     const points = this.getDataPoints(xValues, yValues);
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 f5ae171..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
@@ -20,12 +20,14 @@
   type: string;
   id: string;
   serial: string;
+  filter?: string;
 
-  constructor(source: string, type: string, id: string, serial?: 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/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 d64ca38..06cb787 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;
     }
   }
 
@@ -287,21 +307,26 @@
       };
       list.push(tmp);
     }
-
-    if (node.relations != null && node.relations.length > 0) {
-      for (let relation of node.relations) {
-        if (relation.ids != null && relation.ids.length > 0) {
-          for (let id of relation.ids) {
-            let nodes = this.getNodes(children, id);
-            for (let n of nodes) {
-              list.push(this.createTreeNode(n, children));
-            }
-          }
-        }
+    
+    let tplCompRel = node.relations.find(r => r.entityType === "TemplateComponent")
+    if (tplCompRel && tplCompRel.ids.length > 0) {
+      let nodes = this.getNodesWithTplCompId(children, tplCompRel.ids[0]);
+      for (let n of nodes) {
+        list.push(this.createTreeNode(n, children));
       }
     }
+
     return list;
   }
+  
+  /**
+   * Searches for nodes which have a relation to the given TemplateComponent ID.
+   * @param nodes List with nodes to search
+   * @param tplCompId  TemplateComponent ID
+   */
+  getNodesWithTplCompId(nodes: Node[], tplCompId: string) {
+    return nodes.filter(n => n.relations[0].parentId == tplCompId);
+  }
 
   /**
    * Method to create a tree structure from the flat context entity and attribute map
@@ -444,7 +469,7 @@
     event.stopPropagation();
     this.editMode = false;
     this.isTreeTableExpanded = true;
-    this.undoEditChanges(this.contextType, true);
+    this.reloadData();
   }
 
   onSaveChanges(event: Event) {
@@ -455,24 +480,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 1558e9d..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,32 +34,45 @@
   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) {
-    this.selectedNode = 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) {
       if (node.type.toLowerCase() === 'channel') {
         this.loadQuantity(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/navigator-view/mdm-navigator-view.component.ts b/nucleus/webclient/src/main/webapp/src/app/navigator-view/mdm-navigator-view.component.ts
index cf94fbf..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
@@ -26,6 +26,7 @@
 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',
@@ -51,7 +52,8 @@
 
   constructor(private nodeProviderService: NodeproviderService,
               private notificationService: MDMNotificationService,
-              private translateService: TranslateService) {}
+              private translateService: TranslateService,
+              private navigatorService: NavigatorService) {}
 
   onScrollTop() {
     this.div.scrollTop = 0;
@@ -74,6 +76,7 @@
   }
   activateNodeProvider(nodeprovider: any) {
     this.nodeProviderService.setActiveNodeprovider(nodeprovider);
+    this.navigatorService.fireSelectedItem(undefined);
   }
 
   getNodeproviders() {
@@ -84,14 +87,11 @@
     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)
+    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.ts b/nucleus/webclient/src/main/webapp/src/app/navigator/mdm-navigator.component.ts
index ec8bf46..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
@@ -192,14 +192,14 @@
       if (i.id === null || i.id === 'NO_ID') {
         return null;
       } else {
-        return new MDMItem(i.source, i.type, i.id, i.serial)
+        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)
+        return new MDMItem(i.source, i.type, i.id, i.serial, i.filter)
       }
     }
   }
@@ -234,7 +234,7 @@
 
   onNodeSelect(event) {
     if (event.node.data) {
-        this.navigatorService.setSelectedItem(this.toMDMItem(<TreeNode> event.node));
+      this.navigatorService.fireSelectedItem(this.toMDMItem(<TreeNode> event.node));
     }
 
     if (event.originalEvent.timeStamp - this.lastClickTime < 300) {
@@ -291,7 +291,7 @@
   }
 
   onFocus(event: { eventName: string, node: TreeNode }) {
-    this.navigatorService.setSelectedItem(event.node.data.item);
+    this.navigatorService.fireSelectedItem(event.node.data.item);
   }
 
   getNodeClass(item: MDMTreeNode) {
@@ -300,7 +300,7 @@
 
   loadRootNodes() {
     return this.nodeproviderService.getRoots().pipe(
-      map(n => n.map(node => this.mapNode(node)))
+      map(treeNodes => treeNodes.map(treeNode => this.mapNode(treeNode)))
     );
   }
 
@@ -377,18 +377,16 @@
     return m;
   }
 
-  openInTree(items: MDMItem[]) {
+  private openInTree(items: MDMItem[]) {
     this.selectedNodes = [];
-    items.forEach(item => {
-      this.nodeproviderService.getTreePath(item).subscribe(treepath => this.expandTreePath(treepath));
-    });
+    items.forEach(item => this.nodeproviderService.getTreePath(item).subscribe(treepath => this.expandTreePath(treepath)));
   }
 
-  expandTreePath(treePath: MDMTreeNode[]) {
+  private expandTreePath(treePath: MDMTreeNode[]) {
     this.expandChild(this.nodes, treePath);
   }
 
-  expandChild(children: TreeNode[], treePath: MDMTreeNode[]) {
+  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];
 
@@ -401,7 +399,7 @@
             this.expandChild(nodeToExpand.children, treePath);
           } else {
             this.selectedNodes = [ nodeToExpand ];
-            this.navigatorService.setSelectedItem(this.toMDMItem(nodeToExpand));
+            this.navigatorService.fireSelectedItem(this.toMDMItem(nodeToExpand));
           }
         },
         error => this.notificationService.notifyError(
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 c14fff3..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,58 +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) {
-      if (item === null) {
-        this.setSelectedNode(null);
-        return;
-      }
-      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 77b986a..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';
 
@@ -39,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) {
@@ -99,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)
+      }
     }
   }
 
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..32cd04e 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;">
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..5cd0ddf 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?.label}}</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?.label}}</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>
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..062d5eb 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, 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, 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() {
@@ -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..a41037d 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">
+  <span>{{condition.type | mdmdatasourcetranslate}}</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 | mdmdatasourcetranslate: condition.attribute }}</td> <!--  | translate: condition.attribute -->
 <td style="vertical-align: middle">
-  <div class="btn-group" dropdown style="width: 66px;">
+  <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,112 @@
     </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'">
+    <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>
+    <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 0fabadb..a97fee6 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,13 +25,17 @@
 
 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;
@@ -45,34 +47,27 @@
   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() {
-    if (this.primeAutoCompleteComponent) {
-      /* Workaround for missing onBlur handler in primeng version 2.x.
-       * We overwrite the existing implementation and additional call our own event handler.
-       * In later versions this feature was added https://github.com/primefaces/primeng/issues/2256
-       * and this workaround should be removed when using primeng version 4 or later
-       */
-      this.primeAutoCompleteComponent.onBlur = function() {
-        this.primeAutoCompleteComponent.focus = false;
-        this.primeAutoCompleteComponent.onModelTouched();
-        this.onAutocompleteBlur();
-      }.bind(this);
-    }
-  }
-
-  onAutocompleteBlur() {
-    this.onEnter(new Event('blur'));
+  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) {
@@ -80,27 +75,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 = [];
   }
 
@@ -133,6 +147,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) {
@@ -143,9 +171,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..5c50353 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) {
@@ -253,14 +253,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/tableview/editview.component.html b/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
index 76148ed..4e784bd 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;">
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..64e03f9 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,12 +448,15 @@
       "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": {
+			"filter": "Filter",
+			"tooltip-regexp-filter": "Reguläre Ausdrücke als Filtereingabe möglich.",
       "select-channel-placeholder": "Kanal wählen",
       "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."
@@ -495,7 +509,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..540a505 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,12 +448,15 @@
       "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": {
+			"filter": "Filter",
+			"tooltip-regexp-filter": "Regular expressions are enabled for filtering.",
       "select-channel-placeholder": "Select Channel",
       "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."
@@ -494,7 +508,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",