Merge remote-tracking branch 'dev' into mkoller/atfxadapter

Conflicts:
	build.gradle
	src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java

Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/NOTICE.txt b/NOTICE.txt
index 8bbd2e1..0dc71e0 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -17,13 +17,13 @@
 source code repository logs.
 
 Copyright (c) 2016-2018 Gigatronik Ingolstadt GmbH
-Copyright (c) 2016-2018 Peak Solution GmbH
+Copyright (c) 2016-2019 Peak Solution GmbH
 Copyright (c) 2017-2018 science + computing AG Tuebingen (ATOS SE)
 Copyright (c) 2017-2018 Canoo Engineering AG
 Copyright (c) 2017 Florian Schmitt
-Copyright (c) 2017-2018 Angelika Wittek
+Copyright (c) 2017-2019 Angelika Wittek
 Copyright (c) 2018 Elektronische Fahrwerksysteme GMBH
-Copyright (c) 2018 Karakun AG
+Copyright (c) 2018-2019 Karakun AG
 
 ## Declared Project Licenses
 
@@ -61,7 +61,13 @@
 commons-httpclient-3.1.jar  (3.1)
     * License: Apache License, 2.0
 
-commons-lang3-3.4.jar (3.4)
+commons-lang3-3.8.1.jar (3.8.1)
+    * License: Apache License, 2.0
+
+commons-text-1.6.jar (1.6)
+    * License: Apache License, 2.0
+
+commons-math-2.2.jar (2.2)
     * License: Apache License, 2.0
 
 gson-2.7.jar (2.7)
@@ -71,7 +77,7 @@
     * License: Apache License, 2.0
     
 Gradle Wrapper (4.10.2)
-    * License: Apache License, 2.0    
+    * License: Apache License, 2.0
 
 hk2-api-2.5.0-b05.jar (2.5.0-b05)
     * License: CDDL-1.1
@@ -130,6 +136,9 @@
 jersey-media-sse-2.23.2.jar (2.23.2)
     * License: CDDL
 
+jersey-media-multipart-2.23.2.jar (2.23.2)
+    * License: CDDL
+
 jersey-server-2.23.2.jar (2.23.2)
     * License: Apache-2.0
 
@@ -317,10 +326,10 @@
 From Dr. Ralph Noerenberg
 Date: 08/15/2016
 "Herewith, we release the generated Client-Source-Code generated from our CORBA IDLs, namely
-* CORBANotification Service (generated from „AvalonEvent.idl”)
-* CORBAFileServer (generated from „CorbaFileServer.idl“),
-Under the Eclipse Public License (EPL). This agreement does not include the „AvalonEvent.idl“ and
-„CorbaFileServer.idl“ itself, which remain protected property of HighQSoft. "
+* CORBANotification Service (generated from „AvalonEvent.idl”)
+* CORBAFileServer (generated from „CorbaFileServer.idl“),
+Under the Eclipse Public License (EPL). This agreement does not include the „AvalonEvent.idl“ and
+„CorbaFileServer.idl“ itself, which remain protected property of HighQSoft. "
 
 OMG Notification Service Specification Version: 1.1
 The terms of use are defined in section "Freely Available And Available
diff --git a/build.gradle b/build.gradle
index 34c0ea8..b4f00da 100644
--- a/build.gradle
+++ b/build.gradle
@@ -94,7 +94,7 @@
 
 	// querying es
 	compile 'commons-httpclient:commons-httpclient:3.1'
-	compile 'org.apache.commons:commons-lang3:3.4'
+	compile 'org.apache.commons:commons-lang3:3.8.1'
 
 	// testing
 	testCompile 'junit:junit:4.12'
@@ -140,7 +140,7 @@
 	main = 'com.sun.tools.corba.se.idl.toJavaPortable.Compile'
 
 	// add 'fallTIE' if <Type>POA & <Type>POATie have to be generated   
-	args '-td', 'src/gen/java/', 'src/main/idl/ods530.idl'
+	args '-fallTIE', '-td', 'src/gen/java/', 'src/main/idl/ods530.idl'
 
 	outputs.dir("src/gen/java")
 }
@@ -150,7 +150,7 @@
 
 	main = 'com.sun.tools.corba.se.idl.toJavaPortable.Compile'
 
-	args '-emitAll', '-fall', '-i', 'src/main/idl/', '-td', 'src/gen/java/', 'src/main/idl/CosNotifyChannelAdmin.idl'
+	args '-emitAll', '-fallTIE', '-i', 'src/main/idl/', '-td', 'src/gen/java/', 'src/main/idl/CosNotifyChannelAdmin.idl'
 
 	outputs.dir("src/gen/java")
 	outputs.upToDateWhen { false }
@@ -175,6 +175,15 @@
 	metaInf { from 'LICENSE.txt' }
 }
 
+task sourcesJar(type: Jar, dependsOn: classes) {
+	classifier = 'sources'
+	from sourceSets.main.allSource
+}
+
+artifacts {
+	archives sourcesJar
+}
+
 task deleteGenerated(type: Delete) {
 	delete 'src/gen'
 	delete 'build'
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSContext.java b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSContext.java
index 013c077..c03206a 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSContext.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSContext.java
@@ -97,7 +97,7 @@
 	@Override
 	public Optional<EntityFactory> getEntityFactory() {
 		try {
-			return Optional.of(new ODSEntityFactory(modelManager, entityManager.loadLoggedOnUser().get()));
+			return Optional.of(new ODSEntityFactory(modelManager, entityManager.loadLoggedOnUser()));
 		} catch (DataAccessException e) {
 			throw new IllegalStateException("Unable to load instance of the logged in user.");
 		}
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
index bb7b141..a113ec6 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/ODSEntityManager.java
@@ -25,11 +25,13 @@
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;

 
 import org.asam.ods.AoException;
 import org.asam.ods.ApplicationStructure;
 import org.asam.ods.ElemId;
-import org.asam.ods.InstanceElement;
+import org.asam.ods.InstanceElement;

+import org.asam.ods.T_LONGLONG;

 import org.eclipse.mdm.api.base.ServiceNotProvidedException;
 import org.eclipse.mdm.api.base.Transaction;
 import org.eclipse.mdm.api.base.adapter.EntityType;
@@ -351,13 +353,29 @@
 
 		return Collections.emptyMap();
 	}
-
+

+	/**

+	 * {@inheritDoc}

+	 */

+	public <T extends Entity> List<T> loadRelatedEntities(Entity entity, String relationName, Class<T> relatedClass) {

+		ODSEntityType entityType = ((ODSEntityType) context.getODSModelManager().getEntityType(entity));

+		ElemId elemId = new ElemId(entityType.getODSID(), ODSConverter.toODSID(entity.getID()));

+

+		try {

+			T_LONGLONG[] instanceIds = context.getAoSession().getApplElemAccess().getRelInst(elemId, relationName);

+			List<String> instanceIDs = Stream.of(instanceIds).map(ODSConverter::fromODSLong).map(l -> l.toString()).collect(Collectors.toList());

+			return entityLoader.loadAll(new Key<>(relatedClass), instanceIDs);

+		} catch (AoException e) {

+			throw new DataAccessException("" + e.reason, e); // TODO

+		}

+	}

+	
 	/**
 	 * {@inheritDoc}
 	 */
 	@Override
 	public List<MeasuredValues> readMeasuredValues(ReadRequest readRequest) throws DataAccessException {
-		return new ReadRequestHandler(odsModelManager).execute(readRequest);
+		return new ReadRequestHandler(odsModelManager, queryService).execute(readRequest);
 	}
 
 	/**
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java b/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
index 7d4603e..9d6d2aa 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
@@ -15,26 +15,44 @@
 
 package org.eclipse.mdm.api.odsadapter;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map.Entry;
-
-import org.asam.ods.AoException;
-import org.asam.ods.Column;
-import org.asam.ods.ElemId;
-import org.asam.ods.NameValueSeqUnit;
-import org.asam.ods.T_LONGLONG;
-import org.asam.ods.ValueMatrix;
-import org.asam.ods.ValueMatrixMode;
-import org.eclipse.mdm.api.base.massdata.ReadRequest;
-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.Unit;
-import org.eclipse.mdm.api.base.query.DataAccessException;
-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 java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Set;

+

+import org.apache.commons.lang3.tuple.ImmutablePair;

+import org.apache.commons.lang3.tuple.Pair;

+import org.asam.ods.AoException;

+import org.asam.ods.Column;

+import org.asam.ods.ElemId;

+import org.asam.ods.NameValueSeqUnit;

+import org.asam.ods.T_LONGLONG;

+import org.asam.ods.ValueMatrix;

+import org.asam.ods.ValueMatrixMode;

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

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

+import org.eclipse.mdm.api.base.massdata.ReadRequest;

+import org.eclipse.mdm.api.base.massdata.ReadRequest.ValuesMode;

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

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

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

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

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

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

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

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

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

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

+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 com.google.common.collect.Lists;
 
 /**
  * Reads mass data specified in {@link ReadRequest}s.
@@ -42,13 +60,82 @@
  * @since 1.0.0
  * @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
  */
-final class ReadRequestHandler {
+public final class ReadRequestHandler {

+	public static final class ColumnAttributes

+	{

+		private String name;

+		private SequenceRepresentation sequenceRepresentation;

+		private double[] generationParameters;

+		private boolean independent;

+		private AxisType axisType;

+		

+		public ColumnAttributes(String name, SequenceRepresentation sequenceRepresentation, double[] generationParameters, boolean independent, AxisType axisType)

+		{

+			this.name = name;

+			this.sequenceRepresentation = sequenceRepresentation;

+			this.generationParameters = generationParameters;

+			this.independent = independent;

+			this.axisType = axisType;

+		}

+		

+		public String getName()

+		{

+			return name;

+		}

+		

+		public void setName(String name)

+		{

+			this.name = name;

+		}

+

+		public SequenceRepresentation getSequenceRepresentation()

+		{

+			return sequenceRepresentation;

+		}

+		

+		public void setSequenceRepresentation(SequenceRepresentation sequenceRepresentation)

+		{

+			this.sequenceRepresentation = sequenceRepresentation;

+		}

+		

+		public double[] getGenerationParameters()

+		{

+			return generationParameters;

+		}

+		

+		public void setGenerationParameters(double[] generationParameters)

+		{

+			this.generationParameters = generationParameters;

+		}

+		

+		public boolean isIndependentColumn()

+		{

+			return independent;

+		}

+		

+		public void setIndependentColumn(boolean independent)

+		{

+			this.independent = independent;

+		}

+		

+		public AxisType getAxisType()

+		{

+			return axisType;

+		}

+		

+		public void setAxisType(AxisType axisType)

+		{

+			this.axisType = axisType;

+		}

+		

+	}
 
 	// ======================================================================
 	// Instance variables
 	// ======================================================================
-
-	private final ODSModelManager modelManager;
+

+	private final ODSModelManager modelManager;

+	private final QueryService queryService;
 
 	// ======================================================================
 	// Constructors
@@ -60,8 +147,9 @@
 	 * @param modelManager
 	 *            Used to gain access to value matrices.
 	 */
-	public ReadRequestHandler(ODSModelManager modelManager) {
-		this.modelManager = modelManager;
+	public ReadRequestHandler(ODSModelManager modelManager, QueryService queryService) {

+		this.modelManager = modelManager;

+		this.queryService = queryService;
 	}
 
 	// ======================================================================
@@ -80,18 +168,22 @@
 	 */
 	public List<MeasuredValues> execute(ReadRequest readRequest) throws DataAccessException {
 		ValueMatrix valueMatrix = null;
-		Column[] columns = null;
+		Column[] arrColumns = null;
 
 		try {
-			valueMatrix = getValueMatrix(readRequest);
-			columns = getODSColumns(readRequest, valueMatrix);
-			NameValueSeqUnit[] nvsus = valueMatrix.getValue(columns, readRequest.getStartIndex(),
+			valueMatrix = getValueMatrix(readRequest);

+			List<Pair<Column, ColumnAttributes>> listColumnPairs = getODSColumns(readRequest, valueMatrix);

+			

+			arrColumns = listColumnPairs.stream().map(Pair::getLeft).toArray(Column[]::new);

+			ColumnAttributes[] arrColumnAttributes = listColumnPairs.stream().map(Pair::getRight).toArray(ColumnAttributes[]::new);

+			
+			NameValueSeqUnit[] nvsus = valueMatrix.getValue(arrColumns, readRequest.getStartIndex(),
 					readRequest.getRequestSize());
-			return ODSConverter.fromODSMeasuredValuesSeq(nvsus);
+			return ODSConverter.fromODSMeasuredValuesSeq(nvsus, arrColumnAttributes);
 		} catch (AoException aoe) {
 			throw new DataAccessException(aoe.reason, aoe);
 		} finally {
-			releaseColumns(columns);
+			releaseColumns(arrColumns);
 			releaseValueMatrix(valueMatrix);
 		}
 	}
@@ -116,38 +208,122 @@
 	 * @throws DataAccessException
 	 *             Thrown on wrong {@code ReadRequest} setup.
 	 */
-	private Column[] getODSColumns(ReadRequest readRequest, ValueMatrix valueMatrix)
-			throws AoException, DataAccessException {
-		if (readRequest.isLoadAllChannels()) {
-			// TODO should it be possible to overwrite the unit of some
-			// channels?!
-			// -> this results in a performance issue since we need to call
-			// getName()
-			// on each column for mapping!
-			return valueMatrix.getColumns("*");
-		}
-
-		List<Column> columnList = new ArrayList<>();
-		try {
-			for (Entry<Channel, Unit> entry : readRequest.getChannels().entrySet()) {
-				Channel channel = entry.getKey();
-				Unit unit = entry.getValue();
-				Column[] columns = valueMatrix.getColumns(channel.getName());
-				if (columns == null || columns.length != 1) {
-					releaseColumns(columns);
-					throw new DataAccessException("Column with name '" + channel.getName() + "' not found.");
-				}
-				Column column = columns[0];
-				if (!unit.nameEquals(channel.getUnit().getName())) {
-					column.setUnit(unit.getName());
-				}
-				columnList.add(column);
-			}
-			return columnList.toArray(new Column[columnList.size()]);
-		} catch (AoException e) {
-			releaseColumns(columnList.toArray(new Column[columnList.size()]));
-			throw new DataAccessException("Unable to load column due to: " + e.reason, e);
-		}
+	private List<Pair<Column, ColumnAttributes>> getODSColumns(ReadRequest readRequest, ValueMatrix valueMatrix)

+			throws AoException, DataAccessException {

+		List<Pair<Column, ColumnAttributes>> listColumnPairs = new ArrayList<>();

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

+

+		try {

+			if (readRequest.isLoadAllChannels()) {

+				// TODO should it be possible to overwrite the unit of some

+				// channels?!

+				// -> this results in a performance issue since we need to call

+				// getName()

+				// on each column for mapping! (no longer, see below!)

+				Column[] columns = valueMatrix.getColumns("*");

+

+				if (null != columns) {

+					for (Column column : columns) {

+						String columnName = column.getName();

+						if (mapColumns.containsKey(columnName))

+						{

+							releaseColumns(columns);

+							throw new DataAccessException(String.format("Duplicate column name '%s' found within submatrix ID %d!",

+									columnName, Long.valueOf(readRequest.getChannelGroup().getID())));

+						}

+						

+						mapColumns.put(columnName, column);

+					}

+				}

+			} else {

+				for (Entry<Channel, Unit> entry : readRequest.getChannels().entrySet()) {

+					Channel channel = entry.getKey();

+					Unit unit = entry.getValue();

+					String channelName = channel.getName();

+					Column[] columns = valueMatrix.getColumns(channelName);

+					if (columns == null || columns.length != 1) {

+						releaseColumns(columns);

+						throw new DataAccessException(String.format("Zero or more than one column with name '%s' found within submatrix ID %d!",

+								channelName, Long.valueOf(readRequest.getChannelGroup().getID())));

+					}

+					

+					Column column = columns[0];

+					if (!unit.nameEquals(channel.getUnit().getName())) {

+						column.setUnit(unit.getName());

+					}

+

+					mapColumns.put(channelName, column);

+				}

+			}

+

+			if (mapColumns.size() > 0) {

+				EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");

+				Attribute idAttr = localColumnEntityType.getAttribute("Id");

+				Attribute nameAttr = localColumnEntityType.getAttribute("Name");

+				Attribute submatrixAttr = localColumnEntityType.getAttribute("SubMatrix");

+

+				// Don't query GenerationParameters together with other non-ID attributes as Avalon dislikes this:

+				Query query1 = queryService.createQuery()

+						.select(Lists.newArrayList(idAttr,

+								nameAttr,

+								localColumnEntityType.getAttribute("SequenceRepresentation"),

+								localColumnEntityType.getAttribute("IndependentFlag"),

+								localColumnEntityType.getAttribute("axistype")));

+				

+				

+

+				Filter filter = Filter.and()

+						.add(ComparisonOperator.EQUAL.create(submatrixAttr, Long.valueOf(readRequest.getChannelGroup().getID())));

+				

+				Set<String> setColumnNames = mapColumns.keySet();

+				

+				Map<Long, ColumnAttributes> mapColumnAttributes = new HashMap<>();

+

+				for (Result result : query1.fetch(filter)) {

+					Map<String, Value> mapValues = result.getRecord(localColumnEntityType).getValues();

+

+					String columnName = mapValues.get("Name").extract();

+

+					if (setColumnNames.contains(columnName))

+					{	

+						ColumnAttributes ca = new ColumnAttributes(columnName,

+								(ValuesMode.CALCULATED == readRequest.getValuesMode() ? SequenceRepresentation.EXPLICIT : mapValues.get("SequenceRepresentation").extract()),

+								new double[0],

+								((short) mapValues.get("IndependentFlag").extract() != 0),

+								mapValues.get("axistype").extract());

+						

+						mapColumnAttributes.put(mapValues.get("Id").extract(), ca);

+					}

+				}

+				

+				if (ValuesMode.CALCULATED != readRequest.getValuesMode())

+				{

+					Query query2 = queryService.createQuery()

+							.select(idAttr,

+									localColumnEntityType.getAttribute("GenerationParameters"));

+					

+					for (Result result : query2.fetch(filter)) {

+						Map<String, Value> mapValues = result.getRecord(localColumnEntityType).getValues();

+						

+						ColumnAttributes ca = mapColumnAttributes.get(mapValues.get("Id").extract());

+						

+						if (ca != null) {

+							ca.setGenerationParameters(mapValues.get("GenerationParameters").extract());

+						}

+					}

+				}

+				

+				for (Map.Entry<Long, ColumnAttributes> me : mapColumnAttributes.entrySet()) {

+					ColumnAttributes ca = me.getValue();

+					listColumnPairs.add(new ImmutablePair<Column, ColumnAttributes>(mapColumns.get(ca.getName()), ca));

+				}

+			}

+

+			return listColumnPairs;

+		} catch (AoException e) {

+			releaseColumns(listColumnPairs.stream().map(Pair::getLeft).toArray(Column[]::new));

+			throw new DataAccessException("Unable to load column due to: " + e.reason, e);

+		}

 	}
 
 	/**
@@ -163,8 +339,18 @@
 	private ValueMatrix getValueMatrix(ReadRequest readRequest) throws AoException {
 		Entity entity = readRequest.getChannelGroup();
 		T_LONGLONG iid = ODSConverter.toODSID(entity.getID());
-		T_LONGLONG aid = ((ODSEntityType) modelManager.getEntityType(entity)).getODSID();
-		return modelManager.getApplElemAccess().getValueMatrixInMode(new ElemId(aid, iid), ValueMatrixMode.CALCULATED);
+		T_LONGLONG aid = ((ODSEntityType) modelManager.getEntityType(entity)).getODSID();

+		ValueMatrixMode valueMatrixMode = ValueMatrixMode.CALCULATED;

+		switch (readRequest.getValuesMode())

+		{

+		case CALCULATED: valueMatrixMode = ValueMatrixMode.CALCULATED; break;

+		case STORAGE: valueMatrixMode = ValueMatrixMode.STORAGE; break;

+		default:

+			throw new DataAccessException(

+					String.format("Unsupported ValueMode %s!", readRequest.getValuesMode().name()));

+		}

+		
+		return modelManager.getApplElemAccess().getValueMatrixInMode(new ElemId(aid, iid), valueMatrixMode);
 	}
 
 	/**
@@ -199,7 +385,7 @@
 		}
 
 		for (Column column : columns) {
-			try {
+			try {

 				column.destroy();
 			} catch (AoException e) {
 				// ignore
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
new file mode 100644
index 0000000..e630c00
--- /dev/null
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/DefaultEntityConfigRepositoryLoader.java
@@ -0,0 +1,336 @@
+/********************************************************************************

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

+ *

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

+

+package org.eclipse.mdm.api.odsadapter.lookup.config;

+

+import java.util.Locale;

+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key;

+import org.eclipse.mdm.api.odsadapter.query.ODSEntityType;

+import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;

+import org.eclipse.mdm.api.odsadapter.utils.ODSUtils;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+public class DefaultEntityConfigRepositoryLoader  implements EntityConfigRepositoryLoader {

+

+	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultEntityConfigRepositoryLoader.class);

+	

+	EntityConfigRepository entityConfigRepository;

+	

+	

+	public DefaultEntityConfigRepositoryLoader() {

+	}

+

+	/**

+	 * Loads the {@link EntityConfig}s.

+	 * @return 

+	 */

+	@Override

+	public EntityConfigRepository loadEntityConfigurations(ODSModelManager modelManager) {

+		LOGGER.debug("Loading entity configurations...");

+		long start = System.currentTimeMillis();

+

+		entityConfigRepository = new EntityConfigRepository();

+

+		entityConfigRepository.register(create(modelManager, new Key<>(Role.class), "Role", false));

+		

+		// Environment | Project | Pool | PhysicalDimension | User | Measurement

+		// | ChannelGroup

+		entityConfigRepository.register(create(modelManager, new Key<>(Environment.class), "Environment", false));

+		entityConfigRepository.register(create(modelManager, new Key<>(Project.class), "Project", false));

+		entityConfigRepository.register(create(modelManager, new Key<>(Pool.class), "StructureLevel", true));

+		entityConfigRepository.register(create(modelManager, new Key<>(PhysicalDimension.class), "PhysDimension", false));

+		entityConfigRepository.register(create(modelManager, new Key<>(User.class), "User", false));

+		entityConfigRepository.register(create(modelManager, new Key<>(Measurement.class), "MeaResult", false));

+		entityConfigRepository.register(create(modelManager, new Key<>(ChannelGroup.class), "SubMatrix", false));

+

+		// Unit

+		EntityConfig<Unit> unitConfig = create(modelManager, new Key<>(Unit.class), "Unit", false);

+		unitConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(PhysicalDimension.class)));

+		entityConfigRepository.register(unitConfig);

+

+		// Quantity

+		EntityConfig<Quantity> quantityConfig = create(modelManager, new Key<>(Quantity.class), "Quantity", false);

+		quantityConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Unit.class)));

+		entityConfigRepository.register(quantityConfig);

+

+		// Channel

+		EntityConfig<Channel> channelConfig = create(modelManager, new Key<>(Channel.class), "MeaQuantity", false);

+		channelConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Unit.class)));

+		channelConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Quantity.class)));

+		entityConfigRepository.register(channelConfig);

+

+		// ValueList

+		EntityConfig<ValueListValue> valueListValueConfig = create(modelManager, new Key<>(ValueListValue.class), "ValueListValue",

+				true);

+		valueListValueConfig.setComparator(Sortable.COMPARATOR);

+		EntityConfig<ValueList> valueListConfig = create(modelManager, new Key<>(ValueList.class), "ValueList", true);

+		valueListConfig.addChild(valueListValueConfig);

+		entityConfigRepository.register(valueListConfig);

+

+		// ParameterSet

+		EntityConfig<Parameter> parameterConfig = create(modelManager, new Key<>(Parameter.class), "ResultParameter", true);

+		parameterConfig.addOptional(entityConfigRepository.findRoot(new Key<>(Unit.class)));

+		EntityConfig<ParameterSet> parameterSetConfig = create(modelManager, new Key<>(ParameterSet.class), "ResultParameterSet",

+				true);

+		parameterSetConfig.addChild(parameterConfig);

+		entityConfigRepository.register(parameterSetConfig);

+

+		// CatalogComponents

+		registerCatalogComponent(modelManager, ContextType.UNITUNDERTEST);

+		registerCatalogComponent(modelManager, ContextType.TESTSEQUENCE);

+		registerCatalogComponent(modelManager, ContextType.TESTEQUIPMENT);

+

+		// TemplateRoots

+		registerTemplateRoot(modelManager, ContextType.UNITUNDERTEST);

+		registerTemplateRoot(modelManager, ContextType.TESTSEQUENCE);

+		registerTemplateRoot(modelManager, ContextType.TESTEQUIPMENT);

+

+		// TemplateTestStep

+		EntityConfig<TemplateTestStep> templateTestStepConfig = create(modelManager, new Key<>(TemplateTestStep.class), "TplTestStep",

+				true);

+		templateTestStepConfig

+				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.UNITUNDERTEST)));

+		templateTestStepConfig

+				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.TESTSEQUENCE)));

+		templateTestStepConfig

+				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.TESTEQUIPMENT)));

+		templateTestStepConfig.setComparator(Versionable.COMPARATOR);

+		entityConfigRepository.register(templateTestStepConfig);

+

+		// Status TestStep

+		// TODO check MIME type genration

+		// entityConfigRepository.register(create(new Key<>(Status.class,

+		// TestStep.class), "StatusTestStep", true));

+

+		// TestStep

+		EntityConfig<TestStep> testStepConfig = create(modelManager, new Key<>(TestStep.class), "TestStep", true);

+		// testStepConfig.addMandatory(entityConfigRepository.findRoot(new

+		// Key<>(Status.class, TestStep.class)));

+		testStepConfig.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateTestStep.class)));

+		testStepConfig.setComparator(Sortable.COMPARATOR);

+		entityConfigRepository.register(testStepConfig);

+

+		// TemplateTest

+		EntityConfig<TemplateTestStepUsage> templateTestStepUsageConfig = create(modelManager, new Key<>(TemplateTestStepUsage.class),

+				"TplTestStepUsage", true);

+		templateTestStepUsageConfig.addMandatory(templateTestStepConfig);

+		templateTestStepUsageConfig.setComparator(Sortable.COMPARATOR);

+		EntityConfig<TemplateTest> templateTestConfig = create(modelManager, new Key<>(TemplateTest.class), "TplTest", true);

+		templateTestConfig.addChild(templateTestStepUsageConfig);

+		templateTestConfig.setComparator(Versionable.COMPARATOR);

+		entityConfigRepository.register(templateTestConfig);

+

+		// Status Test

+		// TODO check MIME type genration

+		// entityConfigRepository.register(create(new Key<>(Status.class,

+		// Test.class), "StatusTest", true));

+

+		// Test

+		EntityConfig<Test> testConfig = create(modelManager, new Key<>(Test.class), "Test", true);

+		testConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(User.class)));

+		// testConfig.addMandatory(entityConfigRepository.findRoot(new

+		// Key<>(Status.class, Test.class)));

+		testConfig.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateTest.class)));

+		entityConfigRepository.register(testConfig);

+

+		// ContextRoots

+		registerContextRoot(modelManager, ContextType.UNITUNDERTEST);

+		registerContextRoot(modelManager, ContextType.TESTSEQUENCE);

+		registerContextRoot(modelManager, ContextType.TESTEQUIPMENT);

+

+		LOGGER.debug("Entity configurations loaded in {} ms.", System.currentTimeMillis() - start);

+		return entityConfigRepository;

+	}

+

+	/**

+	 * Loads the {@link EntityConfig}s required for {@link ContextRoot} with

+	 * given {@link ContextType}.

+	 *

+	 * @param contextType

+	 *            The {@code ContextType}.

+	 */

+	private void registerContextRoot(ODSModelManager modelManager, ContextType contextType) {

+		EntityConfig<ContextRoot> contextRootConfig = create(modelManager, new Key<>(ContextRoot.class, contextType),

+				ODSUtils.CONTEXTTYPES.get(contextType), true);

+		contextRootConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, contextType)));

+		for (Relation contextComponentRelation : contextRootConfig.getEntityType().getChildRelations()) {

+			EntityType contextComponentEntityType = contextComponentRelation.getTarget();

+			EntityConfig<ContextComponent> contextComponentConfig = create(modelManager, 

+					new Key<>(ContextComponent.class, contextType), contextComponentEntityType.getName(), true);

+			contextComponentConfig

+					.addInherited(entityConfigRepository.findImplicit(new Key<>(TemplateComponent.class, contextType)));

+			contextRootConfig.addChild(contextComponentConfig);

+			if (contextType.isTestEquipment()) {

+				for (Relation contextSensorRelation : contextComponentEntityType.getChildRelations()) {

+					EntityType contextSensorEntityType = contextSensorRelation.getTarget();

+					EntityConfig<ContextSensor> contextSensorConfig = create(modelManager, new Key<>(ContextSensor.class),

+							contextSensorEntityType.getName(), true);

+					contextSensorConfig

+							.addInherited(entityConfigRepository.findImplicit(new Key<>(TemplateSensor.class)));

+					contextComponentConfig.addChild(contextSensorConfig);

+				}

+			}

+		}

+		entityConfigRepository.register(contextRootConfig);

+	}

+

+	/**

+	 * Loads the {@link EntityConfig}s required for {@link TemplateRoot} with

+	 * given {@link ContextType}.

+	 *

+	 * @param contextType

+	 *            The {@code ContextType}.

+	 */

+	private void registerTemplateRoot(ODSModelManager modelManager, ContextType contextType) {

+		String odsName = ODSUtils.CONTEXTTYPES.get(contextType);

+		EntityConfig<TemplateAttribute> templateAttributeConfig = create(modelManager, 

+				new Key<>(TemplateAttribute.class, contextType), "Tpl" + odsName + "Attr", true);

+		templateAttributeConfig

+				.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogAttribute.class, contextType)));

+		templateAttributeConfig.setComparator(TemplateAttribute.COMPARATOR);

+		EntityConfig<TemplateComponent> templateComponentConfig = create(modelManager, 

+				new Key<>(TemplateComponent.class, contextType), "Tpl" + odsName + "Comp", true);

+		templateComponentConfig.addChild(templateAttributeConfig);

+		templateComponentConfig

+				.addMandatory(entityConfigRepository.findRoot(new Key<>(CatalogComponent.class, contextType)));

+		templateComponentConfig.addChild(templateComponentConfig);

+		templateComponentConfig.setComparator(Sortable.COMPARATOR);

+		if (contextType.isTestEquipment()) {

+			EntityConfig<TemplateAttribute> templateSensorAttributeConfig = create(modelManager, new Key<>(TemplateAttribute.class),

+					"TplSensorAttr", true);

+			templateSensorAttributeConfig.setComparator(TemplateAttribute.COMPARATOR);

+			templateSensorAttributeConfig

+					.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogAttribute.class)));

+			EntityConfig<TemplateSensor> templateSensorConfig = create(modelManager, new Key<>(TemplateSensor.class), "TplSensor",

+					true);

+			templateSensorConfig.addChild(templateSensorAttributeConfig);

+			templateSensorConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Quantity.class)));

+			templateSensorConfig.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogSensor.class)));

+			templateSensorConfig.setComparator(Sortable.COMPARATOR);

+			templateComponentConfig.addChild(templateSensorConfig);

+		}

+		EntityConfig<TemplateRoot> templateRootConfig = create(modelManager, new Key<>(TemplateRoot.class, contextType),

+				"Tpl" + odsName + "Root", true);

+		templateRootConfig.addChild(templateComponentConfig);

+		templateRootConfig.setComparator(Versionable.COMPARATOR);

+		entityConfigRepository.register(templateRootConfig);

+	}

+

+	/**

+	 * Loads the {@link EntityConfig}s required for {@link CatalogComponent}

+	 * with given {@link ContextType}.

+	 *

+	 * @param contextType

+	 *            The {@code ContextType}.

+	 */

+	private void registerCatalogComponent(ODSModelManager modelManager, ContextType contextType) {

+		String odsName = ODSUtils.CONTEXTTYPES.get(contextType);

+		EntityConfig<CatalogAttribute> catalogAttributeConfig = create(modelManager, new Key<>(CatalogAttribute.class, contextType),

+				"Cat" + odsName + "Attr", true);

+		catalogAttributeConfig.addOptional(entityConfigRepository.findRoot(new Key<>(ValueList.class)));

+		catalogAttributeConfig.setComparator(Sortable.COMPARATOR);

+		EntityConfig<CatalogComponent> catalogComponentConfig = create(modelManager, new Key<>(CatalogComponent.class, contextType),

+				"Cat" + odsName + "Comp", true);

+		catalogComponentConfig.addChild(catalogAttributeConfig);

+		if (contextType.isTestEquipment()) {

+			EntityConfig<CatalogAttribute> catalogSensorAttributeConfig = create(modelManager, new Key<>(CatalogAttribute.class),

+					"CatSensorAttr", true);

+			catalogSensorAttributeConfig.addOptional(entityConfigRepository.findRoot(new Key<>(ValueList.class)));

+			EntityConfig<CatalogSensor> catalogSensorConfig = create(modelManager, new Key<>(CatalogSensor.class), "CatSensor", true);

+			catalogSensorConfig.addChild(catalogSensorAttributeConfig);

+			catalogComponentConfig.addChild(catalogSensorConfig);

+		}

+		entityConfigRepository.register(catalogComponentConfig);

+	}

+

+	/**

+	 * Creates a new {@link EntityConfig}.

+	 *

+	 * @param <T>

+	 *            The entity type.

+	 * @param key

+	 *            Used as identifier.

+	 * @param typeName

+	 *            Name of the associated {@link EntityType}.

+	 * @param appendName

+	 *            Flag indicates whether to append the entity types base name to

+	 *            the MIME type.

+	 * @return The created {@code EntityConfig} is returned.

+	 */

+	private <T extends Entity> EntityConfig<T> create(ODSModelManager modelManager, Key<T> key, String typeName, boolean appendName) {

+		EntityConfig<T> entityConfig = new EntityConfig<>(key);

+		ODSEntityType entityType = (ODSEntityType) modelManager.getEntityType(typeName);

+		entityConfig.setEntityType(entityType);

+		entityConfig.setMimeType(buildDefaultMimeType(entityType, appendName));

+		return entityConfig;

+	}

+

+	/**

+	 * Creates a default MIME type for given {@link EntityType}.

+	 *

+	 * @param entityType

+	 *            The {@code EntityType}.

+	 * @param appendName

+	 *            Flag indicates whether to append the entity types base name to

+	 *            the MIME type.

+	 * @return The created MIME type {@code String} is returned.

+	 */

+	private String buildDefaultMimeType(ODSEntityType entityType, boolean appendName) {

+		StringBuilder sb = new StringBuilder();

+		sb.append("application/x-asam.");

+		sb.append(entityType.getBaseName().toLowerCase(Locale.ROOT));

+		if (appendName) {

+			sb.append('.').append(entityType.getName().toLowerCase(Locale.ROOT));

+		}

+		return sb.toString();

+	}

+}

diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/EntityConfigRepositoryLoader.java b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/EntityConfigRepositoryLoader.java
new file mode 100644
index 0000000..88b8f0f
--- /dev/null
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/config/EntityConfigRepositoryLoader.java
@@ -0,0 +1,26 @@
+/********************************************************************************

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

+ *

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

+

+package org.eclipse.mdm.api.odsadapter.lookup.config;

+

+import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;

+

+public interface EntityConfigRepositoryLoader {

+

+	/**

+	 * Loads the {@link EntityConfigRepository}.

+	 * @return 

+	 */

+	EntityConfigRepository loadEntityConfigurations(ODSModelManager modelManager);

+}

diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSEntityFactory.java b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSEntityFactory.java
index c66516f..ec2a38e 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSEntityFactory.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSEntityFactory.java
@@ -76,7 +76,7 @@
 	// ======================================================================
 
 	private final ODSModelManager modelManager;
-	private final User loggedInUser;
+	private final Optional<User> loggedInUser;
 
 	// ======================================================================
 	// Constructors
@@ -90,7 +90,7 @@
 	 * @param loggedInUser
 	 *            The logged in {@link User}.
 	 */
-	public ODSEntityFactory(ODSModelManager modelManager, User loggedInUser) {
+	public ODSEntityFactory(ODSModelManager modelManager, Optional<User> loggedInUser) {
 		this.modelManager = modelManager;
 		this.loggedInUser = loggedInUser;
 	}
@@ -153,7 +153,7 @@
 	 */
 	@Override
 	protected Optional<User> getLoggedInUser() {
-		return Optional.of(loggedInUser);
+		return loggedInUser;
 	}
 
 	/**
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
index 6efe7f3..b1e9b07 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSModelManager.java
@@ -21,7 +21,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.locks.Lock;
@@ -49,52 +48,19 @@
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.adapter.ModelManager;
 import org.eclipse.mdm.api.base.adapter.Relation;
-import org.eclipse.mdm.api.base.model.Channel;
-import org.eclipse.mdm.api.base.model.ChannelGroup;
-import org.eclipse.mdm.api.base.model.ContextComponent;
-import org.eclipse.mdm.api.base.model.ContextRoot;
-import org.eclipse.mdm.api.base.model.ContextSensor;
 import org.eclipse.mdm.api.base.model.ContextType;
 import org.eclipse.mdm.api.base.model.Entity;
 import org.eclipse.mdm.api.base.model.EnumRegistry;
 import org.eclipse.mdm.api.base.model.Enumeration;
-import org.eclipse.mdm.api.base.model.Environment;
-import org.eclipse.mdm.api.base.model.Measurement;
-import org.eclipse.mdm.api.base.model.Parameter;
-import org.eclipse.mdm.api.base.model.ParameterSet;
-import org.eclipse.mdm.api.base.model.PhysicalDimension;
-import org.eclipse.mdm.api.base.model.Quantity;
-import org.eclipse.mdm.api.base.model.Sortable;
-import org.eclipse.mdm.api.base.model.Test;
-import org.eclipse.mdm.api.base.model.TestStep;
 import org.eclipse.mdm.api.base.model.Unit;
-import org.eclipse.mdm.api.base.model.User;
-import org.eclipse.mdm.api.dflt.model.CatalogAttribute;
-import org.eclipse.mdm.api.dflt.model.CatalogComponent;
-import org.eclipse.mdm.api.dflt.model.CatalogSensor;
-import org.eclipse.mdm.api.dflt.model.Classification;
-import org.eclipse.mdm.api.dflt.model.Domain;
-import org.eclipse.mdm.api.dflt.model.Pool;
-import org.eclipse.mdm.api.dflt.model.Project;
-import org.eclipse.mdm.api.dflt.model.ProjectDomain;
-import org.eclipse.mdm.api.dflt.model.Status;
-import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
-import org.eclipse.mdm.api.dflt.model.TemplateComponent;
-import org.eclipse.mdm.api.dflt.model.TemplateRoot;
-import org.eclipse.mdm.api.dflt.model.TemplateSensor;
-import org.eclipse.mdm.api.dflt.model.TemplateTest;
-import org.eclipse.mdm.api.dflt.model.TemplateTestStep;
-import org.eclipse.mdm.api.dflt.model.TemplateTestStepUsage;
-import org.eclipse.mdm.api.dflt.model.ValueList;
-import org.eclipse.mdm.api.dflt.model.ValueListValue;
-import org.eclipse.mdm.api.dflt.model.Versionable;
+import org.eclipse.mdm.api.odsadapter.lookup.config.DefaultEntityConfigRepositoryLoader;
 import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
 import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig.Key;
 import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfigRepository;
+import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfigRepositoryLoader;
 import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
 import org.eclipse.mdm.api.odsadapter.utils.ODSEnum;
 import org.eclipse.mdm.api.odsadapter.utils.ODSEnumerations;
-import org.eclipse.mdm.api.odsadapter.utils.ODSUtils;
 import org.omg.CORBA.ORB;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -118,6 +84,7 @@
 	private final Lock read;
 
 	private EntityConfigRepository entityConfigRepository;
+	private EntityConfigRepositoryLoader entityConfigRepositoryLoader;
 
 	private ApplElemAccess applElemAccess;
 	private AoSession aoSession;
@@ -133,10 +100,25 @@
 	 *             Thrown on errors.
 	 */
 	public ODSModelManager(ORB orb, AoSession aoSession) throws AoException {
+		this(orb, aoSession, new DefaultEntityConfigRepositoryLoader());
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param orb
+	 *            Used to activate CORBA service objects.
+	 * @param aoSession
+	 *            The underlying ODS session.
+	 * @throws AoException
+	 *             Thrown on errors.
+	 */
+	public ODSModelManager(ORB orb, AoSession aoSession, EntityConfigRepositoryLoader entityConfigRepositoryLoader) throws AoException {
 		this.aoSession = aoSession;
 		this.orb = orb;
 		applElemAccess = aoSession.getApplElemAccess();
-
+		this.entityConfigRepositoryLoader = entityConfigRepositoryLoader;
+		
 		// setup locks
 		ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
 		write = reentrantReadWriteLock.writeLock();
@@ -368,7 +350,7 @@
 	 */
 	private void initialize() throws AoException {
 		loadApplicationModel();
-		loadEntityConfigurations();
+		entityConfigRepository = entityConfigRepositoryLoader.loadEntityConfigurations(this);
 	}
 
 	/**
@@ -428,7 +410,7 @@
 			EnumerationDefinition bubu = applicationStructure.getEnumerationDefinition(eas.enumName);
 			for (String itemName : bubu.listItemNames()) {
 				final int intValue = bubu.getItem(itemName);
-				LOGGER.debug("{}:{}:{}", eas.enumName, itemName, intValue);
+				LOGGER.trace("{}:{}:{}", eas.enumName, itemName, intValue);
 			}
 
 			// make sure the enumeration is found
@@ -482,284 +464,4 @@
 
 		return units;
 	}
-
-	/**
-	 * Loads the {@link EntityConfig}s.
-	 */
-	private void loadEntityConfigurations() {
-		LOGGER.debug("Loading entity configurations...");
-		long start = System.currentTimeMillis();
-
-		entityConfigRepository = new EntityConfigRepository();
-
-		// Environment | Project | Pool | PhysicalDimension | User | Measurement
-		// | ChannelGroup
-		entityConfigRepository.register(create(new Key<>(Environment.class), "Environment", false));
-		entityConfigRepository.register(create(new Key<>(Project.class), "Project", false));
-		entityConfigRepository.register(create(new Key<>(Pool.class), "StructureLevel", true));
-		entityConfigRepository.register(create(new Key<>(PhysicalDimension.class), "PhysDimension", false));
-		entityConfigRepository.register(create(new Key<>(User.class), "User", false));
-		entityConfigRepository.register(create(new Key<>(ChannelGroup.class), "SubMatrix", false));
-		entityConfigRepository.register(create(new Key<>(Status.class), "Status", true));
-
-		// Project Domain
-		EntityConfig<ProjectDomain> projectDomainEntityConfig = create(new Key<>(ProjectDomain.class), "ProjectDomain", true);
-		entityConfigRepository.register(projectDomainEntityConfig);
-
-		// Domain
-		EntityConfig<Domain> domainEntityConfig = create(new Key<>(Domain.class), "Domain", true);
-		entityConfigRepository.register(domainEntityConfig);
-
-		// Classification
-		EntityConfig<Classification> classificationEntityConfig = create(new Key<>(Classification.class), "Classification", true);
-		classificationEntityConfig.addOptional(entityConfigRepository.findRoot(new Key<>(ProjectDomain.class)));
-		classificationEntityConfig.addOptional(entityConfigRepository.findRoot(new Key<>(Domain.class)));
-		classificationEntityConfig.addOptional(entityConfigRepository.findRoot(new Key<>(Status.class)));
-		entityConfigRepository.register(classificationEntityConfig);
-
-		EntityConfig<Measurement> measurementEntityConfig = create(new Key<>(Measurement.class), "MeaResult", false);
-		measurementEntityConfig.addOptional(entityConfigRepository.findRoot(new Key<>(Classification.class)));
-		entityConfigRepository.register(measurementEntityConfig);
-
-		// Unit
-		EntityConfig<Unit> unitConfig = create(new Key<>(Unit.class), "Unit", false);
-		unitConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(PhysicalDimension.class)));
-		entityConfigRepository.register(unitConfig);
-
-		// Quantity
-		EntityConfig<Quantity> quantityConfig = create(new Key<>(Quantity.class), "Quantity", false);
-		quantityConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Unit.class)));
-		entityConfigRepository.register(quantityConfig);
-
-		// Channel
-		EntityConfig<Channel> channelConfig = create(new Key<>(Channel.class), "MeaQuantity", false);
-		channelConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Unit.class)));
-		channelConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Quantity.class)));
-		entityConfigRepository.register(channelConfig);
-
-		// ValueList
-		EntityConfig<ValueListValue> valueListValueConfig = create(new Key<>(ValueListValue.class), "ValueListValue",
-				true);
-		valueListValueConfig.setComparator(Sortable.COMPARATOR);
-		EntityConfig<ValueList> valueListConfig = create(new Key<>(ValueList.class), "ValueList", true);
-		valueListConfig.addChild(valueListValueConfig);
-		entityConfigRepository.register(valueListConfig);
-
-		// ParameterSet
-		EntityConfig<Parameter> parameterConfig = create(new Key<>(Parameter.class), "ResultParameter", true);
-		parameterConfig.addOptional(entityConfigRepository.findRoot(new Key<>(Unit.class)));
-		EntityConfig<ParameterSet> parameterSetConfig = create(new Key<>(ParameterSet.class), "ResultParameterSet",
-				true);
-		parameterSetConfig.addChild(parameterConfig);
-		entityConfigRepository.register(parameterSetConfig);
-
-		// CatalogComponents
-		registerCatalogComponent(ContextType.UNITUNDERTEST);
-		registerCatalogComponent(ContextType.TESTSEQUENCE);
-		registerCatalogComponent(ContextType.TESTEQUIPMENT);
-
-		// TemplateRoots
-		registerTemplateRoot(ContextType.UNITUNDERTEST);
-		registerTemplateRoot(ContextType.TESTSEQUENCE);
-		registerTemplateRoot(ContextType.TESTEQUIPMENT);
-
-		// TemplateTestStep
-		EntityConfig<TemplateTestStep> templateTestStepConfig = create(new Key<>(TemplateTestStep.class), "TplTestStep",
-				true);
-		templateTestStepConfig
-				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.UNITUNDERTEST)));
-		templateTestStepConfig
-				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.TESTSEQUENCE)));
-		templateTestStepConfig
-				.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, ContextType.TESTEQUIPMENT)));
-		templateTestStepConfig.setComparator(Versionable.COMPARATOR);
-		entityConfigRepository.register(templateTestStepConfig);
-
-		// Status TestStep
-		// TODO check MIME type genration
-		// entityConfigRepository.register(create(new Key<>(Status.class,
-		// TestStep.class), "StatusTestStep", true));
-
-		// TestStep
-		EntityConfig<TestStep> testStepConfig = create(new Key<>(TestStep.class), "TestStep", true);
-		// testStepConfig.addMandatory(entityConfigRepository.findRoot(new
-		// Key<>(Status.class, TestStep.class)));
-		testStepConfig.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateTestStep.class)));
-		testStepConfig.setComparator(Sortable.COMPARATOR);
-		entityConfigRepository.register(testStepConfig);
-
-		// TemplateTest
-		EntityConfig<TemplateTestStepUsage> templateTestStepUsageConfig = create(new Key<>(TemplateTestStepUsage.class),
-				"TplTestStepUsage", true);
-		templateTestStepUsageConfig.addMandatory(templateTestStepConfig);
-		templateTestStepUsageConfig.setComparator(Sortable.COMPARATOR);
-		EntityConfig<TemplateTest> templateTestConfig = create(new Key<>(TemplateTest.class), "TplTest", true);
-		templateTestConfig.addChild(templateTestStepUsageConfig);
-		templateTestConfig.setComparator(Versionable.COMPARATOR);
-		entityConfigRepository.register(templateTestConfig);
-
-		// Status Test
-		// TODO check MIME type genration
-		// entityConfigRepository.register(create(new Key<>(Status.class,
-		// Test.class), "StatusTest", true));
-
-		// Test
-		EntityConfig<Test> testConfig = create(new Key<>(Test.class), "Test", true);
-		testConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(User.class)));
-		// testConfig.addMandatory(entityConfigRepository.findRoot(new
-		// Key<>(Status.class, Test.class)));
-		testConfig.addOptional(entityConfigRepository.findRoot(new Key<>(TemplateTest.class)));
-		entityConfigRepository.register(testConfig);
-
-		// ContextRoots
-		registerContextRoot(ContextType.UNITUNDERTEST);
-		registerContextRoot(ContextType.TESTSEQUENCE);
-		registerContextRoot(ContextType.TESTEQUIPMENT);
-
-		LOGGER.debug("Entity configurations loaded in {} ms.", System.currentTimeMillis() - start);
-	}
-
-	/**
-	 * Loads the {@link EntityConfig}s required for {@link ContextRoot} with
-	 * given {@link ContextType}.
-	 *
-	 * @param contextType
-	 *            The {@code ContextType}.
-	 */
-	private void registerContextRoot(ContextType contextType) {
-		EntityConfig<ContextRoot> contextRootConfig = create(new Key<>(ContextRoot.class, contextType),
-				ODSUtils.CONTEXTTYPES.get(contextType), true);
-		contextRootConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(TemplateRoot.class, contextType)));
-		for (Relation contextComponentRelation : contextRootConfig.getEntityType().getChildRelations()) {
-			EntityType contextComponentEntityType = contextComponentRelation.getTarget();
-			EntityConfig<ContextComponent> contextComponentConfig = create(
-					new Key<>(ContextComponent.class, contextType), contextComponentEntityType.getName(), true);
-			contextComponentConfig
-					.addInherited(entityConfigRepository.findImplicit(new Key<>(TemplateComponent.class, contextType)));
-			contextRootConfig.addChild(contextComponentConfig);
-			if (contextType.isTestEquipment()) {
-				for (Relation contextSensorRelation : contextComponentEntityType.getChildRelations()) {
-					EntityType contextSensorEntityType = contextSensorRelation.getTarget();
-					EntityConfig<ContextSensor> contextSensorConfig = create(new Key<>(ContextSensor.class),
-							contextSensorEntityType.getName(), true);
-					contextSensorConfig
-							.addInherited(entityConfigRepository.findImplicit(new Key<>(TemplateSensor.class)));
-					contextComponentConfig.addChild(contextSensorConfig);
-				}
-			}
-		}
-		entityConfigRepository.register(contextRootConfig);
-	}
-
-	/**
-	 * Loads the {@link EntityConfig}s required for {@link TemplateRoot} with
-	 * given {@link ContextType}.
-	 *
-	 * @param contextType
-	 *            The {@code ContextType}.
-	 */
-	private void registerTemplateRoot(ContextType contextType) {
-		String odsName = ODSUtils.CONTEXTTYPES.get(contextType);
-		EntityConfig<TemplateAttribute> templateAttributeConfig = create(
-				new Key<>(TemplateAttribute.class, contextType), "Tpl" + odsName + "Attr", true);
-		templateAttributeConfig
-				.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogAttribute.class, contextType)));
-		templateAttributeConfig.setComparator(TemplateAttribute.COMPARATOR);
-		EntityConfig<TemplateComponent> templateComponentConfig = create(
-				new Key<>(TemplateComponent.class, contextType), "Tpl" + odsName + "Comp", true);
-		templateComponentConfig.addChild(templateAttributeConfig);
-		templateComponentConfig
-				.addMandatory(entityConfigRepository.findRoot(new Key<>(CatalogComponent.class, contextType)));
-		templateComponentConfig.addChild(templateComponentConfig);
-		templateComponentConfig.setComparator(Sortable.COMPARATOR);
-		if (contextType.isTestEquipment()) {
-			EntityConfig<TemplateAttribute> templateSensorAttributeConfig = create(new Key<>(TemplateAttribute.class),
-					"TplSensorAttr", true);
-			templateSensorAttributeConfig.setComparator(TemplateAttribute.COMPARATOR);
-			templateSensorAttributeConfig
-					.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogAttribute.class)));
-			EntityConfig<TemplateSensor> templateSensorConfig = create(new Key<>(TemplateSensor.class), "TplSensor",
-					true);
-			templateSensorConfig.addChild(templateSensorAttributeConfig);
-			templateSensorConfig.addMandatory(entityConfigRepository.findRoot(new Key<>(Quantity.class)));
-			templateSensorConfig.addInherited(entityConfigRepository.findImplicit(new Key<>(CatalogSensor.class)));
-			templateSensorConfig.setComparator(Sortable.COMPARATOR);
-			templateComponentConfig.addChild(templateSensorConfig);
-		}
-		EntityConfig<TemplateRoot> templateRootConfig = create(new Key<>(TemplateRoot.class, contextType),
-				"Tpl" + odsName + "Root", true);
-		templateRootConfig.addChild(templateComponentConfig);
-		templateRootConfig.setComparator(Versionable.COMPARATOR);
-		entityConfigRepository.register(templateRootConfig);
-	}
-
-	/**
-	 * Loads the {@link EntityConfig}s required for {@link CatalogComponent}
-	 * with given {@link ContextType}.
-	 *
-	 * @param contextType
-	 *            The {@code ContextType}.
-	 */
-	private void registerCatalogComponent(ContextType contextType) {
-		String odsName = ODSUtils.CONTEXTTYPES.get(contextType);
-		EntityConfig<CatalogAttribute> catalogAttributeConfig = create(new Key<>(CatalogAttribute.class, contextType),
-				"Cat" + odsName + "Attr", true);
-		catalogAttributeConfig.addOptional(entityConfigRepository.findRoot(new Key<>(ValueList.class)));
-		catalogAttributeConfig.setComparator(Sortable.COMPARATOR);
-		EntityConfig<CatalogComponent> catalogComponentConfig = create(new Key<>(CatalogComponent.class, contextType),
-				"Cat" + odsName + "Comp", true);
-		catalogComponentConfig.addChild(catalogAttributeConfig);
-		if (contextType.isTestEquipment()) {
-			EntityConfig<CatalogAttribute> catalogSensorAttributeConfig = create(new Key<>(CatalogAttribute.class),
-					"CatSensorAttr", true);
-			catalogSensorAttributeConfig.addOptional(entityConfigRepository.findRoot(new Key<>(ValueList.class)));
-			EntityConfig<CatalogSensor> catalogSensorConfig = create(new Key<>(CatalogSensor.class), "CatSensor", true);
-			catalogSensorConfig.addChild(catalogSensorAttributeConfig);
-			catalogComponentConfig.addChild(catalogSensorConfig);
-		}
-		entityConfigRepository.register(catalogComponentConfig);
-	}
-
-	/**
-	 * Creates a new {@link EntityConfig}.
-	 *
-	 * @param <T>
-	 *            The entity type.
-	 * @param key
-	 *            Used as identifier.
-	 * @param typeName
-	 *            Name of the associated {@link EntityType}.
-	 * @param appendName
-	 *            Flag indicates whether to append the entity types base name to
-	 *            the MIME type.
-	 * @return The created {@code EntityConfig} is returned.
-	 */
-	private <T extends Entity> EntityConfig<T> create(Key<T> key, String typeName, boolean appendName) {
-		EntityConfig<T> entityConfig = new EntityConfig<>(key);
-		ODSEntityType entityType = (ODSEntityType) getEntityType(typeName);
-		entityConfig.setEntityType(entityType);
-		entityConfig.setMimeType(buildDefaultMimeType(entityType, appendName));
-		return entityConfig;
-	}
-
-	/**
-	 * Creates a default MIME type for given {@link EntityType}.
-	 *
-	 * @param entityType
-	 *            The {@code EntityType}.
-	 * @param appendName
-	 *            Flag indicates whether to append the entity types base name to
-	 *            the MIME type.
-	 * @return The created MIME type {@code String} is returned.
-	 */
-	private String buildDefaultMimeType(ODSEntityType entityType, boolean appendName) {
-		StringBuilder sb = new StringBuilder();
-		sb.append("application/x-asam.");
-		sb.append(entityType.getBaseName().toLowerCase(Locale.ROOT));
-		if (appendName) {
-			sb.append('.').append(entityType.getName().toLowerCase(Locale.ROOT));
-		}
-		return sb.toString();
-	}
-
 }
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSRelation.java b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSRelation.java
index a6055c3..f70933a 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSRelation.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/query/ODSRelation.java
@@ -44,7 +44,7 @@
 	private final String name;
 
 	private final int rangeMax;
-
+	private final int invRangeMax;
 	private Attribute attribute;
 
 	// ======================================================================
@@ -66,7 +66,8 @@
 		this.target = target;
 		name = applRel.arName;
 		relationType = ODSUtils.RELATIONSHIPS.inverse().get(applRel.arRelationType);
-		rangeMax = applRel.arRelationRange.max;
+		rangeMax = applRel.arRelationRange.max;

+		invRangeMax = applRel.invRelationRange.max;
 	}
 
 	// ======================================================================
@@ -163,6 +164,11 @@
 	@Override
 	public boolean isIncoming(RelationType relationType) {
 		return relationType.equals(getRelationType()) && rangeMax == -1;
+	}

+	

+	@Override

+	public boolean isNtoM() {

+		return relationType == RelationType.INFO && rangeMax == -1 && invRangeMax == -1;

 	}
 
 }
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
index 0fc5e90..1408064 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/InsertStatement.java
@@ -271,15 +271,16 @@
 		EntityType testStep = getEntityType();
 		EntityType test = getModelManager().getEntityType(Test.class);
 		Relation parentRelation = testStep.getRelation(test);
-
+

+		Attribute attrSortIndex = testStep.getAttribute(Sortable.ATTR_SORT_INDEX);
 		Query query = getQueryService().createQuery().select(parentRelation.getAttribute())
-				.select(testStep.getAttribute(Sortable.ATTR_SORT_INDEX), Aggregation.MAXIMUM)
+				.select(attrSortIndex, Aggregation.MAXIMUM)
 				.group(parentRelation.getAttribute());
 
 		Filter filter = Filter.idsOnly(parentRelation, sortIndexTestSteps.keySet());
 		for (Result result : query.fetch(filter)) {
 			Record record = result.getRecord(testStep);
-			int sortIndex = (Integer) record.getValues().get(Sortable.ATTR_SORT_INDEX).extract();
+			int sortIndex = (Integer) record.getValues().get(ODSConverter.getColumnName(attrSortIndex, Aggregation.MAXIMUM)).extract();
 			sortIndexTestSteps.remove(record.getID(parentRelation).get()).setIndices(sortIndex + 1);
 		}
 
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/ODSTransaction.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/ODSTransaction.java
index a4bf4b9..952c6b6 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/ODSTransaction.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/ODSTransaction.java
@@ -15,43 +15,51 @@
 
 package org.eclipse.mdm.api.odsadapter.transaction;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.UUID;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import org.asam.ods.AoException;
-import org.eclipse.mdm.api.base.Transaction;
-import org.eclipse.mdm.api.base.adapter.Core;
-import org.eclipse.mdm.api.base.adapter.EntityType;
-import org.eclipse.mdm.api.base.massdata.WriteRequest;
-import org.eclipse.mdm.api.base.model.Channel;
-import org.eclipse.mdm.api.base.model.ContextRoot;
-import org.eclipse.mdm.api.base.model.Deletable;
-import org.eclipse.mdm.api.base.model.Entity;
-import org.eclipse.mdm.api.base.model.FileLink;
-import org.eclipse.mdm.api.base.model.Measurement;
-import org.eclipse.mdm.api.base.model.ScalarType;
-import org.eclipse.mdm.api.base.model.TestStep;
-import org.eclipse.mdm.api.base.model.Value;
-import org.eclipse.mdm.api.base.model.ValueType;
-import org.eclipse.mdm.api.base.query.DataAccessException;
-import org.eclipse.mdm.api.dflt.model.CatalogAttribute;
-import org.eclipse.mdm.api.dflt.model.CatalogComponent;
-import org.eclipse.mdm.api.dflt.model.CatalogSensor;
-import org.eclipse.mdm.api.dflt.model.TemplateAttribute;
-import org.eclipse.mdm.api.odsadapter.ODSContext;
-import org.eclipse.mdm.api.odsadapter.filetransfer.Transfer;
-import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
-import org.eclipse.mdm.api.odsadapter.utils.ODSUtils;
-import org.slf4j.Logger;
+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.Collection;

+import java.util.Collections;

+import java.util.HashSet;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Set;

+import java.util.UUID;

+import java.util.function.Function;

+import java.util.stream.Collectors;

+

+import org.asam.ods.AoException;

+import org.asam.ods.ElemId;

+import org.asam.ods.SetType;

+import org.asam.ods.T_LONGLONG;

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

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

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

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

+import org.eclipse.mdm.api.base.massdata.WriteRequest;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.odsadapter.filetransfer.Transfer;

+import org.eclipse.mdm.api.odsadapter.query.ODSEntityFactory;

+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.eclipse.mdm.api.odsadapter.utils.ODSUtils;

+import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;
 
 /**
@@ -169,14 +177,17 @@
 			}
 
 			List<Measurement> measurements = (List<Measurement>) entitiesByClassType.get(Measurement.class);
-			if (measurements != null) {
-				// use set here, since measurement sibling point to the same
-				// context roots
-				create(measurements.stream().map(ContextRoot::of).collect(HashSet::new, Set::addAll, Set::addAll));
+			if (measurements != null) {

+				// Use set here, since measurement siblings point to the same

+				// context roots. Only create those ContextRoots that haven't been created yet:

+				create(measurements.stream().map(ContextRoot::of)

+						.collect(HashSet<ContextRoot>::new, Set<ContextRoot>::addAll, Set<ContextRoot>::addAll)

+						.stream().filter(cr -> !ODSUtils.isValidID(cr.getID())).collect(Collectors.toSet()));
 			}
 
 			executeStatements(et -> new InsertStatement(this, et), entities);
-
+			processNtoMRelations(entities);

+			
 			List<ContextRoot> roots = (List<ContextRoot>) entitiesByClassType.get(ContextRoot.class);
 			if (roots != null) {
 				roots.forEach(contextRoot -> {
@@ -223,7 +234,8 @@
 					getUploadService().upload(filtered, null);
 				}
 			}
-			executeStatements(et -> new UpdateStatement(this, et, false), entities);
+			executeStatements(et -> new UpdateStatement(this, et, false), entities);

+			processNtoMRelations(entities);
 		} catch (AoException e) {
 			throw new DataAccessException("Unable to update entities due to: " + e.reason, e);
 		} catch (IOException e) {
@@ -490,6 +502,55 @@
 		}
 	}
 
+	/**

+	 * Processes N-to-M relations for the given entities

+	 * @param entities

+	 *            The processed {@code Entity}s.

+	 * @throws DataAccessException

+	 *             Thrown if the execution fails.

+	 */

+	private <T extends Entity> void processNtoMRelations(Collection<T> entities) {

+		for (Entity e : entities) {

+			context.getODSModelManager().getEntityType(e)

+				.getRelations()

+				.stream()

+				.filter(Relation::isNtoM)

+				.forEach(r -> processMtoMRelation(e, r));

+		}

+	}

+

+	private void processMtoMRelation(Entity entity, Relation relation) {

+		

+		List<? extends Deletable> removedRelatedEntities = ODSEntityFactory.extract(entity).getNtoMStore().getRemoved().getOrDefault(relation.getName(), Collections.emptyList());

+		List<? extends Deletable> addedRelatedEntities = ODSEntityFactory.extract(entity).getNtoMStore().getAdded().getOrDefault(relation.getName(), Collections.emptyList());

+		

+		T_LONGLONG[] removedInstIds = removedRelatedEntities.stream()

+				.map(Entity::getID)

+				.map(ODSConverter::toODSID)

+				.toArray(T_LONGLONG[]::new);

+		

+		T_LONGLONG[] addedInstIds = addedRelatedEntities.stream()

+				.map(Entity::getID)

+				.map(ODSConverter::toODSID)

+				.toArray(T_LONGLONG[]::new);

+

+		try {

+			if (removedInstIds.length > 0) {

+				ODSEntityType entityType = ((ODSEntityType) context.getODSModelManager().getEntityType(entity));

+				ElemId elemId = new ElemId(entityType.getODSID(), ODSConverter.toODSID(entity.getID()));

+				context.getAoSession().getApplElemAccess().setRelInst(elemId, relation.getName(), removedInstIds, SetType.REMOVE);

+			}

+			

+			if (addedInstIds.length > 0) {

+				ODSEntityType entityType = ((ODSEntityType) context.getODSModelManager().getEntityType(entity));

+				ElemId elemId = new ElemId(entityType.getODSID(), ODSConverter.toODSID(entity.getID()));

+				context.getAoSession().getApplElemAccess().setRelInst(elemId, relation.getName(), addedInstIds, SetType.APPEND);

+			}

+		} catch (AoException e) {

+			throw new DataAccessException("" + e.reason, e); // TODO

+		}

+	}

+

 	/**
 	 * Closes the co-session of this transaction.
 	 */
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UpdateStatement.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UpdateStatement.java
index f6fa7c8..49040b4 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UpdateStatement.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/UpdateStatement.java
@@ -38,7 +38,8 @@
 import org.eclipse.mdm.api.base.model.FileLink;
 import org.eclipse.mdm.api.base.model.FilesAttachable;
 import org.eclipse.mdm.api.base.model.Value;
-import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.api.base.query.DataAccessException;

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

 import org.eclipse.mdm.api.odsadapter.lookup.config.EntityConfig;
 import org.eclipse.mdm.api.odsadapter.query.ODSEntityFactory;
 import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
@@ -125,6 +126,10 @@
 			if (nonUpdatableRelationNames.contains(entry.getKey())) {
 				// skip "empty" informative relation sequence
 				continue;
+			}

+			if (entry.getKey().equalsIgnoreCase(Role.ATTR_SUPERUSER_FLAG)) {

+				// skip superuser flag as it cannot be written through the ODS API

+				continue;

 			}
 
 			Attribute attribute = getEntityType().getAttribute(entry.getKey());
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
index 34c8d2c..8a27cfd 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
@@ -127,7 +127,7 @@
 		Map<String, Value> values = core.getValues();
 		values.get(Entity.ATTR_NAME).set(writeRequest.getChannel().getName());
 		values.get(Entity.ATTR_MIMETYPE).set("application/x-asam.aolocalcolumn");
-		values.get(AE_LC_ATTR_INDEPENDENT).set(ODSConverter.toODSValidFlag(writeRequest.isIndependent()));
+		values.get(AE_LC_ATTR_INDEPENDENT).set((short) (writeRequest.isIndependent() ? 1 : 0));
 		values.get(AE_LC_ATTR_RAWDATATYPE).set(writeRequest.getRawScalarType());
 		values.get(AE_LC_ATTR_REPRESENTATION).set(writeRequest.getSequenceRepresentation());
 		values.get(AE_LC_ATTR_AXISTYPE).set(writeRequest.getAxisType());
@@ -137,26 +137,24 @@
 			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()));
-
-			if (writeRequest.getSequenceRepresentation().isImplicit()) {
-				// PEAK ODS server: expects values written as generation
-				// parameters
-				Object genParamValues = writeRequest.getValues();
-				double[] genParamD = new double[Array.getLength(genParamValues)];
-				IntStream.range(0, genParamD.length)
-						.forEach(i -> genParamD[i] = ((Number) Array.get(genParamValues, i)).doubleValue());
-				values.get(AE_LC_ATTR_PARAMETERS).set(genParamD);
-			}
+					valueType.create(AE_LC_ATTR_VALUES, unitName, true, writeRequest.getValues()));

+			

+			// OpenATFX issue: For "implicit" columns, if no value for the GenerationParameters attribute is present,

+			// it is attempted to transfer the local column values (through which the generation parameters are

+			// available in these cases) to the GenerationParameters attribute without converting them to the 

+			// correct DS_DOUBLE data type first (unless it is a DOUBLE or LONG column), resulting in an exception.

+			// Hence, supply correctly converted generation parameters as a workaround:

+			if (writeRequest.getSequenceRepresentation().isImplicit()) {

+				Object genParamValues = writeRequest.getValues();

+				double[] genParamD = new double[Array.getLength(genParamValues)];

+				IntStream.range(0, genParamD.length)

+						.forEach(i -> genParamD[i] = ((Number) Array.get(genParamValues, i)).doubleValue());

+				values.get(AE_LC_ATTR_PARAMETERS).set(genParamD);

+			}

 
 			// flags
 			if (writeRequest.areAllValid()) {
 				values.get(AE_LC_ATTR_GLOBAL_FLAG).set((short) 15);
-				// PEAK ODS server issue: though global flag is true a flags
-				// array is expected
-				short[] flags = new short[Array.getLength(writeRequest.getValues())];
-				Arrays.fill(flags, (short) 15);
-				values.get(AE_LC_ATTR_FLAGS).set(flags);
 			} else {
 				short[] flags = ODSConverter.toODSValidFlagSeq(writeRequest.getFlags());
 				values.get(AE_LC_ATTR_FLAGS).set(flags);
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java b/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
index ae893ca..2df4f23 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
@@ -21,7 +21,8 @@
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoField;
-import java.util.ArrayList;
+import java.util.ArrayList;

+import java.util.Arrays;

 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -49,7 +50,8 @@
 import org.eclipse.mdm.api.base.model.Value;
 import org.eclipse.mdm.api.base.model.ValueType;
 import org.eclipse.mdm.api.base.query.Aggregation;
-import org.eclipse.mdm.api.base.query.DataAccessException;
+import org.eclipse.mdm.api.base.query.DataAccessException;

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

 import org.eclipse.mdm.api.odsadapter.query.ODSAttribute;
 
 import com.google.common.collect.Sets;
@@ -334,15 +336,26 @@
 					&& attribute.getValueType().isEnumerationType()
 					&& Sets.immutableEnumSet(Aggregation.MINIMUM, Aggregation.MAXIMUM, Aggregation.DISTINCT)
 							.contains(aggregation)) {
-				return valueType.create(String.format("%s(%s)", aggregation.name(), attribute.getName()), unit, valid,
+				return valueType.create(getColumnName(attribute, aggregation), unit, valid,
 						input, attribute.getEnumObj().getName());
 			} else {
-				return valueType.create(String.format("%s(%s)", aggregation.name(), attribute.getName()), unit, valid,
+				return valueType.create(getColumnName(attribute, aggregation), unit, valid,
 						input);
 			}
 		}
 	}
-
+

+	/**

+	 * Returns the name of the attribute with its applied aggregation as returned by the ODS Server,

+	 * for example: MAXIMUM(sortIndex)

+	 * @param attribute 

+	 * @param aggregation

+	 * @return the name of the attribute with the applied aggragation

+	 */

+	public static String getColumnName(Attribute attribute, Aggregation aggregation) {

+		return String.format("%s(%s)", aggregation.name(), attribute.getName());

+	}

+	
 	private static String[] toString(int[] odsValues) {
 		return IntStream.of(odsValues).mapToObj(Integer::toString).toArray(String[]::new);
 	}
@@ -652,12 +665,18 @@
 	 * @throws DataAccessException
 	 *             Thrown on conversion errors.
 	 */
-	public static List<MeasuredValues> fromODSMeasuredValuesSeq(NameValueSeqUnit[] odsMeasuredValuesSeq)
+	public static List<MeasuredValues> fromODSMeasuredValuesSeq(NameValueSeqUnit[] odsMeasuredValuesSeq, ReadRequestHandler.ColumnAttributes[] columnAttributesArray)
 			throws DataAccessException {
-		List<MeasuredValues> measuredValues = new ArrayList<>(odsMeasuredValuesSeq.length);
+		List<MeasuredValues> measuredValues = new ArrayList<>(odsMeasuredValuesSeq.length);

+		

+		Map<String, ReadRequestHandler.ColumnAttributes> mapColumnAttributes = new HashMap<>();

+		if (null != columnAttributesArray)

+		{

+			Arrays.stream(columnAttributesArray).forEach(ca -> mapColumnAttributes.put(ca.getName(), ca));

+		}
 
 		for (NameValueSeqUnit odsMeasuredValues : odsMeasuredValuesSeq) {
-			measuredValues.add(fromODSMeasuredValues(odsMeasuredValues));
+			measuredValues.add(fromODSMeasuredValues(odsMeasuredValues, mapColumnAttributes.get(odsMeasuredValues.valName)));
 		}
 
 		return measuredValues;
@@ -672,7 +691,7 @@
 	 * @throws DataAccessException
 	 *             Thrown on conversion errors.
 	 */
-	private static MeasuredValues fromODSMeasuredValues(NameValueSeqUnit odsMeasuredValues) throws DataAccessException {
+	private static MeasuredValues fromODSMeasuredValues(NameValueSeqUnit odsMeasuredValues, ReadRequestHandler.ColumnAttributes columnAttributes) throws DataAccessException {
 		TS_ValueSeq odsValueSeq = odsMeasuredValues.value;
 		DataType dataType = odsValueSeq.u.discriminator();
 		ScalarType scalarType;
@@ -720,9 +739,20 @@
 		} else {
 			throw new DataAccessException(
 					"Conversion for ODS measured points of type '" + dataType.toString() + "' does not exist.");
+		}

+		

+		if (null == columnAttributes)

+		{

+			columnAttributes = new ReadRequestHandler.ColumnAttributes("", null, new double[0], false, null);

 		}
 
-		return scalarType.createMeasuredValues(odsMeasuredValues.valName, odsMeasuredValues.unit, values,
+		return scalarType.createMeasuredValues(odsMeasuredValues.valName,

+				odsMeasuredValues.unit, 

+				columnAttributes.getSequenceRepresentation(),

+				columnAttributes.getGenerationParameters(),

+				columnAttributes.isIndependentColumn(),

+				columnAttributes.getAxisType(),

+				values,
 				fromODSValidFlagSeq(odsValueSeq.flag));
 	}
 
diff --git a/src/test/java/org/eclipse/mdm/api/odsadapter/ODSRoleTest.java b/src/test/java/org/eclipse/mdm/api/odsadapter/ODSRoleTest.java
new file mode 100644
index 0000000..f136664
--- /dev/null
+++ b/src/test/java/org/eclipse/mdm/api/odsadapter/ODSRoleTest.java
@@ -0,0 +1,209 @@
+/********************************************************************************

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

+ *

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

+
+
+package org.eclipse.mdm.api.odsadapter;
+
+import static org.assertj.core.api.Assertions.assertThat;

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

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

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

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

+

+import java.lang.reflect.InvocationTargetException;

+import java.util.Arrays;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

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

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

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

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

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

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

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

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

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;
+
+//@Ignore
+// FIXME 10.7.2017: this test needs a running ODS Server, that is not suitable for continous build in Jenkins.
+// Comment this in for local tests only.
+public class ODSRoleTest {
+
+	/*
+	 * ATTENTION: ==========
+	 *
+	 * To run this test make sure the target service is running a MDM default
+	 * model and any database constraint which enforces a relation of Test to a
+	 * parent entity is deactivated!
+	 */
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(ODSRoleTest.class);
+
+	private static final String NAME_SERVICE = "corbaloc::1.2@%s:%s/NameService";
+
+	private static final String USER = "sa";
+	private static final String PASSWORD = "sa";
+
+	private static ApplicationContext context;
+	private static EntityManager em;
+	private static EntityFactory entityFactory;
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws ConnectionException {
+		String nameServiceHost = System.getProperty("host");
+		String nameServicePort = System.getProperty("port");
+		String serviceName = System.getProperty("service");
+
+		if (nameServiceHost == null || nameServiceHost.isEmpty()) {
+			throw new IllegalArgumentException("name service host is unknown: define system property 'host'");
+		}
+
+		nameServicePort = nameServicePort == null || nameServicePort.isEmpty() ? String.valueOf(2809) : nameServicePort;
+		if (nameServicePort == null || nameServicePort.isEmpty()) {
+			throw new IllegalArgumentException("name service port is unknown: define system property 'port'");
+		}
+
+		if (serviceName == null || serviceName.isEmpty()) {
+			throw new IllegalArgumentException("service name is unknown: define system property 'service'");
+		}
+
+		Map<String, String> connectionParameters = new HashMap<>();
+		connectionParameters.put(PARAM_NAMESERVICE, String.format(NAME_SERVICE, nameServiceHost, nameServicePort));
+		connectionParameters.put(PARAM_SERVICENAME, serviceName + ".ASAM-ODS");
+		connectionParameters.put(PARAM_USER, USER);
+		connectionParameters.put(PARAM_PASSWORD, PASSWORD);
+
+		context  = new ODSContextFactory().connect(connectionParameters);
+		em = context.getEntityManager()
+				.orElseThrow(() -> new ServiceNotProvidedException(EntityManager.class));
+		entityFactory = context.getEntityFactory()
+				.orElseThrow(() -> new IllegalStateException("Entity manager factory not available."));
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws ConnectionException {
+		if (context != null) {
+			context.close();
+		}
+	}
+	

+	@org.junit.Test

+	public void testLoadRelatedUsers() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

+		User user = em.loadAll(User.class, "sa").get(0);

+		List<Role> role = em.loadRelatedEntities(user, "users2groups", Role.class);

+		

+		assertThat(role).hasSize(1).extracting(Role::getName).containsExactly("MDMSystemAdministrator");

+	}

+	

+	@org.junit.Test

+	public void testLoadRelatedRoles() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

+		Role role = em.loadAll(Role.class, "MDMSystemAdministrator").get(0);

+		List<User> user = em.loadRelatedEntities(role, "groups2users", User.class);

+		

+		assertThat(user).hasSize(1).extracting(User::getName).containsExactly("sa");

+	}

+	

+	@org.junit.Test

+	public void testCreateUserAndRole() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

+		String userName = "CreateUserAndRole";

+		String roleName = "CreateUserAndRole";

+			

+		try {

+			Transaction transaction = em.startTransaction();

+			User user = entityFactory.createUser(userName, "Test", "User");

+			Role role = entityFactory.createRole(roleName);

+			role.addUser(user);

+			

+			transaction.create(Arrays.asList(user, role));

+			transaction.commit();

+			

+			assertThat(em.loadRelatedEntities(role, "users2groups", User.class))

+				.hasSize(1)

+				.extracting(User::getName)

+				.contains(userName);

+			

+			assertThat(em.loadRelatedEntities(user, "groups2users", Role.class))

+				.hasSize(1)

+				.extracting(Role::getName)

+				.contains(roleName);

+		} finally {

+			Transaction transaction = em.startTransaction();

+			List<User> users = em.loadAll(User.class, userName);

+			transaction.delete(users);

+			List<Role> roles = em.loadAll(Role.class, roleName);

+			transaction.delete(roles);

+			transaction.commit();

+		}

+	}

+	

+	@org.junit.Test

+	public void testAddMultipleUsersToNewRole() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

+		String roleName = "AddMultipleUsersToNewRole";

+		try {

+			Transaction transaction = em.startTransaction();

+			List<User> users = em.loadAll(User.class);

+			Role role = entityFactory.createRole(roleName);

+			

+			users.forEach(u -> role.addUser(u));

+			transaction.create(Arrays.asList(role));

+			transaction.commit();

+			

+			assertThat(em.loadRelatedEntities(role, "users2groups", User.class))

+				.hasSize(users.size());

+		} finally {

+			Transaction transaction = em.startTransaction();

+			List<Role> roles = em.loadAll(Role.class, roleName);

+			transaction.delete(roles);

+			transaction.commit();

+		}

+	}

+	

+	@org.junit.Test

+	public void testRemoveUserFromRole() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

+		String userName = "RemoveUserFromRole";

+		try {

+			Transaction transaction = em.startTransaction();

+			Role role = em.load(Role.class, "1");

+			User user = entityFactory.createUser(userName, "User", "User");

+			

+			role.addUser(user);

+			

+			transaction.create(Arrays.asList(user));

+			transaction.update(Arrays.asList(role));

+			transaction.commit();

+			

+			assertThat(em.loadRelatedEntities(role, "groups2users", User.class))

+				.hasSize(2);

+			

+			Role role2 = em.load(Role.class, "1");

+			transaction = em.startTransaction();

+			role2.removeUser(user);

+			transaction.update(Arrays.asList(role2));

+			transaction.commit();

+			

+			assertThat(em.loadRelatedEntities(role2, "groups2users", User.class))

+				.hasSize(1);

+		} finally {

+			Transaction transaction = em.startTransaction();

+			List<User> users = em.loadAll(User.class, userName);

+			transaction.delete(users);

+			transaction.commit();

+		}

+	}
+}