Bug 574565 - NPE in ContextService if some attributes are not set

Signed-off-by: Matthias Koller <m.koller@peak-solution.de>
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/model/BaseEntityFactory.java b/api/base/src/main/java/org/eclipse/mdm/api/base/model/BaseEntityFactory.java
index 6d78c47..2c64c42 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/model/BaseEntityFactory.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/model/BaseEntityFactory.java
@@ -116,7 +116,7 @@
 
 		return channelGroup;
 	}
-
+		
 	/**
 	 * Creates a new {@link Measurement}.
 	 *
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
index 91de510..195bf38 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
@@ -50,6 +50,8 @@
 import org.eclipse.mdm.businessobjects.utils.Serializer;
 import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
 import org.eclipse.mdm.connector.boundary.ConnectorService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Strings;
 
@@ -58,6 +60,8 @@
 import io.vavr.collection.HashMap;
 import io.vavr.collection.List;
 import io.vavr.collection.Map;
+import io.vavr.control.Option;
+import io.vavr.control.Option.None;
 import io.vavr.control.Option.Some;
 import io.vavr.control.Try;
 
@@ -70,6 +74,8 @@
 @Stateless
 public class ContextService {
 
+	private static final Logger LOGGER = LoggerFactory.getLogger(ContextService.class);
+	
 	@Inject
 	private ConnectorService connectorService;
 
@@ -380,7 +386,7 @@
 
 	private DescribableContexts updateContext(TestStep testStep, Try<Map<String, Object>> contextMap,
 			ContextType[] contextTypes) {
-		Map<ContextType, Object> orderedMap = contextMap.get().get(ContextActivity.CONTEXT_GROUP_ORDERED)
+		Map<ContextType, Object> orderedMap = convertNullValueToNone(contextMap.get().get(ContextActivity.CONTEXT_GROUP_ORDERED))
 				.map(this::transformMap).getOrElse(HashMap.empty())
 				.mapKeys(t -> ServiceUtils.getContextTypeSupplier(t).get())
 				.filterKeys(t -> Arrays.asList(contextTypes).contains(t));
@@ -389,7 +395,7 @@
 		ec.setTestStep(testStep);
 		ec.setOrdered(updateContextDescribableContext(testStep, orderedMap, contextTypes));
 
-		Map<ContextType, Object> measuredMap = contextMap.get().get(ContextActivity.CONTEXT_GROUP_MEASURED)
+		Map<ContextType, Object> measuredMap = convertNullValueToNone(contextMap.get().get(ContextActivity.CONTEXT_GROUP_MEASURED))
 				.map(this::transformMap).getOrElse(HashMap.empty())
 				.mapKeys(t -> ServiceUtils.getContextTypeSupplier(t).get())
 				.filterKeys(t -> Arrays.asList(contextTypes).contains(t));
@@ -558,24 +564,52 @@
 
 	public MDMContextEntity transformToMDMEntity(Map<String, Object> component) {
 		return new MDMContextEntity(
-				component.get("name").map(Object::toString)
-						.getOrElseThrow(() -> new MDMEntityAccessException("Missing attribute 'name' in MDMEntity")),
-				component.get("id").map(Object::toString).getOrElse(""),
-				component.get("type").map(Object::toString).getOrElse(""),
-				component.get("sourceType").map(Object::toString).getOrElse(""),
-				component.get("sourceName").map(Object::toString).getOrElse(""),
-				transformList(component.get("attributes").getOrElseThrow(
+				getValueOrException(component, "name"),
+				getValue(component, "id"),
+				getValue(component, "type"),
+				getValue(component, "sourceType"),
+				getValue(component, "sourceName"),
+				transformList(convertNullValueToNone(component.get("attributes")).getOrElseThrow(
 						() -> new MDMEntityAccessException("Missing attribute 'attributes' in MDMEntity")))
 								.map(this::transformMap)
 								.map(m -> new MDMContextAttribute(
-										m.get("name").map(Object::toString)
-												.getOrElseThrow(() -> new MDMEntityAccessException(
-														"Missing attribute 'name' in MDMAttribute")),
-										m.get("value").map(Object::toString)
-												.getOrElseThrow(() -> new MDMEntityAccessException(
-														"Missing attribute 'value' in MDMAttribute")),
-										m.get("unit").map(Object::toString).getOrElse(""),
-										m.get("datatype").map(Object::toString).getOrElse(""), null, null, null, null))
+										getValueOrException(m, "name"),
+										getValueOrException(m, "value"),
+										getValue(m, "unit"),
+										getValue(m, "datatype"), null, null, null, null))
 								.toJavaList());
 	}
+	
+	/**
+	 * Check if the option contains null an converts the option to None. Else returns the given option
+	 * @param <T>
+	 * @param option
+	 * @return None if Some(null) is given else option
+	 */
+	private <T> Option<T> convertNullValueToNone(Option<T> option) {
+		if (option.contains(null)) {
+			return Option.none();
+		} else {
+			return option;
+		}
+	}
+
+	private String getValue(Map<String, Object> component, String attributeName) {
+		if (component.get(attributeName).contains(null)) {
+			LOGGER.debug("{} is null.", attributeName);
+			return "";
+		} else {
+			return component.get(attributeName).map(Object::toString).getOrElse("");
+		}
+	}
+	
+	private String getValueOrException(Map<String, Object> component, String attributeName) {
+		if (component.get(attributeName).contains(null)) {
+			LOGGER.debug("{} is null.", attributeName);
+			throw new MDMEntityAccessException("Attribute '" + attributeName + "' in " + component + " is null.");
+		} else {
+			return component.get(attributeName).map(Object::toString).getOrElseThrow(() -> new MDMEntityAccessException(
+					"Missing attribute '" + attributeName + "' in " + component));
+		}
+	}
 }