525536: make access to entitystores possible and improve documentation and examples for relations

Change-Id: I75c2f2385eade9d25c1dee4626b7d9f20d6e9fdb
Signed-off-by: Florian Schmitt <florian.schmitt@atos.net>
diff --git a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/RelationConfig.java b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/RelationConfig.java
index f014bee..a2dee0d 100644
--- a/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/RelationConfig.java
+++ b/src/main/java/org/eclipse/mdm/api/odsadapter/lookup/RelationConfig.java
@@ -69,7 +69,7 @@
 		if (relatedEntityID.isPresent()) {
 			dependants.computeIfAbsent(relatedEntityID.get(), k -> new ArrayList<>()).add(entityRecord);
 		} else if (mandatory) {
-			throw new IllegalStateException("Mandatory relation unsatisfied.");
+			throw new IllegalStateException("Mandatory relation unsatisfied for relation "+relation.getName());
 		}
 	}
 
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 39e0008..117b241 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
@@ -19,7 +19,9 @@
 import java.util.Set;
 
 import org.eclipse.mdm.api.base.adapter.DefaultCore;
+import org.eclipse.mdm.api.base.core.ChildrenStore;
 import org.eclipse.mdm.api.base.core.Core;
+import org.eclipse.mdm.api.base.core.EntityStore;
 import org.eclipse.mdm.api.base.model.AxisType;
 import org.eclipse.mdm.api.base.model.BaseEntity;
 import org.eclipse.mdm.api.base.model.ContextType;
@@ -162,6 +164,27 @@
 	protected <T extends Entity> Core createCore(Class<T> entityClass, ContextType contextType) {
 		return createCore(new Key<>(entityClass, contextType));
 	}
+		
+	/**
+	 * {@inheritDoc}
+	 */
+	public static final EntityStore getMutableStore(BaseEntity entity) {
+		return EntityFactory.getMutableStore(entity);
+	}
+	
+	/**
+	 * {@inheritDoc}
+	 */
+	public static final EntityStore getPermanentStore(BaseEntity entity) {
+		return EntityFactory.getPermanentStore(entity);
+	}
+	
+	/**
+	 * {@inheritDoc}
+	 */
+	public static final ChildrenStore getChildrenStore(BaseEntity entity) {
+		return EntityFactory.getChildrenStore(entity);
+	}
 
 	/**
 	 * {@inheritDoc}
diff --git a/src/test/java/org/eclipse/mdm/api/odsadapter/RelationTest.java b/src/test/java/org/eclipse/mdm/api/odsadapter/RelationTest.java
new file mode 100755
index 0000000..e14b583
--- /dev/null
+++ b/src/test/java/org/eclipse/mdm/api/odsadapter/RelationTest.java
@@ -0,0 +1,182 @@
+/*

+ * Copyright (c) 2017 Florian Schmitt and others

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

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

+ */

+

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

+

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

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

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

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

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertNotNull;

+

+import java.util.ArrayList;

+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.adapter.EntityType;

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

+import org.eclipse.mdm.api.base.core.EntityStore;

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.odsadapter.search.RelationSearchQuery;

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+import org.junit.Ignore;

+

+@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 RelationTest {

+

+	/*

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

+	 *

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

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

+	 * parent entity is deactivated!

+	 */

+

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

+

+	private static final String USER = "sa";

+	private static final String PASSWORD = "sa";

+

+	private static ApplicationContext context;

+	private static EntityManager entityManager;

+	private static ModelManager modelManager;

+	private static QueryService queryService;

+	private static SearchService searchService;

+	

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

+		entityManager = context.getEntityManager()

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

+		modelManager = context.getModelManager()

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

+		queryService = context.getQueryService()

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

+		searchService = context.getSearchService()

+				.orElseThrow(() -> new IllegalStateException("Search service not available."));

+	}

+

+	@AfterClass

+	public static void tearDownAfterClass() throws ConnectionException {

+		if (context != null) {

+			context.close();

+		}

+	}

+	

+	

+	

+	/**

+	 * changes a relation between instances. There needs to exist a MeaResult of id 1101 and a ParameterSet of id

+	 * 1102 which will be related after running this test.

+	 * If these don't exist, please leave the following line commented, or the test will fail.  

+	 * @throws Exception 

+	 */

+	@org.junit.Test

+	public void changeRelation() throws Exception {

+		String idmea = "1101";

+		String idparamset = "1002";

+

+		EntityType etmeasurement = modelManager.getEntityType(Measurement.class);

+		EntityType etparamset = modelManager.getEntityType(ParameterSet.class);

+		Transaction transaction;

+

+		transaction = entityManager.startTransaction();

+

+		try {

+			List<Measurement> mealist;

+			// we use the searchService to fetch a measurement. Please note, that we

+			// use the per default defined method to fetch the measurement.

+			// This does not load any related ParameterSets! But

+			// we don't care at this point, because we just want to change 

+			// the related ParameterSet to a new one, not look at

+			// the existing relations.

+			mealist = searchService.fetch(Measurement.class, Filter.idOnly(etmeasurement, idmea));

+			assertEquals(1, mealist.size());

+			Measurement mea = mealist.get(0);

+

+			// load the parameter set which we want to relate to our measurment.

+			// again we don't care for any preexisting relations.

+			List<ParameterSet> paramsetlist = new ArrayList<>();

+			ParameterSet paramset = entityManager.load(ParameterSet.class, idparamset);

+			paramsetlist.add(paramset);

+			// Note: we can only set relations in the mutable store (which

+			// doesn't include parent-child-relations)

+			EntityStore store = ODSEntityFactory.getMutableStore(paramset);

+			store.set(mea);

+			assertEquals(store.get(Measurement.class), mea);

+

+			transaction.update(paramsetlist);

+			transaction.commit();

+

+			// reload from database and check if relation is consistent with

+			// expectations

+			// first we need to build our own SearchQuery, because the

+			// predefined ones don't include ParameterSet as stated above.

+			RelationSearchQuery mq = new RelationSearchQuery((ODSModelManager) modelManager,

+					queryService);

+			// the SearchQuery defines how to join measurement and parameterset,

+			// but we also have to declare that we want to fetch the related

+			// measurement

+			List<EntityType> fetchList = new ArrayList<>();

+			fetchList.add(etmeasurement);

+			List<Result> fetch = mq.fetchComplete(fetchList, Filter.idOnly(etparamset, idparamset));

+			assertEquals(fetch.size(), 1);

+			

+			// now we can check that the new relation is there as expected

+			Record record = fetch.get(0).getRecord(etmeasurement);

+			assertNotNull(record);

+			assertEquals(record.getID(), idmea);

+

+		} catch (RuntimeException e) {

+			transaction.abort();

+			throw e;

+		}

+

+	}

+	}

diff --git a/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceTest.java b/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceTest.java
index e7c1a75..372e173 100644
--- a/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceTest.java
+++ b/src/test/java/org/eclipse/mdm/api/odsadapter/search/ODSSearchServiceTest.java
@@ -48,6 +48,7 @@
 import org.eclipse.mdm.api.dflt.model.Pool;
 import org.eclipse.mdm.api.dflt.model.Project;
 import org.eclipse.mdm.api.odsadapter.ODSContextFactory;
+import org.eclipse.mdm.api.odsadapter.ODSEntityManager;
 import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
 import org.eclipse.mdm.api.odsadapter.search.JoinTree.JoinConfig;
 import org.junit.AfterClass;
@@ -151,77 +152,7 @@
 		}
 
 	}
-	
-	/**
-	 * changes a relation between instances. There needs to exist a MeaResult of id 1101 and a ParameterSet of id
-	 * 1102 which will be related after running this test.
-	 * If these don't exist, please leave the following line commented, or the test will fail.  
-	 * @throws Exception 
-	 */
-	//@org.junit.Test
-	public void changeRelation() throws Exception {
-		String idmea = "1101";
-		String idparamset = "1002";
-
-		EntityType etmeasurement = modelManager.getEntityType(Measurement.class);
-		EntityType etparamset = modelManager.getEntityType(ParameterSet.class);
-		Transaction transaction;
-
-		transaction = entityManager.startTransaction();
-
-		try {
-			List<Measurement> mealist;
-			mealist = searchService.fetch(Measurement.class, Filter.idOnly(etmeasurement, idmea));
-			assertEquals(1, mealist.size());
-			Measurement mea = mealist.get(0);
-
-			List<ParameterSet> paramsetlist = new ArrayList<>();
-			ParameterSet paramset = entityManager.load(ParameterSet.class, idparamset);
-			paramsetlist.add(paramset);
-
-			// FIXME: the API should expose the getCore method so that we don't
-			// need to use introspection
-			// to access the core.
-			Method GET_CORE_METHOD;
-			try {
-				GET_CORE_METHOD = BaseEntity.class.getDeclaredMethod("getCore");
-				GET_CORE_METHOD.setAccessible(true);
-			} catch (NoSuchMethodException | SecurityException e) {
-				throw new IllegalStateException(
-						"Unable to load 'getCore()' in class '" + BaseEntity.class.getSimpleName() + "'.", e);
-			}
-			Core core = (Core) GET_CORE_METHOD.invoke(paramset);
-			// Note: we can only set relations in the mutable store (which
-			// doesn't include parent-child-relations)
-			EntityStore store = core.getMutableStore();
-			store.set(mea);
-			assertEquals(core.getMutableStore().get(Measurement.class), mea);
-
-			transaction.update(paramsetlist);
-			transaction.commit();
-
-			// reload from database and check if relation is consistent with
-			// expectations
-			// first we need to build our own SearchQuery, because the
-			// predefined ones don't include ParameterSet
-			MeasurementSearchQuery mq = new MeasurementSearchQuery((ODSModelManager) modelManager,
-					queryService,
-					ContextState.MEASURED);
-			List<EntityType> fetchList = new ArrayList<>();
-			fetchList.add(etmeasurement);
-			List<Result> fetch = mq.fetchComplete(fetchList, Filter.idOnly(etparamset, idparamset));
-			assertEquals(fetch.size(), 1);
-			Record record = fetch.get(0).getRecord(etmeasurement);
-			assertNotNull(record);
-			assertEquals(record.getID(), idmea);
-
-		} catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
-			transaction.abort();
-			throw e;
-		}
-
-	}
-	
+		
 	private Extractor<FilterItem, Tuple> filterExtractors = new Extractor<FilterItem, Tuple>() {
 
 		@Override
diff --git a/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java b/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java
new file mode 100755
index 0000000..4ccb327
--- /dev/null
+++ b/src/test/java/org/eclipse/mdm/api/odsadapter/search/RelationSearchQuery.java
@@ -0,0 +1,62 @@
+/*

+ * Copyright (c) 2017 Florian Schmitt and others

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

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

+ */

+

+package org.eclipse.mdm.api.odsadapter.search;

+

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.odsadapter.search.JoinTree.JoinConfig;

+

+/**

+ * This class is used as a helper to the tests in org.eclipse.mdm.api.odsadapter.RelationTest

+ * It needs to be here, because BaseEntitySearchQuery is declared as protected

+ * in the org.eclipse.mdm.api.odsadapter.search package. 

+ * It makes some sense to leave it protected in that way, because implementing a BaseEntitySearchQuery

+ * requires understanding of the underlying data model which   

+ * The RelationTest requires that the related parametersets to a measurement are

+ * loaded. This class provides a JoinConfig which allows this to happen by defining

+ * the way the entities in question have to be joined. 

+ * If we wouldn't join the ParameterSet in, the related

+ * entities would not be loaded, and it would be impossible to determine whether there

+ * are any related ParameterSets or not.

+ *

+ */

+public final class RelationSearchQuery extends BaseEntitySearchQuery {

+

+	/**

+	 * Constructor.

+	 *

+	 * @param modelManager

+	 *            Used to load {@link EntityType}s.

+	 * @param contextState

+	 *            The {@link ContextState}.

+	 */

+	public RelationSearchQuery(ODSModelManager modelManager, QueryService queryService) {

+		super(modelManager, queryService, ParameterSet.class, Project.class);

+

+		// layers

+		addJoinConfig(JoinConfig.up(Pool.class, Project.class));

+		addJoinConfig(JoinConfig.up(Test.class, Pool.class));

+		addJoinConfig(JoinConfig.up(TestStep.class, Test.class));

+		addJoinConfig(JoinConfig.up(Measurement.class, TestStep.class));

+		addJoinConfig(JoinConfig.up(ParameterSet.class, Measurement.class));

+		addJoinConfig(JoinConfig.down(Measurement.class, Channel.class));

+		addJoinConfig(JoinConfig.down(Measurement.class, ChannelGroup.class));

+

+	}

+

+}
\ No newline at end of file