Merge branch 'dev'

Change-Id: I5017e29b2ceb28ee55629a9dc359fd5ce83fb25d
diff --git a/README.md b/README.md
index 33af6a5..fbb164a 100644
--- a/README.md
+++ b/README.md
@@ -399,6 +399,15 @@
 * GET:    /tpltests/{TEMPLATETESTID}/tplteststepusages/searchattributes
 * GET:    /tpltests/{TEMPLATETESTID}/tplteststepusages/localizations
 
+**Values endpoint**
+
+Values endpoint accepts `application/protobuf` and `application/json`. For larger amounts of data Protobuf should be preferred. The IDL-file for Protobuf is available in `org.eclipse.mdm.businessobjects/src/main/proto/mdm.proto`
+
+* POST: /values/read (Protobuf: in - ReadRequest, out - MeasuredValuesList)
+* POST: /values/write (Protobuf: in - WriteRequestList)
+* POST: /values/preview (Protobuf: in - PreviewRequest, out - PreviewValuesList)
+
+
 **Query endpoint**
 
 * http://{SERVER}:{PORT}/{APPLICATIONROOT}/mdm/query
diff --git a/build.gradle b/build.gradle
index 9a0f78f..f697735 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@
 }
 
 group = 'org.eclipse.mdm'
-version = '5.1.0M6'
+version = '5.1.0M7-SNAPSHOT'
 
 description = 'mdm nucleus'
 apply plugin: 'war'
diff --git a/doc/GettingStarted_mdmbl.pdf b/doc/GettingStarted_mdmbl.pdf
index 255f2af..4a231a6 100644
--- a/doc/GettingStarted_mdmbl.pdf
+++ b/doc/GettingStarted_mdmbl.pdf
Binary files differ
diff --git a/doc/Installation Guide for the openMDM5 Application.pdf b/doc/Installation Guide for the openMDM5 Application.pdf
index 3b34f32..8b4b060 100644
--- a/doc/Installation Guide for the openMDM5 Application.pdf
+++ b/doc/Installation Guide for the openMDM5 Application.pdf
Binary files differ
diff --git a/org.eclipse.mdm.businessobjects/build.gradle b/org.eclipse.mdm.businessobjects/build.gradle
index 305bbe4..dbb45ff 100644
--- a/org.eclipse.mdm.businessobjects/build.gradle
+++ b/org.eclipse.mdm.businessobjects/build.gradle
@@ -12,6 +12,9 @@
  *
  ********************************************************************************/
 
+plugins {
+  id "com.google.protobuf" version "0.8.8"
+}
 
 description = 'business object service'
 
@@ -21,6 +24,9 @@
 	compile project(':org.eclipse.mdm.connector')
 	compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.5.1'
 	compile 'io.vavr:vavr:0.9.1'
+	
+	compile 'com.google.protobuf:protobuf-java:3.2.0'
+	compile 'com.google.protobuf:protobuf-java-util:3.2.0'
 
 	testCompile 'org.assertj:assertj-core:3.6.2'
 	testCompile 'io.rest-assured:rest-assured:3.0.5'
@@ -36,7 +42,7 @@
 generateGrammarSource { arguments += ["-visitor"] }
 
 test {
-    exclude 'org/eclipse/mdm/businessobjects/boundary/integrationtest/*'
+	exclude 'org/eclipse/mdm/businessobjects/boundary/integrationtest/*'
 }
 
 compileJava.dependsOn(generateGrammarSource)
@@ -45,3 +51,18 @@
 	metaInf { from '../NOTICE.txt' }
 	metaInf { from '../LICENSE.txt' }
 }
+
+protobuf {
+	protoc {
+		artifact = 'com.google.protobuf:protoc:3.0.0'
+	}
+}
+
+sourceSets {
+	main {
+		java {
+			srcDirs 'build/generated/source/proto/main/java'
+		}
+	}
+}
+
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
index 932091d..b4a4de1 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelGroupResource.java
@@ -14,11 +14,18 @@
 
 package org.eclipse.mdm.businessobjects.boundary;
 
-import java.util.List;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_ID;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
+import static org.eclipse.mdm.businessobjects.service.EntityService.V;
+
 import java.util.Map;
 
 import javax.ejb.EJB;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -32,12 +39,18 @@
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.model.ChannelGroup;
 import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.dflt.model.ValueList;
 import org.eclipse.mdm.businessobjects.entity.I18NResponse;
 import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
+import org.eclipse.mdm.businessobjects.service.EntityService;
+import org.eclipse.mdm.businessobjects.utils.RequestBody;
 import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.vavr.collection.List;
+
 import io.swagger.v3.oas.annotations.tags.Tag;
 
 /**
@@ -55,9 +68,12 @@
 	@EJB
 	private ChannelGroupService channelGroupService;
 
+	@EJB
+	private EntityService entityService;
+
 	/**
 	 * delegates the request to the {@link ChannelGroupService}
-	 * 
+	 *
 	 * @param sourceName name of the source (MDM {@link Environment} name)
 	 * @param filter     filter string to filter the {@link ChannelGroup} result
 	 * @return the result of the delegated request as {@link Response}
@@ -67,7 +83,7 @@
 	public Response getChannelGroups(@PathParam("SOURCENAME") String sourceName, @QueryParam("filter") String filter) {
 
 		try {
-			List<ChannelGroup> channelGroups = this.channelGroupService.getChannelGroups(sourceName, filter);
+			java.util.List<ChannelGroup> channelGroups = this.channelGroupService.getChannelGroups(sourceName, filter);
 			return ServiceUtils.toResponse(new MDMEntityResponse(ChannelGroup.class, channelGroups), Status.OK);
 
 		} catch (RuntimeException e) {
@@ -120,4 +136,64 @@
 			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
 		}
 	}
+
+	/**
+	 * Returns the created {@link ChannelGroup}.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param body       The {@link ChannelGroup} to create.
+	 * @return the created {@link ChannelGroup} as {@link Response}.
+	 */
+	@POST
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	public Response create(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName, String body) {
+
+		return entityService
+				.create(V(sourceName), ChannelGroup.class,
+						entityService.extractRequestBody(body, sourceName, List.of(Measurement.class)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.CREATED))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the {@link ChannelGroup} with all parameters set in the given JSON
+	 * body of the request.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         the identifier of the {@link ChannelGroup} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the updated {@link ChannelGroup}
+	 */
+	@PUT
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response update(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName, @PathParam(REQUESTPARAM_ID) String id,
+			String body) {
+		RequestBody requestBody = RequestBody.create(body);
+
+		return entityService
+				.update(V(sourceName), entityService.find(V(sourceName), ChannelGroup.class, V(id)),
+						requestBody.getValueMapSupplier())
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Deletes and returns the deleted {@link ChannelGroup}.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         The identifier of the {@link ChannelGroup} to delete.
+	 * @return the deleted {@link ValueList }s as {@link Response}
+	 */
+	@DELETE
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response delete(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.delete(V(sourceName), entityService.find(V(sourceName), ChannelGroup.class, V(id)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
 }
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
index c493e9d..d853bb4 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ChannelResource.java
@@ -14,11 +14,18 @@
 
 package org.eclipse.mdm.businessobjects.boundary;
 
-import java.util.List;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_ID;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.REQUESTPARAM_SOURCENAME;
+import static org.eclipse.mdm.businessobjects.service.EntityService.V;
+
 import java.util.Map;
 
 import javax.ejb.EJB;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -32,13 +39,20 @@
 import org.eclipse.mdm.api.base.adapter.EntityType;
 import org.eclipse.mdm.api.base.model.Channel;
 import org.eclipse.mdm.api.base.model.Environment;
+import org.eclipse.mdm.api.base.model.Measurement;
+import org.eclipse.mdm.api.base.model.Quantity;
 import org.eclipse.mdm.api.base.model.Test;
+import org.eclipse.mdm.api.dflt.model.ValueList;
 import org.eclipse.mdm.businessobjects.entity.I18NResponse;
 import org.eclipse.mdm.businessobjects.entity.MDMEntityResponse;
+import org.eclipse.mdm.businessobjects.service.EntityService;
+import org.eclipse.mdm.businessobjects.utils.RequestBody;
 import org.eclipse.mdm.businessobjects.utils.ServiceUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.vavr.collection.List;
+
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Content;
@@ -61,6 +75,9 @@
 	@EJB
 	private ChannelService channelService;
 
+	@EJB
+	private EntityService entityService;
+
 	/**
 	 * delegates the request to the {@link ChannelService}
 	 * 
@@ -79,7 +96,7 @@
 			@Parameter(description = "Filter expression", required = false) @QueryParam("filter") String filter) {
 
 		try {
-			List<Channel> channels = this.channelService.getChannels(sourceName, filter);
+			java.util.List<Channel> channels = this.channelService.getChannels(sourceName, filter);
 			return ServiceUtils.toResponse(new MDMEntityResponse(Channel.class, channels), Status.OK);
 
 		} catch (RuntimeException e) {
@@ -140,4 +157,64 @@
 			throw new WebApplicationException(e.getMessage(), e, Status.INTERNAL_SERVER_ERROR);
 		}
 	}
+	
+	/**
+	 * Returns the created {@link Channel}.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param body       The {@link Channel} to create.
+	 * @return the created {@link Channel} as {@link Response}.
+	 */
+	@POST
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	public Response create(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName, String body) {
+
+		return entityService
+				.create(V(sourceName), Channel.class,
+						entityService.extractRequestBody(body, sourceName, List.of(Measurement.class, Quantity.class)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.CREATED))
+				.recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER).getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Updates the {@link Channel} with all parameters set in the given JSON body
+	 * of the request.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         the identifier of the {@link Channel} to update.
+	 * @param body       the body of the request containing the attributes to update
+	 * @return the updated {@link Channel}
+	 */
+	@PUT
+	@Produces(MediaType.APPLICATION_JSON)
+	@Consumes(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response update(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName, @PathParam(REQUESTPARAM_ID) String id,
+			String body) {
+		RequestBody requestBody = RequestBody.create(body);
+
+		return entityService
+				.update(V(sourceName), entityService.find(V(sourceName), Channel.class, V(id)),
+						requestBody.getValueMapSupplier())
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
+
+	/**
+	 * Deletes and returns the deleted {@link Channel}.
+	 * 
+	 * @param sourceName name of the source (MDM {@link Environment} name)
+	 * @param id         The identifier of the {@link Channel} to delete.
+	 * @return the deleted {@link ValueList }s as {@link Response}
+	 */
+	@DELETE
+	@Produces(MediaType.APPLICATION_JSON)
+	@Path("/{" + REQUESTPARAM_ID + "}")
+	public Response delete(@PathParam(REQUESTPARAM_SOURCENAME) String sourceName,
+			@PathParam(REQUESTPARAM_ID) String id) {
+		return entityService.delete(V(sourceName), entityService.find(V(sourceName), Channel.class, V(id)))
+				.map(e -> ServiceUtils.buildEntityResponse(e, Status.OK)).recover(ServiceUtils.ERROR_RESPONSE_SUPPLIER)
+				.getOrElse(ServiceUtils.SERVER_ERROR_RESPONSE);
+	}
 }
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
index 41d62a1..cdb2fae 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ResourceConstants.java
@@ -14,6 +14,7 @@
 
 package org.eclipse.mdm.businessobjects.boundary;
 
+import org.eclipse.mdm.api.base.model.ChannelGroup;
 import org.eclipse.mdm.api.base.model.ContextType;
 import org.eclipse.mdm.api.base.model.Entity;
 import org.eclipse.mdm.api.base.model.Environment;
@@ -36,6 +37,8 @@
  *
  */
 public final class ResourceConstants {
+	public static final String MEDIATYPE_APPLICATION_PROTOBUF = "application/protobuf";
+
 	/**
 	 * Parameter name holding the {@link Environment}, i.e. the source name
 	 */
@@ -82,6 +85,12 @@
 	public static final String ENTITYATTRIBUTE_NAME = "Name";
 
 	/**
+	 * Parameter holding the number of values of the {@link ChannelGroup} in the
+	 * request body
+	 */
+	public static final String ENTITYATTRIBUTE_NUMBER_OF_VALUES = "NumberOfValues";
+
+	/**
 	 * Parameter holding the {@link ValueType} of the {@link Entity} in the request
 	 * body
 	 */
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesResource.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesResource.java
new file mode 100644
index 0000000..2348b03
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesResource.java
@@ -0,0 +1,84 @@
+/********************************************************************************

+ * 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.businessobjects.boundary;

+

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

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

+

+import javax.ejb.EJB;

+import javax.ws.rs.Consumes;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

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

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

+

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

+

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.Parameter;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

+import io.swagger.v3.oas.annotations.tags.Tag;

+

+@Tag(name = "Values")

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

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

+@Path("/environments/{" + REQUESTPARAM_SOURCENAME + "}/values")

+public class ValuesResource {

+

+	@EJB

+	private ValuesService valuesService;

+

+	@POST

+	@Path("read")

+	@Operation(summary = "Read measurement data", description = "Read measurement specified by a ReadRequest.", responses = {

+			@ApiResponse(description = "A list with MeasuredValues. Each MeasuredValues represents the values of a Channel.", content = @Content(schema = @Schema(implementation = Mdm.MeasuredValuesList.class))),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response find(

+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,

+			@Parameter(description = "ReadRequest specifying the Channels and the portion of the Channel's data to read.", required = true, content = @Content(schema = @Schema(implementation = Mdm.ReadRequest.class))) Mdm.ReadRequest protoReadRequest) {

+

+		return Response.ok(valuesService.load(sourceName, protoReadRequest)).build();

+	}

+

+	@POST

+	@Path("write")

+	@Operation(summary = "Write measurement data", description = "Write measurement data for the specified ChannelGroup and Channels. Both ChannelGroup and Channels must exist.", responses = {

+			@ApiResponse(description = "An empty response"),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response find(

+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,

+			Mdm.WriteRequestList protoWriteRequestList) {

+

+		valuesService.write(sourceName, protoWriteRequestList);

+

+		return Response.noContent().build();

+	}

+

+	@POST

+	@Path("preview")

+	@Operation(summary = "Read preview data", description = "Read preview data for the specified ReadRequest. The number of chunks defines the number of returned values per channel. The original measured values are chunked and for each chunk the average, minimum and maximum values are calculated.", responses = {

+			@ApiResponse(description = "Average, minimum and maximum values for each chunk are returned as MeasuredValues. Each MeasuredValues represents the preview of a Channel where the lenght of MeasuredValues equals numberOfChunks.", content = @Content(schema = @Schema(implementation = Mdm.PreviewValuesList.class))),

+			@ApiResponse(responseCode = "400", description = "Error") })

+	public Response preview(

+			@Parameter(description = "Name of the MDM datasource", required = true) @PathParam(REQUESTPARAM_SOURCENAME) String sourceName,

+			@Parameter(description = "PreviewRequest specifying the Channels and number of chunks to read.", required = true, content = @Content(schema = @Schema(implementation = Mdm.PreviewRequest.class))) Mdm.PreviewRequest previewRequest) {

+

+		return Response.ok(valuesService.preview(sourceName, previewRequest)).build();

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesService.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesService.java
new file mode 100644
index 0000000..bf1109d
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/boundary/ValuesService.java
@@ -0,0 +1,328 @@
+/********************************************************************************

+ * 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.businessobjects.boundary;

+

+import java.time.LocalDateTime;

+import java.time.ZoneId;

+import java.util.ArrayList;

+import java.util.List;

+

+import javax.ejb.Stateless;

+import javax.inject.Inject;

+

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ExplicitData;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitConstant;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitLinear;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitSaw;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawLinear;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawLinearCalibrated;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawPolynomial;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

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

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

+import com.google.common.primitives.Booleans;

+import com.google.common.primitives.Doubles;

+import com.google.common.primitives.Floats;

+import com.google.common.primitives.Ints;

+import com.google.common.primitives.Longs;

+import com.google.common.primitives.Shorts;

+

+@Stateless

+public class ValuesService {

+

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

+

+	@Inject

+	private ConnectorService connectorService;

+

+	/**

+	 * Loads multiple MeasuredValues as specified in ReadRequest.

+	 * 

+	 * @param sourceName       name of the source (MDM {@link Environment} name)

+	 * @param protoReadRequest Protobuf ReadRequest

+	 * @return the loaded MeasuredValuesList

+	 */

+	public MeasuredValuesList load(String sourceName, Mdm.ReadRequest protoReadRequest) {

+

+		ApplicationContext context = connectorService.getContextByName(sourceName);

+

+		ReadRequest readRequest = ProtobufConverter.convert(context, protoReadRequest);

+

+		List<MeasuredValues> measuredValues = context.getEntityManager().get().readMeasuredValues(readRequest);

+

+		return ProtobufConverter.convert(measuredValues);

+	}

+

+	/**

+	 * Loads preview values for multiple channels as specified in

+	 * {@link PreviewRequest}. The preview consists of minimum (min), maximum (max)

+	 * and average (avg) values for each chunk of values. The number of chunks must

+	 * be given in {@link PreviewRequest}.

+	 * 

+	 * @param sourceName       name of the source (MDM {@link Environment} name)

+	 * @param protoReadRequest Protobuf {@link PreviewRequest}

+	 * @return the loaded PreviewValuesList

+	 */

+	public PreviewValuesList preview(String sourceName, Mdm.PreviewRequest previewRequest) {

+		ApplicationContext context = connectorService.getContextByName(sourceName);

+

+		ReadRequest readRequest = ProtobufConverter.convert(context, previewRequest.getReadRequest());

+

+		List<Mdm.MeasuredValues> measuredValues = ProtobufConverter

+				.convert(context.getEntityManager().get().readMeasuredValues(readRequest)).getValuesList();

+

+		// Calculate Preview

+		return new PreviewHelper().calculatePreview(measuredValues, previewRequest.getNumberOfChunks());

+	}

+

+	/**

+	 * Writes values for multiple Channels.

+	 * 

+	 * @param sourceName            name of the source (MDM {@link Environment}

+	 *                              name)

+	 * @param protoWriteRequestList Protobuf {@link WriteRequest}

+	 */

+	public void write(String sourceName, Mdm.WriteRequestList protoWriteRequestList) {

+		ApplicationContext context = connectorService.getContextByName(sourceName);

+		Supplier<ZoneId> zoneId = Suppliers

+				.memoize(() -> getServerZoneId(context.getEntityManager().get().loadEnvironment()));

+

+		List<WriteRequest> writeRequests = new ArrayList<>();

+		for (Mdm.WriteRequestList.WriteRequest r : protoWriteRequestList.getValuesList()) {

+			writeRequests.add(convert(context, r, zoneId));

+		}

+

+		Transaction t = context.getEntityManager().get().startTransaction();

+		t.writeMeasuredValues(writeRequests);

+		t.commit();

+	}

+

+	/**

+	 * Converts a Protobuf to a api.base WritRequest.

+	 * 

+	 * @param context           {@link ApplicationContext} to write to.

+	 * @param protoWriteRequest Data to write.

+	 * @param zoneId            A supplier for a {@link ZoneId} which will be used,

+	 *                          if Data with DT_DATE is written.

+	 * @return converted {@link WriteRequest}

+	 */

+	private WriteRequest convert(ApplicationContext context, Mdm.WriteRequestList.WriteRequest protoWriteRequest,

+			Supplier<ZoneId> zoneId) {

+		EntityManager em = context.getEntityManager().get();

+

+		ChannelGroup channelGroup = em.load(ChannelGroup.class, protoWriteRequest.getChannelGroupId());

+		Channel channel = em.load(Channel.class, protoWriteRequest.getChannelId());

+		AxisType axisType = ProtobufConverter.convert(protoWriteRequest.getAxisType());

+

+		WriteRequestBuilder rb = WriteRequest.create(channelGroup, channel, axisType);

+

+		WriteRequestFinalizer wrf;

+		switch (protoWriteRequest.getDataCase()) {

+		case EXPLICIT:

+			wrf = setValues(rb.explicit(), protoWriteRequest.getExplicit(), zoneId);

+			break;

+		case IMPLICIT_CONSTANT:

+			ImplicitConstant ic = protoWriteRequest.getImplicitConstant();

+			wrf = rb.implicitConstant(ProtobufConverter.convert(ic.getScalarType()), ic.getOffset());

+			break;

+		case IMPLICIT_LINEAR:

+			ImplicitLinear il = protoWriteRequest.getImplicitLinear();

+			wrf = rb.implicitLinear(ProtobufConverter.convert(il.getScalarType()), il.getStart(), il.getIncrement());

+			break;

+		case IMPLICIT_SAW:

+			ImplicitSaw is = protoWriteRequest.getImplicitSaw();

+			wrf = rb.implicitSaw(ProtobufConverter.convert(is.getScalarType()), is.getStart(), is.getIncrement(),

+					is.getStop());

+			break;

+		case RAW_LINEAR:

+			RawLinear rl = protoWriteRequest.getRawLinear();

+			wrf = setValues(rb.rawLinear(rl.getOffset(), rl.getFactor()), rl.getValues(), zoneId);

+			break;

+		case RAW_LINEAR_CALIBRATED:

+			RawLinearCalibrated rlc = protoWriteRequest.getRawLinearCalibrated();

+			wrf = setValues(rb.rawLinearCalibrated(rlc.getOffset(), rlc.getFactor(), rlc.getCalibration()),

+					rlc.getValues(), zoneId);

+			break;

+		case RAW_POLYNOMIAL:

+			RawPolynomial rp = protoWriteRequest.getRawPolynomial();

+			wrf = setValues(rb.rawPolynomial(Doubles.toArray(rp.getCoefficientsList())), rp.getValues(), zoneId);

+			break;

+		case DATA_NOT_SET:

+		default:

+			throw new RuntimeException("Not supported yet: " + protoWriteRequest.getDataCase());

+

+		}

+

+		// TODO mkoller 04.11.2019, Unit conversion is not yet supported.

+

+//		if (wrf instanceof UnitBuilder) {

+//			// Unit convertion not supported yet!

+//			Unit unit = context.getEntityManager().get().load(Unit.class, "" + protoWriteRequest.getUnitId());

+//			wrf = ((UnitBuilder) wrf).sourceUnit(unit);

+//		}

+//		if (wrf instanceof UnitIndependentBuilder) {

+//			// Unit convertion not supported yet!

+//			Unit unit = context.getEntityManager().get().load(Unit.class, "" + protoWriteRequest.getUnitId());

+//			wrf = ((UnitBuilder) wrf).sourceUnit(unit);

+//		}

+

+		if (wrf instanceof IndependentBuilder) {

+			wrf = ((IndependentBuilder) wrf).independent(protoWriteRequest.getIndependent());

+		}

+		return wrf.build();

+

+	}

+

+	/**

+	 * Helper function to set values for the appropriate datatype in an

+	 * {@link AnyTypeValuesBuilder}

+	 * 

+	 * @param builder

+	 * @param explicitData

+	 * @param zoneId

+	 * @return {@link WriteRequestFinalizer}

+	 */

+	private WriteRequestFinalizer setValues(AnyTypeValuesBuilder builder, ExplicitData explicitData,

+			Supplier<ZoneId> zoneId) {

+		boolean[] flags = Booleans.toArray(explicitData.getFlagsList());

+		switch (explicitData.getValuesCase()) {

+		case STRING_ARRAY:

+			String[] strings = ProtobufConverter.convertStrings(explicitData.getStringArray());

+			return (flags.length > 0) ? builder.stringValues(strings, flags) : builder.stringValues(strings);

+		case DATE_ARRAY:

+			LocalDateTime[] dates = ProtobufConverter.convertDates(explicitData.getDateArray(), zoneId.get());

+			return (flags.length > 0) ? builder.dateValues(dates, flags) : builder.dateValues(dates);

+		case BOOLEAN_ARRAY:

+			boolean[] booleans = Booleans.toArray(explicitData.getBooleanArray().getValuesList());

+			return (flags.length > 0) ? builder.booleanValues(booleans, flags) : builder.booleanValues(booleans);

+		case BYTE_STREAM_ARRAY:

+			byte[][] byteStreams = ProtobufConverter

+					.convertByteStreams(explicitData.getByteStreamArray().getValuesList());

+			return (flags.length > 0) ? builder.byteStreamValues(byteStreams, flags)

+					: builder.byteStreamValues(byteStreams);

+		case BYTE_ARRAY:

+		case SHORT_ARRAY:

+		case INTEGER_ARRAY:

+		case LONG_ARRAY:

+		case FLOAT_ARRAY:

+		case DOUBLE_ARRAY:

+		case FLOAT_COMPLEX_ARRAY:

+		case DOUBLE_COMPLEX_ARRAY:

+			return setValues((ComplexNumericalValuesBuilder) builder, explicitData, zoneId);

+		case VALUES_NOT_SET:

+		default:

+			throw new RuntimeException("No explicit data set!");

+		}

+	}

+

+	/**

+	 * Helper function to set values for the appropriate datatype in a

+	 * {@link ComplexNumericalValuesBuilder}.

+	 * 

+	 * @param builder

+	 * @param explicitData

+	 * @param zoneId

+	 * @return {@link WriteRequestFinalizer}

+	 */

+	private WriteRequestFinalizer setValues(ComplexNumericalValuesBuilder builder, ExplicitData explicitData,

+			Supplier<ZoneId> zoneId) {

+		boolean[] flags = Booleans.toArray(explicitData.getFlagsList());

+		switch (explicitData.getValuesCase()) {

+		case STRING_ARRAY:

+		case DATE_ARRAY:

+		case BOOLEAN_ARRAY:

+		case BYTE_STREAM_ARRAY:

+			throw new RuntimeException(

+					explicitData.getValuesCase() + " not supported by ComplexNumericalValuesBuilder!");

+		case BYTE_ARRAY:

+			byte[] bytes = explicitData.getByteArray().getValues().toByteArray();

+			return (flags.length > 0) ? builder.byteValues(bytes, flags) : builder.byteValues(bytes);

+		case SHORT_ARRAY:

+			short[] shorts = Shorts.toArray(explicitData.getShortArray().getValuesList());

+			return (flags.length > 0) ? builder.shortValues(shorts, flags) : builder.shortValues(shorts);

+		case INTEGER_ARRAY:

+			int[] ints = Ints.toArray(explicitData.getIntegerArray().getValuesList());

+			return (flags.length > 0) ? builder.integerValues(ints, flags) : builder.integerValues(ints);

+		case LONG_ARRAY:

+			long[] longs = Longs.toArray(explicitData.getLongArray().getValuesList());

+			return (flags.length > 0) ? builder.longValues(longs, flags) : builder.longValues(longs);

+		case FLOAT_ARRAY:

+			float[] floats = Floats.toArray(explicitData.getFloatArray().getValuesList());

+			return (flags.length > 0) ? builder.floatValues(floats, flags) : builder.floatValues(floats);

+		case DOUBLE_ARRAY:

+			double[] doubles = Doubles.toArray(explicitData.getDoubleArray().getValuesList());

+			return (flags.length > 0) ? builder.doubleValues(doubles, flags) : builder.doubleValues(doubles);

+		case FLOAT_COMPLEX_ARRAY:

+			FloatComplex[] floatComplexes = ProtobufConverter

+					.convertFloatComplex(explicitData.getFloatComplexArray().getValuesList());

+			return (flags.length > 0) ? builder.floatComplexValues(floatComplexes, flags)

+					: builder.floatComplexValues(floatComplexes);

+		case DOUBLE_COMPLEX_ARRAY:

+			DoubleComplex[] doubleComplexes = ProtobufConverter

+					.convertDoubleComplex(explicitData.getDoubleComplexArray().getValuesList());

+			return (flags.length > 0) ? builder.doubleComplexValues(doubleComplexes, flags)

+					: builder.doubleComplexValues(doubleComplexes);

+		case VALUES_NOT_SET:

+		default:

+			throw new RuntimeException("No explicit data set!");

+		}

+	}

+

+	/**

+	 * Returns the ZonId configured in the {@link Environment}

+	 * 

+	 * @param env {@link Environment} for extracting the ZoneId

+	 * @return ZonId configured in the {@link Environment}

+	 */

+	private ZoneId getServerZoneId(Environment env) {

+		String timezone = env.getTimezone();

+		try {

+			return ZoneId.of(timezone);

+		} catch (Exception e) {

+			if (LOG.isDebugEnabled()) {

+				LOG.debug("Timezone '" + timezone + "' of Environment '" + env.getName() + "' is invalid!", e);

+			} else {

+				LOG.warn("Timezone '" + timezone + "' of Environment '" + env.getName() + "' is invalid!");

+			}

+			return ZoneId.systemDefault();

+		}

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/EntityService.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/EntityService.java
index 313e8e1..f1ca87e 100644
--- a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/EntityService.java
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/EntityService.java
@@ -19,6 +19,7 @@
 import static io.vavr.API.Tuple;
 import static io.vavr.Predicates.instanceOf;
 import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.ENTITYATTRIBUTE_NAME;
+import static org.eclipse.mdm.businessobjects.boundary.ResourceConstants.ENTITYATTRIBUTE_NUMBER_OF_VALUES;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -60,6 +61,7 @@
 import org.eclipse.mdm.businessobjects.control.SearchActivity;
 import org.eclipse.mdm.businessobjects.entity.SearchAttribute;
 import org.eclipse.mdm.businessobjects.utils.EntityNotFoundException;
+import org.eclipse.mdm.businessobjects.utils.ReflectUtil;
 import org.eclipse.mdm.businessobjects.utils.RequestBody;
 import org.eclipse.mdm.businessobjects.utils.Serializer;
 import org.eclipse.mdm.connector.boundary.ConnectorService;
@@ -454,7 +456,10 @@
 	 *         required parameters
 	 */
 	boolean satisfiesParameters(Class<?>[] parameterTypes, Seq<Class<?>> requiredParameters) {
-		return List.of(parameterTypes).zip(requiredParameters).filter(t -> !t._1().isAssignableFrom(t._2())).isEmpty();
+		boolean result = parameterTypes.length == requiredParameters.length();
+		result &= List.of(parameterTypes).zip(requiredParameters).filter(t -> !ReflectUtil.isAssignable(t._2(), t._1()))
+				.isEmpty();
+		return result;
 	}
 
 	/**
@@ -727,6 +732,15 @@
 			List<Class<? extends Entity>> entityClasses) {
 		RequestBody requestBody = RequestBody.create(body);
 		List<Try<?>> name = List.of(requestBody.getStringValueSupplier(ENTITYATTRIBUTE_NAME));
+
+		// Mandatory in ChannelGroup.
+		Try<?> numberOfValues = requestBody.getStringValueSupplier(ENTITYATTRIBUTE_NUMBER_OF_VALUES)
+				.map(val -> Integer.parseInt(val));
+
+		if (numberOfValues.isSuccess()) {
+			name = name.append(numberOfValues);
+		}
+
 		return entityClasses
 				.map(clazz -> find(V(sourceName), clazz, requestBody.getStringValueSupplier(clazz.getSimpleName())))
 				.foldLeft(name, (l, e) -> l.append(e)).filter(Try::isSuccess).map(Try::toOption);
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/JsonMessageBodyProvider.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/JsonMessageBodyProvider.java
new file mode 100644
index 0000000..f90b2aa
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/JsonMessageBodyProvider.java
@@ -0,0 +1,130 @@
+/********************************************************************************
+ * 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.businessobjects.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import com.google.common.base.Charsets;
+import com.google.protobuf.GeneratedMessageV3;
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+import com.google.protobuf.util.JsonFormat.Parser;
+import com.google.protobuf.util.JsonFormat.Printer;
+
+/**
+ * MessageBodyProvider for handling json payloads.
+ * 
+ */
+@Provider
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class JsonMessageBodyProvider implements MessageBodyReader<Message>, MessageBodyWriter<Message> {
+
+	private static final Charset charset = Charsets.UTF_8;
+	private Printer jsonPrinter = JsonFormat.printer();
+	private Parser jsonParser = JsonFormat.parser();
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class,
+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],
+	 * javax.ws.rs.core.MediaType)
+	 */
+	@Override
+	public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+			final MediaType mediaType) {
+		return Message.class.isAssignableFrom(type);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class,
+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],
+	 * javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap,
+	 * java.io.InputStream)
+	 */
+	@Override
+	public Message readFrom(final Class<Message> type, final Type genericType, final Annotation[] annotations,
+			final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
+			throws IOException {
+		try {
+			final Method newBuilder = type.getMethod("newBuilder");
+			final GeneratedMessageV3.Builder<?> builder = (GeneratedMessageV3.Builder<?>) newBuilder.invoke(type);
+			jsonParser.merge(new InputStreamReader(entityStream, charset), builder);
+			return builder.build();
+		} catch (Exception e) {
+			throw new WebApplicationException(e);
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object,
+	 * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[],
+	 * javax.ws.rs.core.MediaType)
+	 */
+	@Override
+	public long getSize(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations,
+			final MediaType mediaType) {
+		return -1;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class,
+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],
+	 * javax.ws.rs.core.MediaType)
+	 */
+	@Override
+	public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,
+			final MediaType mediaType) {
+		return Message.class.isAssignableFrom(type);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object,
+	 * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[],
+	 * javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap,
+	 * java.io.OutputStream)
+	 */
+	@Override
+	public void writeTo(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations,
+			final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,
+			final OutputStream entityStream) throws IOException {
+		entityStream.write(jsonPrinter.print(m).getBytes(charset));
+	}
+}
diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewException.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewException.java
new file mode 100644
index 0000000..ab8316c
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewException.java
@@ -0,0 +1,33 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

+/**

+ * Thrown to indicate errors while calculating preview values.

+ *

+ */

+public class PreviewException extends RuntimeException {

+

+	private static final long serialVersionUID = -6305539444033722516L;

+

+	/**

+	 * Constructor.

+	 *

+	 * @param message The error message.

+	 */

+	public PreviewException(String message) {

+		super(message);

+	}

+

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewHelper.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewHelper.java
new file mode 100644
index 0000000..a7fd79a
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/PreviewHelper.java
@@ -0,0 +1,271 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

+import java.math.BigDecimal;

+import java.math.RoundingMode;

+import java.util.List;

+

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

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

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

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

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

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

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

+

+import com.google.common.primitives.Bytes;

+import com.google.common.primitives.Ints;

+import com.google.protobuf.Timestamp;

+

+/**

+ * Helper class to calculate preview values.

+ *

+ */

+public class PreviewHelper {

+	/**

+	 * Calcluates the preview values for numerical datatypes. For non numberical

+	 * datatypes an empty MeasuredValues object is returned.

+	 * 

+	 * @param measuredValues List of measuredValues containing the mass data for the

+	 *                       preview.

+	 * @param numberOfChunks number of requested chunks for the preview

+	 * @return calculated preview values

+	 */

+	public PreviewValuesList calculatePreview(List<MeasuredValues> measuredValues, int numberOfChunks) {

+		return measuredValues.stream().map(mv -> calculatePreview(mv, numberOfChunks))

+				.reduce(Mdm.PreviewValuesList.newBuilder(), (pl1, pl2) -> {

+					pl1.addAllMin(pl2.getMinList());

+					pl1.addAllMax(pl2.getMaxList());

+					pl1.addAllAvg(pl2.getAvgList());

+					return pl1;

+				}).build();

+	}

+

+	/**

+	 * Calculate preview for one {@link MeasuredValues}

+	 * 

+	 * @param measuredValues measured values

+	 * @param numberOfChunks number of chunks

+	 * @return a builder with the preview values set

+	 */

+	protected Mdm.PreviewValuesList.Builder calculatePreview(Mdm.MeasuredValues measuredValues, int numberOfChunks) {

+		Mdm.MeasuredValues.Builder mv = Mdm.MeasuredValues.newBuilder(measuredValues).setScalarType(ScalarType.DOUBLE);

+

+		switch (mv.getValuesCase()) {

+		case BOOLEAN_ARRAY:

+			return createEmpty(mv.clearBooleanArray());

+		case BYTE_ARRAY:

+			byte[] bytes = measuredValues.getByteArray().getValues().toByteArray();

+			return calculate(mv, numberOfChunks, Bytes.asList(bytes), measuredValues.getFlagsList());

+		case BYTE_STREAM_ARRAY:

+			return createEmpty(mv.clearByteStreamArray());

+		case DATE_ARRAY:

+			List<Timestamp> timestamps = measuredValues.getDateArray().getValuesList();

+			return calculateDate(mv, numberOfChunks, timestamps, measuredValues.getFlagsList());

+		case DOUBLE_ARRAY:

+			return calculate(mv, numberOfChunks, measuredValues.getDoubleArray().getValuesList(),

+					measuredValues.getFlagsList());

+		case DOUBLE_COMPLEX_ARRAY:

+			return createEmpty(mv.clearDoubleComplexArray());

+		case FLOAT_ARRAY:

+			return calculate(mv, numberOfChunks, measuredValues.getFloatArray().getValuesList(),

+					measuredValues.getFlagsList());

+		case FLOAT_COMPLEX_ARRAY:

+			return createEmpty(mv.clearFloatComplexArray());

+		case INTEGER_ARRAY:

+			return calculate(mv, numberOfChunks, measuredValues.getIntegerArray().getValuesList(),

+					measuredValues.getFlagsList());

+		case LONG_ARRAY:

+			return calculate(mv, numberOfChunks, measuredValues.getLongArray().getValuesList(),

+					measuredValues.getFlagsList());

+		case SHORT_ARRAY:

+			return calculate(mv, numberOfChunks, measuredValues.getShortArray().getValuesList(),

+					measuredValues.getFlagsList());

+		case STRING_ARRAY:

+			return createEmpty(mv.clearStringArray());

+		case VALUES_NOT_SET:

+			throw new PreviewException(

+					"Property 'values' of MeasuredValues with name '" + measuredValues.getName() + "' not set.");

+		default:

+			throw new PreviewException(

+					"The value '" + mv.getValuesCase().name() + "' for Property 'values' of MeasuredValues with name '"

+							+ measuredValues.getName() + "' is not supported.");

+		}

+	}

+

+	/**

+	 * Returns an empty builder.

+	 * 

+	 * @param mv

+	 * @return a builder with no values or flags set

+	 */

+	private Mdm.PreviewValuesList.Builder createEmpty(Builder mv) {

+		return Mdm.PreviewValuesList.newBuilder().addMin(mv.clearFlags().clearLength())

+				.addMax(mv.clearFlags().clearLength()).addAvg(mv.clearFlags().clearLength());

+	}

+

+	/**

+	 * Calculates min, max and avg for the given values for all data types except

+	 * DT_DATE (@see this{@link #calculateDate(Builder, int, List, List)})

+	 * 

+	 * @param builder                 a builder to apply the values to.

+	 * @param requestedNumberOfChunks number of chunks to calculate

+	 * @param values                  measurement values

+	 * @param flags                   list with flags

+	 * @return an builder with the calculated values.

+	 */

+	private Mdm.PreviewValuesList.Builder calculate(Mdm.MeasuredValues.Builder builder, int requestedNumberOfChunks,

+			List<? extends Number> values, List<Boolean> flags) {

+

+		if (requestedNumberOfChunks < 1) {

+			throw new PreviewException("Number of chunks requested must be positive!");

+		}

+

+		// numberOfChunks cannot be larger than length of data

+		int numberOfChunks = Math.min(requestedNumberOfChunks, values.size());

+

+		double chunkSize = values.size() / (double) numberOfChunks;

+

+		DoubleArray.Builder minValues = DoubleArray.newBuilder();

+		DoubleArray.Builder avgValues = DoubleArray.newBuilder();

+		DoubleArray.Builder maxValues = DoubleArray.newBuilder();

+

+		for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++) {

+			double min = Double.MAX_VALUE;

+			double sum = 0;

+			double max = Double.MIN_VALUE;

+			int count = 0;

+

+			int startIndex = Ints.checkedCast(Math.round(chunkIndex * chunkSize));

+			int endIndex = Ints.checkedCast(Math.round((chunkIndex + 1) * chunkSize));

+

+			for (int valueIndex = startIndex; valueIndex < endIndex; valueIndex++) {

+				if (valueIndex >= flags.size() || flags.get(valueIndex)) {

+					double value = values.get(valueIndex).doubleValue();

+					sum += value;

+					min = Math.min(min, value);

+					max = Math.max(max, value);

+					count++;

+				}

+			}

+

+			minValues.addValues(min);

+			avgValues.addValues(sum / count);

+			maxValues.addValues(max);

+		}

+

+		builder = builder.setLength(numberOfChunks);

+		return PreviewValuesList.newBuilder().addMin(builder.setDoubleArray(minValues))

+				.addAvg(builder.setDoubleArray(avgValues)).addMax(builder.setDoubleArray(maxValues));

+	}

+

+	/**

+	 * Calculates min, max and avg for the given values for all data type DT_DATE.

+	 * 

+	 * @param builder                 a builder to apply the values to.

+	 * @param requestedNumberOfChunks number of chunks to calculate

+	 * @param values                  measurement values

+	 * @param flags                   list with flags

+	 * @return an builder with the calculated values.

+	 */

+	private Mdm.PreviewValuesList.Builder calculateDate(Mdm.MeasuredValues.Builder builder, int numberOfChunks,

+			List<Timestamp> timestamps, List<Boolean> flags) {

+

+		if (numberOfChunks < 1) {

+			throw new PreviewException("Number of chunks requested must be positive!");

+		}

+

+		// numberOfChunks cannot be larger than length of data

+		numberOfChunks = Math.min(numberOfChunks, timestamps.size());

+

+		double chunkSize = timestamps.size() / (double) numberOfChunks;

+

+		DateArray.Builder minValues = DateArray.newBuilder();

+		DateArray.Builder avgValues = DateArray.newBuilder();

+		DateArray.Builder maxValues = DateArray.newBuilder();

+

+		for (int chunkIndex = 0; chunkIndex < numberOfChunks; chunkIndex++) {

+

+			double sumSeconds = 0.0;

+			double sumNanos = 0.0;

+

+			Timestamp max = Timestamp.newBuilder().setSeconds(0L).setNanos(0).build();

+			Timestamp min = Timestamp.newBuilder().setSeconds(Long.MAX_VALUE).setNanos(Integer.MAX_VALUE).build();

+			int count = 0;

+

+			int startIndex = Ints.checkedCast(Math.round(chunkIndex * chunkSize));

+			int endIndex = Ints.checkedCast(Math.round((chunkIndex + 1) * chunkSize));

+

+			for (int valueIndex = startIndex; valueIndex < endIndex; valueIndex++) {

+				if (valueIndex >= flags.size() || flags.get(valueIndex)) {

+					Timestamp value = timestamps.get(valueIndex);

+					sumSeconds += value.getSeconds();

+					sumNanos += value.getNanos();

+					min = min(min, value);

+					max = max(max, value);

+					count++;

+				}

+			}

+			minValues.addValues(min);

+			maxValues.addValues(max);

+

+			BigDecimal c = BigDecimal.valueOf(count);

+			// calculate average and remainder

+			BigDecimal[] avg = BigDecimal.valueOf(sumSeconds).add(BigDecimal.valueOf(sumNanos * 1E-9))

+					.divideAndRemainder(c);

+

+			// the remainder will be converted to nanoseconds

+			avgValues.addValues(Timestamp.newBuilder().setSeconds(avg[0].longValue())

+					.setNanos(avg[1].multiply(BigDecimal.valueOf(1E9)).divide(c, RoundingMode.HALF_UP).intValue()));

+		}

+

+		return PreviewValuesList.newBuilder().addMin(builder.setDateArray(minValues))

+				.addAvg(builder.setDateArray(avgValues)).addMax(builder.setDateArray(maxValues));

+	}

+

+	/**

+	 * Returns the smaller of the given timestamps.

+	 * 

+	 * @param t1 first timestamp

+	 * @param t2 second timestamp

+	 * @return t1, if t1 before t2 else t2

+	 */

+	private Timestamp min(Timestamp t1, Timestamp t2) {

+		if (t1.getSeconds() < t2.getSeconds()) {

+			return t1;

+		} else if (t1.getSeconds() > t2.getSeconds()) {

+			return t2;

+		} else {

+			return t1.getNanos() <= t2.getNanos() ? t1 : t2;

+		}

+	}

+

+	/**

+	 * Returns the larger of the given timestamps.

+	 * 

+	 * @param t1 first timestamp

+	 * @param t2 second timestamp

+	 * @return t1, if t1 after t2 else t2

+	 */

+	private Timestamp max(Timestamp t1, Timestamp t2) {

+		if (t1.getSeconds() > t2.getSeconds()) {

+			return t1;

+		} else if (t1.getSeconds() < t2.getSeconds()) {

+			return t2;

+		} else {

+			return t1.getNanos() >= t2.getNanos() ? t1 : t2;

+		}

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverter.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverter.java
new file mode 100644
index 0000000..29d7717
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverter.java
@@ -0,0 +1,442 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

+import java.io.ByteArrayOutputStream;

+import java.time.Instant;

+import java.time.LocalDateTime;

+import java.time.ZoneId;

+import java.time.ZoneOffset;

+import java.util.List;

+import java.util.Map;

+import java.util.Optional;

+import java.util.stream.Collectors;

+

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.api.base.model.MeasuredValues.ValueIterator;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+

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

+import com.google.common.primitives.Doubles;

+import com.google.protobuf.ByteString;

+import com.google.protobuf.Timestamp;

+

+/**

+ * Helper class for converting between protobuf and mdm types.

+ *

+ */

+public class ProtobufConverter {

+

+	/**

+	 * Converted a DateArray to an array of {@link LocalDateTime}

+	 * 

+	 * @param dateArray

+	 * @param zoneId

+	 * @return array of {@link LocalDateTime}

+	 */

+	public static LocalDateTime[] convertDates(DateArray dateArray, ZoneId zoneId) {

+		LocalDateTime[] strings = new LocalDateTime[dateArray.getValuesCount()];

+		for (int i = 0; i < dateArray.getValuesCount(); i++) {

+			Timestamp ts = dateArray.getValues(i);

+			strings[i] = Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()).atZone(zoneId).toLocalDateTime();

+		}

+		return strings;

+	}

+

+	/**

+	 * Converts a {@link StringArray} to an array of strings

+	 * 

+	 * @param stringArray

+	 * @return array of strings

+	 */

+	public static String[] convertStrings(StringArray stringArray) {

+		String[] strings = new String[stringArray.getValuesCount()];

+		for (int i = 0; i < stringArray.getValuesCount(); i++) {

+			strings[i] = stringArray.getValues(i);

+		}

+		return strings;

+	}

+

+	/**

+	 * Converts a list of {@link org.eclipse.mdm.protobuf.Mdm.FloatComplex} to an

+	 * array of {@link FloatComplex}

+	 * 

+	 * @param valuesList

+	 * @return array of {@link FloatComplex}

+	 */

+	public static FloatComplex[] convertFloatComplex(List<org.eclipse.mdm.protobuf.Mdm.FloatComplex> valuesList) {

+		FloatComplex[] floatComplexes = new FloatComplex[valuesList.size()];

+		for (int i = 0; i < valuesList.size(); i++) {

+			floatComplexes[i] = new FloatComplex(valuesList.get(i).getRe(), valuesList.get(i).getIm());

+		}

+		return floatComplexes;

+	}

+

+	/**

+	 * Converts a list of {@link org.eclipse.mdm.protobuf.Mdm.DoubleComplex} to an

+	 * array of {@link DoubleComplex}

+	 * 

+	 * @param valuesList

+	 * @return array of {@link DoubleComplex}

+	 */

+	public static DoubleComplex[] convertDoubleComplex(List<org.eclipse.mdm.protobuf.Mdm.DoubleComplex> valuesList) {

+		DoubleComplex[] doubleComplexes = new DoubleComplex[valuesList.size()];

+		for (int i = 0; i < valuesList.size(); i++) {

+			doubleComplexes[i] = new DoubleComplex(valuesList.get(i).getRe(), valuesList.get(i).getIm());

+		}

+		return doubleComplexes;

+	}

+

+	/**

+	 * Converts a list of {@link ByteString} to an array of array of byte.

+	 * 

+	 * @param valuesList

+	 * @return array of array of byte.

+	 */

+	public static byte[][] convertByteStreams(List<ByteString> valuesList) {

+		byte[][] byteStreams = new byte[valuesList.size()][];

+		for (int i = 0; i < valuesList.size(); i++) {

+			byteStreams[i] = valuesList.get(i).toByteArray();

+		}

+		return byteStreams;

+	}

+

+	/**

+	 * Converts an {@link org.eclipse.mdm.protobuf.Mdm.AxisType} to {@link AxisType}

+	 * 

+	 * @param axisType

+	 * @return converted {@link AxisType}

+	 */

+	public static AxisType convert(Mdm.AxisType axisType) {

+		switch (axisType) {

+		case X_AXIS:

+			return AxisType.X_AXIS;

+		case Y_AXIS:

+			return AxisType.Y_AXIS;

+		case XY_AXIS:

+			return AxisType.XY_AXIS;

+		default:

+			throw new RuntimeException("Invalid value for AxisType: " + axisType.name());

+		}

+	}

+

+	/**

+	 * Converts an {@link org.eclipse.mdm.protobuf.Mdm.ScalarType} to a

+	 * {@link ScalarType}.

+	 * 

+	 * @param scalarType

+	 * @return converted {@link ScalarType}

+	 */

+	public static ScalarType convert(Mdm.ScalarType scalarType) {

+		switch (scalarType) {

+		case STRING:

+			return ScalarType.STRING;

+		case DATE:

+			return ScalarType.DATE;

+		case BOOLEAN:

+			return ScalarType.BOOLEAN;

+		case BYTE:

+			return ScalarType.BYTE;

+		case SHORT:

+			return ScalarType.SHORT;

+		case INTEGER:

+			return ScalarType.INTEGER;

+		case LONG:

+			return ScalarType.LONG;

+		case FLOAT:

+			return ScalarType.FLOAT;

+		case DOUBLE:

+			return ScalarType.DOUBLE;

+		case BYTE_STREAM:

+			return ScalarType.BYTE_STREAM;

+		case FLOAT_COMPLEX:

+			return ScalarType.FLOAT_COMPLEX;

+		case DOUBLE_COMPLEX:

+			return ScalarType.DOUBLE_COMPLEX;

+		case ENUMERATION:

+			return ScalarType.ENUMERATION;

+		case FILE_LINK:

+			return ScalarType.FILE_LINK;

+		case BLOB:

+			return ScalarType.BLOB;

+		case UNKNOWN:

+			return ScalarType.UNKNOWN;

+		default:

+			throw new RuntimeException("Invalid value for ScalarType: " + scalarType.name());

+		}

+	}

+

+	/**

+	 * Converts a list of {@link MeasuredValues} to a {@link MeasuredValuesList}

+	 * 

+	 * @param measuredValues

+	 * @return converted {@link MeasuredValuesList}

+	 */

+	public static MeasuredValuesList convert(List<MeasuredValues> measuredValues) {

+		MeasuredValuesList.Builder builder = MeasuredValuesList.newBuilder();

+		for (MeasuredValues m : measuredValues) {

+			builder.addValues(convert(m));

+		}

+		return builder.build();

+	}

+

+	/**

+	 * Converts {@link MeasuredValues} to

+	 * {@link org.eclipse.mdm.protobuf.Mdm.MeasuredValues}

+	 * 

+	 * @param m

+	 * @return converted {@link org.eclipse.mdm.protobuf.Mdm.MeasuredValues}

+	 */

+	public static Mdm.MeasuredValues convert(MeasuredValues m) {

+

+		Mdm.MeasuredValues.Builder builder = Mdm.MeasuredValues.newBuilder().setName(m.getName()).setUnit(m.getUnit())

+				.setLength(m.getLength()).setAxisType(convert(m.getAxisType())).setIndependent(m.isIndependent())

+				.setScalarType(Mdm.ScalarType.valueOf(m.getScalarType().name()))

+				.addAllGenerationParameters(Doubles.asList(m.getGenerationParameters()));

+

+		BooleanArray.Builder flags = BooleanArray.newBuilder();

+		ValueIterator<Object> it = m.iterator();

+

+		if (m.getScalarType().isString()) {

+			StringArray.Builder strings = StringArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				strings.addValues((String) it.next());

+			}

+			builder.setStringArray(strings);

+		} else if (m.getScalarType().isDate()) {

+			DateArray.Builder dates = DateArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				LocalDateTime t = (LocalDateTime) it.next();

+				Instant time = t.toInstant(ZoneOffset.UTC);

+				dates.addValues(

+						Timestamp.newBuilder().setSeconds(time.getEpochSecond()).setNanos(time.getNano()).build());

+			}

+			builder.setDateArray(dates);

+		} else if (m.getScalarType().isBoolean()) {

+			BooleanArray.Builder booleans = BooleanArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				booleans.addValues((boolean) it.next());

+			}

+			builder.setBooleanArray(booleans);

+		} else if (m.getScalarType().isByte()) {

+			ByteArrayOutputStream bytes = new ByteArrayOutputStream();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				bytes.write((byte) it.next());

+			}

+			builder.setByteArray(ByteArray.newBuilder().setValues(ByteString.copyFrom(bytes.toByteArray())));

+		} else if (m.getScalarType().isShort()) {

+			ShortArray.Builder shorts = ShortArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				shorts.addValues((short) it.next());

+			}

+			builder.setShortArray(shorts);

+		} else if (m.getScalarType().isInteger()) {

+			IntegerArray.Builder ints = IntegerArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				int i = (int) it.next();

+				ints.addValues(i);

+			}

+			builder.setIntegerArray(ints);

+		} else if (m.getScalarType().isLong()) {

+			LongArray.Builder ints = LongArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				ints.addValues((long) it.next());

+			}

+			builder.setLongArray(ints);

+		} else if (m.getScalarType().isFloat()) {

+			FloatArray.Builder floats = FloatArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				floats.addValues((float) it.next());

+			}

+			builder.setFloatArray(floats);

+		} else if (m.getScalarType().isDouble()) {

+			DoubleArray.Builder doubles = DoubleArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				doubles.addValues((double) it.next());

+			}

+			builder.setDoubleArray(doubles);

+		} else if (m.getScalarType().isByteStream()) {

+			ByteStreamArray.Builder bytestrs = ByteStreamArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				bytestrs.addValues(ByteString.copyFrom((byte[]) it.next()));

+			}

+			builder.setByteStreamArray(bytestrs);

+		} else if (m.getScalarType().isFloatComplex()) {

+			FloatComplexArray.Builder floats = FloatComplexArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				floats.addValues(convert((FloatComplex) it.next()));

+			}

+			builder.setFloatComplexArray(floats);

+		} else if (m.getScalarType().isDoubleComplex()) {

+			DoubleComplexArray.Builder doubles = DoubleComplexArray.newBuilder();

+			while (it.hasNext()) {

+				flags.addValues(it.isValid());

+				doubles.addValues(convert((DoubleComplex) it.next()));

+			}

+			builder.setDoubleComplexArray(doubles);

+		} else {

+			throw new IllegalArgumentException(

+					"MeasuredValues with scalarType '" + m.getScalarType() + "' not supported!");

+		}

+		builder.addAllFlags(flags.getValuesList());

+

+		return builder.build();

+	}

+

+	/**

+	 * Converts between FloatComplex.

+	 * 

+	 * @param complex

+	 * @return

+	 */

+	public static Mdm.FloatComplex convert(FloatComplex complex) {

+		return Mdm.FloatComplex.newBuilder().setRe(complex.real()).setIm(complex.imaginary()).build();

+	}

+

+	/**

+	 * Converts between DoubleComplex.

+	 * 

+	 * @param complex

+	 * @return

+	 */

+	public static Mdm.DoubleComplex convert(DoubleComplex complex) {

+		return Mdm.DoubleComplex.newBuilder().setRe(complex.real()).setIm(complex.imaginary()).build();

+	}

+

+	/**

+	 * Converts between AxisType.

+	 * 

+	 * @param axisType

+	 * @return

+	 */

+	public static Mdm.AxisType convert(AxisType axisType) {

+		return Mdm.AxisType.valueOf(axisType.name());

+	}

+

+	/**

+	 * Converts between ValuesMode.

+	 * 

+	 * @param valuesMode

+	 * @return

+	 */

+	public static ValuesMode convert(Mdm.ValuesMode valuesMode) {

+		return ValuesMode.valueOf(valuesMode.name());

+	}

+

+	/**

+	 * Converts between ScalarType.

+	 * 

+	 * @param value

+	 * @return

+	 */

+	public static Mdm.ScalarType convert(ScalarType value) {

+		return Mdm.ScalarType.valueOf(value.name());

+	}

+

+	/**

+	 * Converts a {@link org.eclipse.mdm.protobuf.Mdm.ReadRequest} to a

+	 * {@link ReadRequest}

+	 * 

+	 * @param context

+	 * @param protoReadRequest

+	 * @return converted {@link ReadRequest}

+	 */

+	public static ReadRequest convert(ApplicationContext context, Mdm.ReadRequest protoReadRequest) {

+

+		EntityManager em = context.getEntityManager()

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

+

+		ChannelGroup channelGroup = em.load(ChannelGroup.class, protoReadRequest.getChannelGroupId());

+		ReadRequestBuilder rb = ReadRequest.create(channelGroup);

+		if (protoReadRequest.getChannelIdsCount() == 0) {

+			rb = rb.allChannels();

+		} else {

+			// Load Channels and group by ID. If multiple Channels with the same ID are

+			// loaded (which would be incorrect data), only the first one is used.

+			Map<String, Optional<Channel>> channels = em.load(Channel.class, protoReadRequest.getChannelIdsList())

+					.stream().collect(Collectors.groupingBy(Channel::getID, Collectors.reducing((c1, c2) -> c1)));

+

+			// Load Units and group by ID. If multiple Units with the same ID are

+			// loaded (which would be incorrect data), only the first one is used.

+			Map<String, Optional<Unit>> units = em.load(Unit.class, protoReadRequest.getUnitIdsList()).stream()

+					.collect(Collectors.groupingBy(Unit::getID, Collectors.reducing((u1, u2) -> u1)));

+

+			for (int i = 0; i < protoReadRequest.getChannelIdsCount(); i++) {

+				String channelId = protoReadRequest.getChannelIds(i);

+

+				Channel channel = channels.get(channelId).orElseThrow(

+						() -> new IllegalArgumentException("Channel with ID '" + channelId + "' does not exist!"));

+

+				final String unitId;

+				if (i < protoReadRequest.getUnitIdsCount()) {

+					unitId = protoReadRequest.getUnitIds(i);

+				} else {

+					unitId = null;

+				}

+

+				Unit unit;

+

+				if (Strings.isNullOrEmpty(unitId)) {

+					// no unit provided -> use unit from channel

+					unit = channel.getUnit();

+				} else {

+					unit = units.get(unitId).orElseThrow(

+							() -> new IllegalArgumentException("Unit with ID '" + unitId + "' does not exist!"));

+				}

+				rb = rb.channel(channel, unit);

+			}

+		}

+

+		return rb.valuesMode(convert(protoReadRequest.getValuesMode())).values(protoReadRequest.getStartIndex(),

+				protoReadRequest.getRequestSize());

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufMessageBodyProvider.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufMessageBodyProvider.java
new file mode 100644
index 0000000..10b609d
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ProtobufMessageBodyProvider.java
@@ -0,0 +1,144 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

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

+

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.lang.annotation.Annotation;

+import java.lang.reflect.InvocationTargetException;

+import java.lang.reflect.Method;

+import java.lang.reflect.Type;

+

+import javax.ws.rs.Consumes;

+import javax.ws.rs.Produces;

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

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

+import javax.ws.rs.ext.MessageBodyReader;

+import javax.ws.rs.ext.MessageBodyWriter;

+import javax.ws.rs.ext.Provider;

+

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+import com.google.common.io.ByteStreams;

+import com.google.protobuf.AbstractMessage;

+import com.google.protobuf.CodedInputStream;

+import com.google.protobuf.InvalidProtocolBufferException;

+import com.google.protobuf.Message;

+

+/**

+ * MessageBodyProvider for handling protobuf payloads.

+ * 

+ */

+@Provider

+@Consumes({ MEDIATYPE_APPLICATION_PROTOBUF })

+@Produces({ MEDIATYPE_APPLICATION_PROTOBUF })

+public class ProtobufMessageBodyProvider implements MessageBodyReader<Message>, MessageBodyWriter<Message> {

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

+

+	private static final int DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB

+	private static final int MAX_SIZE = DEFAULT_SIZE_LIMIT;

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see javax.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class,

+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],

+	 * javax.ws.rs.core.MediaType)

+	 */

+	@Override

+	public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations,

+			final MediaType mediaType) {

+		return Message.class.isAssignableFrom(type);

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class,

+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],

+	 * javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap,

+	 * java.io.InputStream)

+	 */

+	@Override

+	public Message readFrom(final Class<Message> type, final Type genericType, final Annotation[] annotations,

+			final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)

+			throws IOException {

+		try {

+			final Method newBuilder = type.getMethod("newBuilder");

+			final AbstractMessage.Builder<?> builder = (AbstractMessage.Builder<?>) newBuilder.invoke(type);

+

+			CodedInputStream in = CodedInputStream.newInstance(entityStream);

+			in.setSizeLimit(MAX_SIZE);

+

+			byte[] b = ByteStreams.toByteArray(entityStream);

+			LOG.debug("Reading type {} with size {}.", genericType, b.length);

+			return builder.mergeFrom(b).build();

+		} catch (InvalidProtocolBufferException e) {

+			throw new IOException("Could not read Protobuf entity!", e);

+		} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {

+			throw new IOException("Could not retrive builder for type " + genericType, e);

+		}

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object,

+	 * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[],

+	 * javax.ws.rs.core.MediaType)

+	 */

+	@Override

+	public long getSize(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations,

+			final MediaType mediaType) {

+		return -1; // as method is not actually used by JAX-RS

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class,

+	 * java.lang.reflect.Type, java.lang.annotation.Annotation[],

+	 * javax.ws.rs.core.MediaType)

+	 */

+	@Override

+	public boolean isWriteable(final Class<?> type, final Type genericType, final Annotation[] annotations,

+			final MediaType mediaType) {

+		return Message.class.isAssignableFrom(type);

+	}

+

+	/*

+	 * (non-Javadoc)

+	 * 

+	 * @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object,

+	 * java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[],

+	 * javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap,

+	 * java.io.OutputStream)

+	 */

+	@Override

+	public void writeTo(final Message m, final Class<?> type, final Type genericType, final Annotation[] annotations,

+			final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders,

+			final OutputStream entityStream) throws IOException {

+

+		if (LOG.isDebugEnabled()) {

+			LOG.debug("Writing type {} with size {}.", genericType, m.getSerializedSize());

+		}

+		m.writeTo(entityStream);

+	}

+

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ReflectUtil.java b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ReflectUtil.java
new file mode 100644
index 0000000..1cff072
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/java/org/eclipse/mdm/businessobjects/utils/ReflectUtil.java
@@ -0,0 +1,60 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

+import io.vavr.collection.HashMap;

+import io.vavr.collection.Map;

+

+/**

+ * Utility class for method reflection

+ * 

+ * @author Johannes Stamm, Peak Solution GmbH Nuernberg

+ *

+ */

+public class ReflectUtil {

+

+	private static final Map<Class<?>, Class<?>> TYPE_MAP = HashMap.of(boolean.class, Boolean.class, byte.class,

+			Byte.class, char.class, Character.class, double.class, Double.class, float.class, Float.class, int.class,

+			Integer.class, long.class, Long.class, short.class, Short.class);

+

+	/**

+	 * This function returns same value as to.isAssignableFrom(from), except for

+	 * wrapper classes.

+	 * 

+	 * to.isAssignableFrom(from) returns false for wrapper class <-> primitive This

+	 * function returns true in that case.

+	 * 

+	 * @param from class to assign from

+	 * @param to   class to assign to

+	 * @return true if assigning is possible (respecting auto boxing/unboxing) false

+	 *         otherwise

+	 */

+	public static boolean isAssignable(Class<?> from, Class<?> to) {

+		return to.isAssignableFrom(from) || isPrimitiveWrapperOf(to, from) || isPrimitiveWrapperOf(from, to);

+	}

+

+	/**

+	 * Checks if wrapperClazz is wrapper class of primitiveClazz.

+	 * 

+	 * @param wrapperClazz   the wrapper class

+	 * @param primitiveClazz the primitive class

+	 * @return True if wrapperClazz is wrapper class of primitiveClazz, false

+	 *         otherwise.

+	 */

+	private static boolean isPrimitiveWrapperOf(Class<?> wrapperClazz, Class<?> primitiveClazz) {

+		return TYPE_MAP.get(primitiveClazz).map(c -> c == wrapperClazz).getOrElse(false);

+	}

+

+}

diff --git a/org.eclipse.mdm.businessobjects/src/main/proto/mdm.proto b/org.eclipse.mdm.businessobjects/src/main/proto/mdm.proto
new file mode 100644
index 0000000..073d57d
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/main/proto/mdm.proto
@@ -0,0 +1,374 @@
+/********************************************************************************

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

+ *

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

+syntax = "proto3";

+

+package org.eclipse.mdm.protobuf;

+

+import "google/protobuf/timestamp.proto";

+

+/**

+ * Specifies if the MeasuredValues contain the final values or the generation parameters.

+ */

+enum ValuesMode {

+		/**

+		 * MeasuredValues will contain the final values, calculated for non-explicit

+		 * sequence representations using generation parameters and, if applicable, the

+		 * raw values. No generation parameters are returned in the resulting

+		 * MeasuredValues, SequenceRepresentation will be EXPLICIT.

+		 */

+		CALCULATED = 0;

+		/**

+		 * MeasuredValues will contain the (raw) values and generation parameters as

+		 * present in the storage.

+		 */

+		STORAGE = 1;

+}

+

+/**

+ * Scalar type enumeration represents the datatype of MeasuredValues.

+ */

+enum ScalarType {

+	STRING = 0;          // MeasuredValues are provided as StringArray

+	DATE = 1;            // MeasuredValues are provided as DateArray

+	BOOLEAN = 2;         // MeasuredValues are provided as BooleanArray

+	BYTE = 3;            // MeasuredValues are provided as ByteArray

+	SHORT = 4;           // MeasuredValues are provided as ShortArray

+	INTEGER = 5;         // MeasuredValues are provided as IntegerArray

+	LONG = 6;            // MeasuredValues are provided as LongArray

+	FLOAT = 7;           // MeasuredValues are provided as FloatArray

+	DOUBLE = 8;          // MeasuredValues are provided as DoubleArray

+	BYTE_STREAM = 9;     // MeasuredValues are provided as ByteStreamArray

+	FLOAT_COMPLEX = 10;  // MeasuredValues are provided as FloatComplexArray

+	DOUBLE_COMPLEX = 11; // MeasuredValues are provided as DoubleComplexArray

+	ENUMERATION = 12;    // not used for MeasuredValues

+	FILE_LINK = 13;      // not used for MeasuredValues

+	BLOB = 14;           // not used for MeasuredValues

+	UNKNOWN = 15;        // not used for MeasuredValues

+}

+

+/**

+ * This is the axis type enumeration as defined in the ASAM ODS NVH model.

+ */

+enum AxisType {

+	X_AXIS = 0;  // A Channel of this type may be displayed as the x-axis.

+	Y_AXIS = 1;  // A Channel of this type may be displayed as the y-axis.

+	XY_AXIS = 2; // A Channel of this type may be displayed as the x- or y-axis.

+}

+

+/**

+ * The sequence representation to describe the storage type of measured values.

+ */

+enum SequenceRepresentation {

+  /**

+	 * Measured values are stored as is and values are therefore immediately

+	 * available.

+	 */

+	EXPLICIT = 0;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> = p for i

+	 * &isin; [1, n], n is the total number of values and generation parameter p

+	 * (offset).

+	 */

+	IMPLICIT_CONSTANT = 1;

+	  /**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * p<sub>1</sub>+(i-1)*p<sub>2</sub> for i &isin; [1, n], n is the total number

+	 * of values and generation parameters p<sub>1</sub> (start value) and

+	 * p<sub>2</sub> (increment).

+	 */

+	IMPLICIT_LINEAR = 2;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * p<sub>1</sub>+((i-1)mod(p<sub>3</sub>-p<sub>1</sub>)/p<sub>2</sub>)*

+	 * p<sub>2</sub> for i &isin; [1, n], n is the total number of values and

+	 * generation parameters p<sub>1</sub> (start value), p<sub>2</sub> (increment)

+	 * and p<sub>3</sub> (number of values per saw). The expression

+	 * (p<sub>3</sub>-p<sub>1</sub>)/p<sub>2</sub> must be truncated to integer to

+	 * start each saw curve cycle at p<sub>1</sub>.

+	 */

+	IMPLICIT_SAW = 3;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * p<sub>1</sub>+p<sub>2</sub>*r<sub>i</sub> for i &isin; [1, n], n is the total

+	 * number of values and generation parameters p<sub>1</sub> (offset),

+	 * p<sub>2</sub> (factor) and the raw value r at position i.

+	 */

+	RAW_LINEAR = 4;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> = &sum;

+	 * p<sub>j</sub>*r<sub>i</sub><sup>j-1</sup> = p<sub>2</sub>+p<sub>3

+	 * </sub>*r<sub>i</sub>+p<sub>4</sub>*r<sub>i</sub><sup>2</sup>+... for i &isin;

+	 * [1, n], n is the total number of values and generation parameters

+	 * p<sub>j</sub> for j &isin; [1, p<sub>1</sub>] and the raw value r at position

+	 * i.

+	 */

+	RAW_POLYNOMIAL = 5;

+	/*

+	 * Not used. But keep here to show ordinal sequence.

+	 */

+	FORMULA = 6;

+	/**

+	 * Measured values are stored as is in an external file and values are therefore

+	 * immediately available.

+	 */

+	EXPLICIT_EXTERNAL = 7;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * p<sub>1</sub>+p<sub>2</sub>*r<sub>i</sub> for i &isin; [1, n], n is the total

+	 * number of values and generation parameters p<sub>1</sub> (offset),

+	 * p<sub>2</sub> (factor) and the raw value r at position i read from an

+	 * external file.

+	 */

+	RAW_LINEAR_EXTERNAL = 8;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> = &sum;

+	 * p<sub>j</sub>*r<sub>i</sub><sup>j-1</sup> = p<sub>2</sub>+p<sub>3

+	 * </sub>*r<sub>i</sub>+p<sub>4</sub>*r<sub>i</sub><sup>2</sup>+... for i &isin;

+	 * [1, n], n is the total number of values and generation parameters

+	 * p<sub>j</sub> for j &isin; [1, p<sub>1</sub>] and the raw value r at position

+	 * i read from an external file.

+	 */

+	RAW_POLYNOMIAL_EXTERNAL = 9;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * (p<sub>1</sub>+p<sub>2</sub>*r<sub>i</sub>)*p<sub>3</sub> for i &isin; [1,

+	 * n], n is the total number of values and generation parameters p<sub>1</sub>

+	 * (offset), p<sub>2</sub> (factor), p<sub>2</sub> (calibration) and the raw

+	 * value r at position i.

+	 */

+	RAW_LINEAR_CALIBRATED = 10;

+	/**

+	 * Each value x<sub>i</sub> is generated as follows: x<sub>i</sub> =

+	 * (p<sub>1</sub>+p<sub>2</sub>*r<sub>i</sub>)*p<sub>3</sub> for i &isin; [1,

+	 * n], n is the total number of values and generation parameters p<sub>1</sub>

+	 * (offset), p<sub>2</sub> (factor), p<sub>2</sub> (calibration) and the raw

+	 * value r at position i read from an external file.

+	 */

+	RAW_LINEAR_CALIBRATED_EXTERNAL = 11;

+}

+

+/**

+ * This class provides all required informations to load measured values.

+ */

+ message ReadRequest {

+	string channel_group_id = 1;     // ID of the ChannelGroup which Channel values are requested.

+

+	repeated string channel_ids = 2; // IDs of the channels to load. If empty, all channels are loaded.

+	/**

+	 * IDSs of the units in which the corresponding channel values (i.e. the channels at the same index as in channel_ids)

+	 * are loaded. A missing or empty string is interpreted as the default unit of the channel.

+	 * Superfluous unit IDs are ignored.

+	 */

+	repeated string unit_ids = 3;

+

+	int32 request_size = 4;          // Number of requested values

+	int32 start_index = 5;           // Start index of the requested values

+

+	ValuesMode values_mode = 6;      // Mode in which the values are returned. See description of ValueMode enum.

+}

+

+/**

+ * This message represents a complex value with real and imaginary parts of type float.

+ */

+message FloatComplex {

+	float re = 1; // The real part.

+	float im = 2; // The imaginary part.

+}

+

+/**

+ * This message represents a complex value with real and imaginary parts of type double.

+ */

+message DoubleComplex {

+	double re = 1; // The real part.

+	double im = 2; // The imaginary part.

+}

+

+message StringArray        { repeated string values = 1; }

+message DateArray          { repeated google.protobuf.Timestamp values = 1; }

+message BooleanArray       { repeated bool   values = 1 [packed=true]; }

+message ByteArray          {          bytes  values = 1; }

+message ShortArray         { repeated int32  values = 1 [packed=true]; }

+message IntegerArray       { repeated int32  values = 1 [packed=true]; }

+message LongArray          { repeated int64  values = 1 [packed=true]; }

+message FloatArray         { repeated float  values = 1 [packed=true]; }

+message DoubleArray        { repeated double values = 1 [packed=true]; }

+message ByteStreamArray    { repeated bytes  values = 1; }

+message FloatComplexArray  { repeated FloatComplex  values = 1; }

+message DoubleComplexArray { repeated DoubleComplex values = 1; }

+

+/**

+ * This message represents a sequence of measured values for one Channel.

+ */

+message MeasuredValues {

+	string name = 1;                                         // Name of the channel

+	string unit = 2;                                         // Unit of the values

+	int32 length = 3;                                        // Length of the channel

+

+	SequenceRepresentation sequence_representation = 4;      // The SequenceRepresentation of the measured values.

+	repeated double generation_parameters = 5 [packed=true]; // The generation parameters of the channel

+	bool independent = 6;                                    // The independent flag of the channel

+	AxisType axis_type = 7;                                  // The axis type of the channel

+	ScalarType scalar_type = 8;                              // Scalar type of the channel's values

+	oneof values {

+		StringArray         string_array = 9;                // STRING

+		DateArray           date_array = 10;                 // DATE

+		BooleanArray        boolean_array = 11;              // BOOLEAN

+		ByteArray           byte_array = 12;                 // BYTE

+		ShortArray          short_array = 13;                // SHORT

+		IntegerArray        integer_array = 14;              // INTEGER

+		LongArray           long_array = 15;                 // LONG

+		FloatArray          float_array = 16;                // FLOAT

+		DoubleArray         double_array = 17;               // DOUBLE,

+		ByteStreamArray     byte_stream_array = 18;          // BYTE_STREAM

+		FloatComplexArray   float_complex_array = 19;        // FLOAT_COMPLEX

+		DoubleComplexArray  double_complex_array = 20;       // DOUBLE_COMPLEX

+		// The following datatypes are not supported in ODS for measuredvalues: ENUMERATION, FILE_LINK, BLOB, UNKNOWN

+	}                                                        // the actual measured values

+	repeated bool flags = 21 [packed=true];                  // The flags of the measured values

+}

+

+/**

+ * Container for a list of MeasuredValues

+ */

+message MeasuredValuesList {

+	repeated MeasuredValues values = 1; // List of MeasuredValues

+}

+

+/**

+ * Container for a list of WriteRequests

+ */

+message WriteRequestList {

+	/**

+	 * Holds required data to write mass data.

+	 */

+	message WriteRequest {

+		message ExplicitData {

+			oneof values {

+				StringArray         string_array = 1;          // STRING

+				DateArray           date_array = 2;            // DATE

+				BooleanArray        boolean_array = 3;         // BOOLEAN

+				ByteArray           byte_array = 4;            // BYTE

+				ShortArray          short_array = 5;           // SHORT

+				IntegerArray        integer_array = 6;         // INTEGER

+				LongArray           long_array = 7;            // LONG

+				FloatArray          float_array = 8;           // FLOAT

+				DoubleArray         double_array = 9;          // DOUBLE,

+				ByteStreamArray     byte_stream_array = 10;    // BYTE_STREAM

+				FloatComplexArray   float_complex_array = 11;  // FLOAT_COMPLEX

+				DoubleComplexArray  double_complex_array = 12; // DOUBLE_COMPLEX

+				// The following datatypes are not supported in ODS for measuredvalues: ENUMERATION, FILE_LINK, BLOB, UNKNOWN

+			} // the actual values

+

+			repeated bool flags = 13 [packed=true];            // The flags of the measured values

+		}

+

+		/**

+		 * Configures a WriteRequest to create an implicit linear sequence

+		 * of measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.implicitLinear(ScalarType, double, double)

+		 */

+		message ImplicitLinear {

+			ScalarType scalar_type = 1; // Scalar type of the channel's values

+			double start = 2;           // The start value of the line.

+			double increment = 3;       // The gradient of the line.

+		}

+

+		/**

+		 * Configures a WriteRequest to create an implicit saw sequence

+		 * of measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.implicitSaw(ScalarType, double, double, double).

+		 */

+		message ImplicitSaw {

+			ScalarType scalar_type = 1; // Scalar type of the channel's values

+			double start = 2;           // The start value of each saw cycle.

+			double increment = 3;       // The increment.

+			double stop = 4;            // The stop value of each saw cycle. The stop value is exclusive.

+		}

+

+		/**

+		 * Configures a WriteRequest to create an implicit constant sequence

+		 * of measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.implicitConstant(ScalarType, double).

+		 */

+		message ImplicitConstant {

+			ScalarType scalar_type = 1; // Scalar type of the channel's values

+			double offset = 2;          // The constant value.

+		}

+

+		/**

+		 * Configures the WriteRequest to create a raw linear sequence of

+		 * measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.rawLinear(double, double)

+		 */

+		message RawLinear {

+			ScalarType scalar_type = 1; // Scalar type of the channel's values

+			double offset = 2;          // The offset for each value.

+			double factor = 3;          // The factor for each value.

+			ExplicitData values = 4;    // The raw values.

+		}

+

+		/**

+		 * Configures the WriteRequest to create a raw polynomial sequence of

+		 * measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.rawPolynomial(double...)

+		 */

+		message RawPolynomial {

+			ScalarType scalar_type = 1;       // Scalar type of the channel's values

+			repeated double coefficients = 2; // At least 2 coefficients must be provided.

+			ExplicitData values = 3;          // The raw values.

+		}

+

+		/**

+		 * Configures the WriteRequest to create a raw linear calibrated sequence of

+		 * measured values. See org.eclipse.mdm.api.base.massdata.WriteRequestBuilder.rawLinearCalibrated(double, double, double)

+		 */

+		message RawLinearCalibrated {

+			ScalarType scalar_type = 1; // Scalar type of the channel's values

+			double offset = 2;          // The offset for each value.

+			double factor = 3;          // The factor for each value.

+			double calibration = 4;     // The calibration factor.

+			ExplicitData values = 5;    // The raw values.

+		}

+

+		string channel_group_id = 1;    // The ChannelGroup for this request.

+		string channel_id = 2;          // The Channel specified mass data will be dedicated to.

+		AxisType axis_type = 3;         // The AxisType of the written mass data.

+		bool independent = 4;           // The independent flag for the Channel

+		oneof data {

+			ExplicitData explicit = 5;

+			ImplicitConstant implicit_constant = 6;

+			ImplicitLinear implicit_linear = 7;

+			ImplicitSaw implicit_saw = 8;

+			RawLinear raw_linear = 9;

+			RawPolynomial raw_polynomial = 10;

+			RawLinearCalibrated raw_linear_calibrated = 11;

+		}                               // The mass data for this Channel

+		string source_unit_id = 12;     // The source Unit ID for the data. (Hint this not implemented as of version 5.1.0M6)

+	}

+	repeated WriteRequest values = 1;   // The List of WriteRequests

+}

+

+/**

+ * Message to request preview values.

+ * Preview values are only provided for numerical Datatypes.

+ * If channels with other Datatypes are requested, an empty MeasuredValues message is returned.

+ */

+message PreviewRequest {

+	ReadRequest read_request = 1; // Read request, which specifies which data should be read.

+	int32 number_of_chunks = 2;   // Number of chunks, e.g. number of values returned by the preview request.

+}

+

+/**

+ * Message containing preview data requested by a PreviewRequest

+ */

+message PreviewValuesList {

+	repeated MeasuredValues min = 1; // minimum values for each chunk

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

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

+}
\ No newline at end of file
diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelGroupResourceIntegrationTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelGroupResourceIntegrationTest.java
new file mode 100644
index 0000000..2a322bb
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelGroupResourceIntegrationTest.java
@@ -0,0 +1,64 @@
+/********************************************************************************

+ * 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.businessobjects.boundary.integrationtest;

+

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

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+

+import com.google.gson.JsonObject;

+import com.google.gson.JsonPrimitive;

+

+/**

+ * Test class for {@ChannelGroupResource}.

+ * 

+ * @author Johannes Stamm, Peak Solution GmbH Nuernberg

+ * @see EntityResourceIntegrationTest

+ *

+ */

+public class ChannelGroupResourceIntegrationTest extends EntityResourceIntegrationTest {

+	@BeforeClass

+	public static void prepareTestData() {

+		getLogger().debug("Preparing ChannelGroupResourceIntegrationTest");

+

+		// prepare test data for creating the parent Measurement

+		MeasurementResourceIntegrationTest.prepareTestData();

+		MeasurementResourceIntegrationTest.createEntity();

+

+		// set up test data

+		setContextClass(ChannelGroupResourceIntegrationTest.class);

+

+		putTestDataValue(TESTDATA_RESOURCE_URI, "/channelgroups");

+		putTestDataValue(TESTDATA_ENTITY_NAME, "testChannelGroup");

+		putTestDataValue(TESTDATA_ENTITY_TYPE, "ChannelGroup");

+		putTestDataValue(TESTDATA_NUMBER_OF_VALUES, "42");

+

+		// json object for teststep entity

+		JsonObject json = new JsonObject();

+		json.add(ResourceConstants.ENTITYATTRIBUTE_NAME, new JsonPrimitive(getTestDataValue(TESTDATA_ENTITY_NAME)));

+		json.add(ResourceConstants.ENTITYATTRIBUTE_NUMBER_OF_VALUES,

+				new JsonPrimitive(getTestDataValue(TESTDATA_NUMBER_OF_VALUES)));

+		json.add("Measurement",

+				new JsonPrimitive(getTestDataValue(MeasurementResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+

+		putTestDataValue(TESTDATA_CREATE_JSON_BODY, json.toString());

+	}

+

+	@AfterClass

+	public static void tearDownAfterClass() {

+		// call tearDownAfterClass() of parent entity

+		setContextClass(MeasurementResourceIntegrationTest.class);

+		MeasurementResourceIntegrationTest.tearDownAfterClass();

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelResourceIntegrationTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelResourceIntegrationTest.java
new file mode 100644
index 0000000..bb1bff9
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ChannelResourceIntegrationTest.java
@@ -0,0 +1,66 @@
+/********************************************************************************

+ * 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.businessobjects.boundary.integrationtest;

+

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

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+

+import com.google.gson.JsonObject;

+import com.google.gson.JsonPrimitive;

+

+/**

+ * Test class for {@ChannelResource}.

+ * 

+ * @author Johannes Stamm, Peak Solution GmbH Nuernberg

+ * @see EntityResourceIntegrationTest

+ *

+ */

+public class ChannelResourceIntegrationTest extends EntityResourceIntegrationTest {

+	@BeforeClass

+	public static void prepareTestData() {

+		getLogger().debug("Preparing ChannelGroupResourceIntegrationTest");

+

+		// prepare test data for creating the parent Measurement

+		MeasurementResourceIntegrationTest.prepareTestData();

+		MeasurementResourceIntegrationTest.createEntity();

+		// prepare test data for creating Quantity

+		QuantityResourceIntegrationTest.prepareTestData();

+		QuantityResourceIntegrationTest.createEntity();

+

+		// set up test data

+		setContextClass(ChannelResourceIntegrationTest.class);

+

+		putTestDataValue(TESTDATA_RESOURCE_URI, "/channels");

+		putTestDataValue(TESTDATA_ENTITY_NAME, "testChannel");

+		putTestDataValue(TESTDATA_ENTITY_TYPE, "Channel");

+

+		// json object for teststep entity

+		JsonObject json = new JsonObject();

+		json.add(ResourceConstants.ENTITYATTRIBUTE_NAME, new JsonPrimitive(getTestDataValue(TESTDATA_ENTITY_NAME)));

+		json.add("Measurement",

+				new JsonPrimitive(getTestDataValue(MeasurementResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+		json.add("Quantity",

+				new JsonPrimitive(getTestDataValue(QuantityResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+

+		putTestDataValue(TESTDATA_CREATE_JSON_BODY, json.toString());

+	}

+

+	@AfterClass

+	public static void tearDownAfterClass() {

+		// call tearDownAfterClass() of parent entity

+		setContextClass(MeasurementResourceIntegrationTest.class);

+		MeasurementResourceIntegrationTest.tearDownAfterClass();

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ResourceIntegrationTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ResourceIntegrationTest.java
index cf0ee79..343ccf2 100644
--- a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ResourceIntegrationTest.java
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ResourceIntegrationTest.java
@@ -24,7 +24,8 @@
 import org.slf4j.LoggerFactory;
 
 import io.restassured.RestAssured;
-import io.restassured.authentication.PreemptiveBasicAuthScheme;
+import io.restassured.authentication.FormAuthConfig;
+import io.restassured.authentication.FormAuthScheme;
 import io.vavr.collection.HashMap;
 import io.vavr.collection.HashSet;
 import io.vavr.collection.Map;
@@ -42,7 +43,7 @@
 	private static final String BASE_PATH = "org.eclipse.mdm.nucleus";
 	private static final String API_PATH = "mdm";
 	private static final String ENV_PATH = "environments";
-	protected static final String SOURCE_NAME = "BMWMDM";
+	protected static final String SOURCE_NAME = "MDMNVH";
 
 	private static final String AUTH_USERNAME = "sa";
 	private static final String AUTH_PASSWORD = "sa";
@@ -52,6 +53,7 @@
 	protected static final String TESTDATA_ENTITY_TYPE = "entityType";
 	protected static final String TESTDATA_CONTEXT_GROUP = "contextGroup";
 	protected static final String TESTDATA_CREATE_JSON_BODY = "createJSONBody";
+	protected static final String TESTDATA_NUMBER_OF_VALUES = "NumberOfValues";
 	protected static final String TESTDATA_UPDATE_JSON_BODY = "updateJSONBody";
 	protected static final String TESTDATA_RESOURCE_URI = "resourceURI";
 	protected static final String TESTDATA_RANDOM_DATA = "RANDOM_DATA";
@@ -87,7 +89,9 @@
 		LOGGER.debug("RestAssured set up to " + RestAssured.baseURI + "/" + RestAssured.basePath);
 
 		// setup authentication
-		PreemptiveBasicAuthScheme authScheme = new PreemptiveBasicAuthScheme();
+//		PreemptiveBasicAuthScheme authScheme = new PreemptiveBasicAuthScheme();
+		FormAuthScheme authScheme = new FormAuthScheme();
+		authScheme.setConfig(new FormAuthConfig("/" + BASE_PATH + "/j_security_check", "j_username", "j_password"));
 		authScheme.setUserName(AUTH_USERNAME);
 		authScheme.setPassword(AUTH_PASSWORD);
 
diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ValuesResourceIntegrationTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ValuesResourceIntegrationTest.java
new file mode 100644
index 0000000..4b93cab
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/boundary/integrationtest/ValuesResourceIntegrationTest.java
@@ -0,0 +1,766 @@
+/********************************************************************************

+ * 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.businessobjects.boundary.integrationtest;

+

+import static io.restassured.RestAssured.given;

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

+import static org.hamcrest.Matchers.equalTo;

+

+import java.util.Arrays;

+import java.util.Comparator;

+import java.util.EnumSet;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Map.Entry;

+import java.util.Optional;

+import java.util.stream.Collectors;

+import java.util.stream.IntStream;

+

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

+

+import org.assertj.core.api.SoftAssertions;

+import org.assertj.core.groups.Tuple;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.DataCase;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ExplicitData;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitConstant;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitLinear;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.ImplicitSaw;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawLinear;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawLinearCalibrated;

+import org.eclipse.mdm.protobuf.Mdm.WriteRequestList.WriteRequest.RawPolynomial;

+import org.junit.AfterClass;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+import com.google.common.primitives.Booleans;

+import com.google.common.primitives.Doubles;

+import com.google.common.primitives.Floats;

+import com.google.common.primitives.Ints;

+import com.google.common.primitives.Longs;

+import com.google.gson.JsonObject;

+import com.google.gson.JsonPrimitive;

+import com.google.protobuf.ByteString;

+import com.google.protobuf.InvalidProtocolBufferException;

+import com.google.protobuf.Timestamp;

+import com.google.protobuf.util.Timestamps;

+

+import io.restassured.http.ContentType;

+import io.restassured.response.ExtractableResponse;

+

+/**

+ * Test class for {@ChannelResource}.

+ * 

+ * @author Johannes Stamm, Peak Solution GmbH Nuernberg

+ * @see EntityResourceIntegrationTest

+ *

+ */

+public class ValuesResourceIntegrationTest extends ResourceIntegrationTest {

+

+	public static final EnumSet<ScalarType> scalarTypes = EnumSet.of(ScalarType.STRING, ScalarType.DATE,

+			ScalarType.BOOLEAN, ScalarType.BYTE, ScalarType.SHORT, ScalarType.INTEGER, ScalarType.LONG,

+			ScalarType.FLOAT, ScalarType.DOUBLE, ScalarType.BYTE_STREAM, ScalarType.FLOAT_COMPLEX,

+			ScalarType.DOUBLE_COMPLEX);

+

+	private static final List<Boolean> SCALARTYPE_BOOLEAN = Booleans.asList(true, false, true);

+	private static final ByteString SCALARTYPE_BYTES = ByteString

+			.copyFrom(new byte[] { (byte) 0x01, (byte) 0xFF, (byte) 0xF1 });

+	private static final List<Integer> SCALARTYPE_SHORTS = Ints.asList(12, 13, 14);

+	private static final List<Integer> SCALARTYPE_INTEGER = Ints.asList(12, 13, 14);

+	private static final List<Long> SCALARTYPE_LONG = Longs.asList(12L, 13L, 14L);

+	private static final List<Float> SCALARTYPE_FLOAT = Floats.asList(12f, 13f, 14f);

+	private static final List<Double> SCALARTYPE_DOUBLE = Doubles.asList(12.0, 13.0, 14.0);

+	private static final List<FloatComplex> SCALARTYPE_FLOAT_COMPLEX = Arrays.asList(

+			FloatComplex.newBuilder().setRe(12f).setIm(11f).build(),

+			FloatComplex.newBuilder().setRe(13f).setIm(10f).build(),

+			FloatComplex.newBuilder().setRe(14f).setIm(9f).build());

+	private static final List<DoubleComplex> SCALARTYPE_DOUBLE_COMPLEX = Arrays.asList(

+			DoubleComplex.newBuilder().setRe(12.0).setIm(11.0).build(),

+			DoubleComplex.newBuilder().setRe(13.0).setIm(10.0).build(),

+			DoubleComplex.newBuilder().setRe(14.0).setIm(9.0).build());

+	private static final List<ByteString> SCALARTYPE_BYTE_STREAM = Arrays.asList(

+			ByteString.copyFrom(new byte[] { (byte) 12, (byte) 11 }),

+			ByteString.copyFrom(new byte[] { (byte) 13, (byte) 10 }),

+			ByteString.copyFrom(new byte[] { (byte) 14, (byte) 9 }));

+	private static final List<String> SCALARTYPE_STRING = Arrays.asList("one", "two", "three");

+	private static final List<Timestamp> SCALARTYPE_DATE = Arrays.asList(Timestamps.fromMillis(1546300800000L),

+			Timestamps.fromMillis(1546400800000L), Timestamps.fromMillis(1546500800000L));

+	private static final List<Boolean> SCALARTYPE_FLAGS = Booleans.asList(true, true, true);

+

+	private static final String MIMETYPE_APPLICATION_PROTOBUF = "application/protobuf";

+

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

+

+	private static final int LENGTH = 5;

+

+	private static final List<Integer> INTS = IntStream.range(0, LENGTH)

+			.map(i -> (int) (10 * Math.sin((double) i / LENGTH * Math.PI))).boxed().collect(Collectors.toList());

+

+	private static final List<Double> DOUBLES = IntStream.range(0, LENGTH)

+			.mapToDouble(i -> 10 * Math.sin((double) i / LENGTH * Math.PI)).boxed().collect(Collectors.toList());

+

+	private static final ExplicitData INT_DATA = ExplicitData.newBuilder()

+			.setIntegerArray(IntegerArray.newBuilder().addAllValues(INTS)).build();

+

+	private static final ExplicitData DOUBLE_DATA = ExplicitData.newBuilder()

+			.setDoubleArray(DoubleArray.newBuilder().addAllValues(DOUBLES)).build();

+

+	private static List<Boolean> FLAGS;

+

+	@BeforeClass

+	public static void prepareTestData() {

+		LOGGER.debug("Preparing ValuesResourceIntegrationTest");

+

+		boolean[] flagsArray = new boolean[LENGTH];

+		Arrays.fill(flagsArray, true);

+		FLAGS = Booleans.asList(flagsArray);

+

+		// prepare test data for creating the parent Measurement

+		MeasurementResourceIntegrationTest.prepareTestData();

+		MeasurementResourceIntegrationTest.createEntity();

+		// prepare test data for creating Quantity

+		QuantityResourceIntegrationTest.prepareTestData();

+		QuantityResourceIntegrationTest.createEntity();

+	}

+

+	@Test

+	public void testAllDataSeqReps() throws InvalidProtocolBufferException {

+

+		String channelGroupId = createChannelGroup();

+

+		Map<String, DataCase> dataCaseChannelIds = createChannelsForDataCase();

+

+		writeValuesAllSequenceRepresentations(channelGroupId, dataCaseChannelIds);

+

+		// read MeasuredValues

+		Mdm.ReadRequest readRequest = Mdm.ReadRequest.newBuilder().setChannelGroupId(channelGroupId).build();

+

+		ExtractableResponse<io.restassured.response.Response> response = given()

+				.contentType(MIMETYPE_APPLICATION_PROTOBUF).body(readRequest.toByteArray()).post("/values/read").then()

+				.log().ifError().contentType(MIMETYPE_APPLICATION_PROTOBUF).statusCode(200).extract();

+

+		Mdm.MeasuredValuesList mvl = Mdm.MeasuredValuesList.parseFrom(response.asByteArray());

+

+		SoftAssertions softly = new SoftAssertions();

+		assertExplicit(softly, mvl);

+		assertImplicitConstant(softly, mvl);

+		assertImplicitLinear(softly, mvl);

+		assertImplicitSaw(softly, mvl);

+		assertRawLinear(softly, mvl);

+		assertRawPolynomial(softly, mvl);

+		assertRawLinearCalibrated(softly, mvl);

+		softly.assertAll();

+

+		// read preview values

+		int chunkSize = 2;

+

+		Mdm.PreviewRequest previewRequest = Mdm.PreviewRequest.newBuilder()

+				.setReadRequest(Mdm.ReadRequest.newBuilder().setChannelGroupId(channelGroupId))

+				.setNumberOfChunks(chunkSize).build();

+

+		ExtractableResponse<io.restassured.response.Response> previewResponse = given()

+				.contentType(MIMETYPE_APPLICATION_PROTOBUF).body(previewRequest.toByteArray()).post("/values/preview")

+				.then().log().ifError().contentType(MIMETYPE_APPLICATION_PROTOBUF).statusCode(200).extract();

+

+		Mdm.PreviewValuesList pvl = Mdm.PreviewValuesList.parseFrom(previewResponse.asByteArray());

+

+		List<String> channelNames = Arrays.asList("Channel_EXPLICIT", "Channel_IMPLICIT_CONSTANT",

+				"Channel_IMPLICIT_LINEAR", "Channel_IMPLICIT_SAW", "Channel_RAW_LINEAR",

+				"Channel_RAW_LINEAR_CALIBRATED", "Channel_RAW_POLYNOMIAL");

+

+		SoftAssertions softly2 = new SoftAssertions();

+		softly2.assertThat(pvl.getAvgList()).extracting(MeasuredValues::getName).containsAll(channelNames);

+		softly2.assertThat(pvl.getMinList()).extracting(MeasuredValues::getName).containsAll(channelNames);

+		softly2.assertThat(pvl.getMaxList()).extracting(MeasuredValues::getName).containsAll(channelNames);

+

+		softly2.assertThat(pvl.getAvgList()).extracting(mv -> mv.getDoubleArray().getValuesCount())

+				.containsOnly(chunkSize);

+		softly2.assertThat(pvl.getMinList()).extracting(mv -> mv.getDoubleArray().getValuesCount())

+				.containsOnly(chunkSize);

+		softly2.assertThat(pvl.getMaxList()).extracting(mv -> mv.getDoubleArray().getValuesCount())

+				.containsOnly(chunkSize);

+		softly2.assertAll();

+	}

+

+	@Test

+	public void testAllScalarTypes() throws InvalidProtocolBufferException {

+

+		String channelGroupId = createChannelGroup();

+

+		Map<String, ScalarType> scalarTypeChannelIds = createChannelsForScalarTypes();

+

+		writeValuesAllScalarTypes(channelGroupId, scalarTypeChannelIds);

+

+		// read MeasuredValues

+		Mdm.ReadRequest readRequest = Mdm.ReadRequest.newBuilder().setChannelGroupId(channelGroupId).build();

+

+		ExtractableResponse<io.restassured.response.Response> response = given()

+				.contentType(MIMETYPE_APPLICATION_PROTOBUF).body(readRequest.toByteArray()).post("/values/read").then()

+				.log().ifError().contentType(MIMETYPE_APPLICATION_PROTOBUF).statusCode(200).extract();

+

+		Mdm.MeasuredValuesList mvl = Mdm.MeasuredValuesList.parseFrom(response.asByteArray());

+

+		SoftAssertions softly = new SoftAssertions();

+		assertBoolean(softly, mvl);

+		assertByte(softly, mvl);

+		assertShort(softly, mvl);

+		assertInteger(softly, mvl);

+		assertLong(softly, mvl);

+		assertFloat(softly, mvl);

+		assertDouble(softly, mvl);

+		assertFloatComplex(softly, mvl);

+		assertDoubleComplex(softly, mvl);

+		assertByteStream(softly, mvl);

+		assertString(softly, mvl);

+		assertDate(softly, mvl);

+		softly.assertAll();

+

+		// read preview values

+		int chunkSize = 2;

+

+		Mdm.PreviewRequest previewRequest = Mdm.PreviewRequest.newBuilder()

+				.setReadRequest(Mdm.ReadRequest.newBuilder().setChannelGroupId(channelGroupId))

+				.setNumberOfChunks(chunkSize).build();

+

+		ExtractableResponse<io.restassured.response.Response> previewResponse = given()

+				.contentType(MIMETYPE_APPLICATION_PROTOBUF).body(previewRequest.toByteArray()).post("/values/preview")

+				.then().log().ifError().contentType(MIMETYPE_APPLICATION_PROTOBUF).statusCode(200).extract();

+

+		Mdm.PreviewValuesList pvl = Mdm.PreviewValuesList.parseFrom(previewResponse.asByteArray());

+

+		Tuple[] previewChannelNames = new Tuple[] { tuple("Channel_BYTE", 2), tuple("Channel_SHORT", 2),

+				tuple("Channel_INTEGER", 2), tuple("Channel_LONG", 2), tuple("Channel_FLOAT", 2),

+				tuple("Channel_DOUBLE", 2), tuple("Channel_FLOAT_COMPLEX", 0), tuple("Channel_DOUBLE_COMPLEX", 0),

+				tuple("Channel_BOOLEAN", 0), tuple("Channel_STRING", 0), tuple("Channel_BYTE_STREAM", 0),

+				tuple("Channel_DATE", 0) };

+

+		SoftAssertions softly2 = new SoftAssertions();

+		softly2.assertThat(pvl.getAvgList()).extracting(mv -> tuple(mv.getName(), mv.getDoubleArray().getValuesCount()))

+				.containsOnly(previewChannelNames);

+		softly2.assertThat(pvl.getMinList()).extracting(mv -> tuple(mv.getName(), mv.getDoubleArray().getValuesCount()))

+				.containsOnly(previewChannelNames);

+		softly2.assertThat(pvl.getMaxList()).extracting(mv -> tuple(mv.getName(), mv.getDoubleArray().getValuesCount()))

+				.containsOnly(previewChannelNames);

+

+		// Date will return a DateArray instead of a DoubleArray

+		softly2.assertThat(pvl.getAvgList()).extracting(mv -> tuple(mv.getName(), mv.getDateArray().getValuesCount()))

+				.contains(tuple("Channel_DATE", 2));

+		softly2.assertThat(pvl.getMinList()).extracting(mv -> tuple(mv.getName(), mv.getDateArray().getValuesCount()))

+				.contains(tuple("Channel_DATE", 2));

+		softly2.assertThat(pvl.getMaxList()).extracting(mv -> tuple(mv.getName(), mv.getDateArray().getValuesCount()))

+				.contains(tuple("Channel_DATE", 2));

+

+		softly2.assertAll();

+	}

+

+	private void writeValuesAllSequenceRepresentations(String channelGroupId, Map<String, DataCase> channelIds) {

+		// write MeasuredValues

+		WriteRequestList.Builder writeRequestList = WriteRequestList.newBuilder();

+

+		for (Map.Entry<String, DataCase> entry : channelIds.entrySet()) {

+			WriteRequest.Builder builder = WriteRequest.newBuilder().setChannelGroupId(channelGroupId)

+					.setChannelId(entry.getKey()).setAxisTypeValue(AxisType.XY_AXIS_VALUE).setIndependent(false);

+			switch (entry.getValue()) {

+			case EXPLICIT:

+				builder.setExplicit(INT_DATA);

+				break;

+			case IMPLICIT_CONSTANT:

+				builder.setImplicitConstant(

+						ImplicitConstant.newBuilder().setScalarType(ScalarType.INTEGER).setOffset(12));

+				break;

+			case IMPLICIT_LINEAR:

+				builder.setImplicitLinear(

+						ImplicitLinear.newBuilder().setScalarType(ScalarType.INTEGER).setStart(1).setIncrement(1));

+				break;

+			case IMPLICIT_SAW:

+				builder.setImplicitSaw(ImplicitSaw.newBuilder().setScalarType(ScalarType.INTEGER).setStart(1)

+						.setIncrement(1).setStop(5));

+				break;

+			case RAW_LINEAR:

+				builder.setRawLinear(RawLinear.newBuilder().setScalarType(ScalarType.DOUBLE).setFactor(2).setOffset(10)

+						.setValues(DOUBLE_DATA));

+				break;

+			case RAW_LINEAR_CALIBRATED:

+				builder.setRawLinearCalibrated(RawLinearCalibrated.newBuilder().setScalarType(ScalarType.DOUBLE)

+						.setOffset(1).setFactor(2).setCalibration(3).setValues(DOUBLE_DATA));

+				break;

+			case RAW_POLYNOMIAL:

+				builder.setRawPolynomial(RawPolynomial.newBuilder().setScalarType(ScalarType.DOUBLE).addCoefficients(1)

+						.addCoefficients(2).addCoefficients(3).setValues(DOUBLE_DATA));

+				break;

+			default:

+				break;

+			}

+

+			writeRequestList.addValues(builder);

+		}

+

+		given().contentType(MIMETYPE_APPLICATION_PROTOBUF).body(writeRequestList.build().toByteArray())

+				.post("/values/write").then().log().ifError().assertThat()

+				.statusCode(Status.NO_CONTENT.getStatusCode());

+	}

+

+	private void writeValuesAllScalarTypes(String channelGroupId, Map<String, ScalarType> channelIds) {

+		WriteRequestList.Builder writeRequestList = WriteRequestList.newBuilder();

+

+		for (Entry<String, ScalarType> entry : channelIds.entrySet()) {

+			WriteRequest.Builder builder = WriteRequest.newBuilder().setChannelGroupId(channelGroupId)

+					.setChannelId(entry.getKey()).setAxisTypeValue(AxisType.XY_AXIS_VALUE).setIndependent(false);

+			switch (entry.getValue()) {

+			case STRING:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setStringArray(StringArray.newBuilder().addAllValues(SCALARTYPE_STRING)));

+				break;

+			case DATE:

+				builder = builder.setExplicit(

+						ExplicitData.newBuilder().setDateArray(DateArray.newBuilder().addAllValues(SCALARTYPE_DATE)));

+				break;

+			case BOOLEAN:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setBooleanArray(BooleanArray.newBuilder().addAllValues(SCALARTYPE_BOOLEAN)));

+				break;

+			case BYTE:

+				builder = builder.setExplicit(

+						ExplicitData.newBuilder().setByteArray(ByteArray.newBuilder().setValues(SCALARTYPE_BYTES)));

+				break;

+			case SHORT:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setShortArray(ShortArray.newBuilder().addAllValues(SCALARTYPE_SHORTS)));

+				break;

+			case INTEGER:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setIntegerArray(IntegerArray.newBuilder().addAllValues(SCALARTYPE_INTEGER)));

+				break;

+			case LONG:

+				builder = builder.setExplicit(

+						ExplicitData.newBuilder().setLongArray(LongArray.newBuilder().addAllValues(SCALARTYPE_LONG)));

+				break;

+			case FLOAT:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setFloatArray(FloatArray.newBuilder().addAllValues(SCALARTYPE_FLOAT)));

+				break;

+			case DOUBLE:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setDoubleArray(DoubleArray.newBuilder().addAllValues(SCALARTYPE_DOUBLE)));

+				break;

+			case BYTE_STREAM:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setByteStreamArray(ByteStreamArray.newBuilder().addAllValues(SCALARTYPE_BYTE_STREAM)));

+				break;

+			case FLOAT_COMPLEX:

+				builder = builder.setExplicit(ExplicitData.newBuilder()

+						.setFloatComplexArray(FloatComplexArray.newBuilder().addAllValues(SCALARTYPE_FLOAT_COMPLEX)));

+				break;

+			case DOUBLE_COMPLEX:

+				builder = builder.setExplicit(ExplicitData.newBuilder().setDoubleComplexArray(

+						DoubleComplexArray.newBuilder().addAllValues(SCALARTYPE_DOUBLE_COMPLEX)));

+				break;

+			default:

+				throw new RuntimeException("The ScalarType " + entry.getValue() + " is not supported!");

+			}

+

+			writeRequestList.addValues(builder);

+		}

+

+		given().contentType(MIMETYPE_APPLICATION_PROTOBUF).body(writeRequestList.build().toByteArray())

+				.post("/values/write").then().log().ifError().assertThat()

+				.statusCode(Status.NO_CONTENT.getStatusCode());

+	}

+

+	private void assertBoolean(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_BOOLEAN".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_BOOLEAN")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_BOOLEAN.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.BOOLEAN)

+						.setBooleanArray(BooleanArray.newBuilder().addAllValues(SCALARTYPE_BOOLEAN))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+

+	}

+

+	private void assertByte(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_BYTE".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_BYTE")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+				.setLength(SCALARTYPE_BYTES.size()).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.BYTE)

+				.setByteArray(ByteArray.newBuilder().setValues(SCALARTYPE_BYTES)).addAllFlags(SCALARTYPE_FLAGS)

+				.build());

+

+	}

+

+	private void assertShort(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_SHORT".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_SHORT")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_SHORTS.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.SHORT)

+						.setShortArray(ShortArray.newBuilder().addAllValues(SCALARTYPE_SHORTS))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertInteger(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_INTEGER".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_INTEGER")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_INTEGER.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.INTEGER)

+						.setIntegerArray(IntegerArray.newBuilder().addAllValues(SCALARTYPE_INTEGER))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertLong(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_LONG".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_LONG")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+				.setLength(SCALARTYPE_LONG.size()).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.LONG)

+				.setLongArray(LongArray.newBuilder().addAllValues(SCALARTYPE_LONG)).addAllFlags(SCALARTYPE_FLAGS)

+				.build());

+	}

+

+	private void assertFloat(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_FLOAT".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_FLOAT")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_FLOAT.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.FLOAT)

+						.setFloatArray(FloatArray.newBuilder().addAllValues(SCALARTYPE_FLOAT))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertDouble(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_DOUBLE".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_DOUBLE")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_DOUBLE.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.DOUBLE)

+						.setDoubleArray(DoubleArray.newBuilder().addAllValues(SCALARTYPE_DOUBLE))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertFloatComplex(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_FLOAT_COMPLEX".equals(m.getName())).findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_FLOAT_COMPLEX")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_FLOAT.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.FLOAT_COMPLEX)

+						.setFloatComplexArray(FloatComplexArray.newBuilder().addAllValues(SCALARTYPE_FLOAT_COMPLEX))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertDoubleComplex(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_DOUBLE_COMPLEX".equals(m.getName())).findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_DOUBLE_COMPLEX")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_DOUBLE.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.DOUBLE_COMPLEX)

+						.setDoubleComplexArray(DoubleComplexArray.newBuilder().addAllValues(SCALARTYPE_DOUBLE_COMPLEX))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertByteStream(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_BYTE_STREAM".equals(m.getName())).findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_BYTE_STREAM")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_DOUBLE.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.BYTE_STREAM)

+						.setByteStreamArray(ByteStreamArray.newBuilder().addAllValues(SCALARTYPE_BYTE_STREAM))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertString(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_STRING".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_STRING")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(SCALARTYPE_STRING.size()).setAxisType(Mdm.AxisType.XY_AXIS)

+						.setScalarType(Mdm.ScalarType.STRING)

+						.setStringArray(StringArray.newBuilder().addAllValues(SCALARTYPE_STRING))

+						.addAllFlags(SCALARTYPE_FLAGS).build());

+	}

+

+	private void assertDate(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_DATE".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_DATE")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+				.setLength(SCALARTYPE_DATE.size()).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.DATE)

+				.setDateArray(DateArray.newBuilder().addAllValues(SCALARTYPE_DATE)).addAllFlags(SCALARTYPE_FLAGS)

+				.build());

+	}

+

+	private void assertExplicit(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_EXPLICIT".equals(m.getName()))

+				.findFirst();

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_EXPLICIT")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(LENGTH).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.INTEGER)

+						.setIntegerArray(IntegerArray.newBuilder().addAllValues(INTS)).addAllFlags(FLAGS).build());

+

+	}

+

+	private void assertImplicitConstant(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_IMPLICIT_CONSTANT".equals(m.getName())).findFirst();

+

+		List<Integer> constantValues = IntStream.range(0, LENGTH).map(i -> 12).boxed().collect(Collectors.toList());

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_IMPLICIT_CONSTANT")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME)).setLength(LENGTH)

+				.setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.INTEGER)

+				.setIntegerArray(IntegerArray.newBuilder().addAllValues(constantValues)).addAllFlags(FLAGS).build());

+

+	}

+

+	private void assertImplicitLinear(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_IMPLICIT_LINEAR".equals(m.getName())).findFirst();

+

+		List<Integer> linearValues = IntStream.range(0, LENGTH).map(i -> i + 1).boxed().collect(Collectors.toList());

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_IMPLICIT_LINEAR")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME)).setLength(LENGTH)

+				.setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.INTEGER)

+				.setIntegerArray(IntegerArray.newBuilder().addAllValues(linearValues)).addAllFlags(FLAGS).build());

+

+	}

+

+	private void assertImplicitSaw(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_IMPLICIT_SAW".equals(m.getName())).findFirst();

+

+		List<Integer> sawValues = IntStream.range(0, LENGTH).map(i -> i % 4 + 1).boxed().collect(Collectors.toList());

+

+		softly.assertThat(mv)

+				.contains(Mdm.MeasuredValues.newBuilder().setName("Channel_IMPLICIT_SAW")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(LENGTH).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.INTEGER)

+						.setIntegerArray(IntegerArray.newBuilder().addAllValues(sawValues)).addAllFlags(FLAGS).build());

+

+	}

+

+	private void assertRawLinear(SoftAssertions softly, MeasuredValuesList mvl) throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream().filter(m -> "Channel_RAW_LINEAR".equals(m.getName()))

+				.findFirst();

+

+		List<Double> rawLinearValues = DOUBLES.stream().mapToDouble(d -> 2 * d + 10).boxed()

+				.collect(Collectors.toList());

+

+		softly.assertThat(mv).contains(Mdm.MeasuredValues.newBuilder().setName("Channel_RAW_LINEAR")

+				.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME)).setLength(LENGTH)

+				.setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.DOUBLE)

+				.setDoubleArray(DoubleArray.newBuilder().addAllValues(rawLinearValues)).addAllFlags(FLAGS).build());

+	}

+

+	private void assertRawLinearCalibrated(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_RAW_LINEAR_CALIBRATED".equals(m.getName())).findFirst();

+

+		List<Double> rawLinCalValues = DOUBLES.stream().mapToDouble(d -> (1 + 2 * d) * 3).boxed()

+				.collect(Collectors.toList());

+

+		softly.assertThat(mv.get())

+				.isEqualToIgnoringGivenFields(Mdm.MeasuredValues.newBuilder().setName("Channel_RAW_LINEAR_CALIBRATED")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(LENGTH).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.DOUBLE)

+						.setDoubleArray(DoubleArray.newBuilder().addAllValues(rawLinCalValues)).addAllFlags(FLAGS)

+						.build(), "values", "values_", "memoizedIsInitialized");

+

+		softly.assertThat(mv.get().getDoubleArray().getValuesList()).usingElementComparator(DOUBLE_COMP)

+				.isEqualTo(rawLinCalValues);

+	}

+

+	private void assertRawPolynomial(SoftAssertions softly, MeasuredValuesList mvl)

+			throws InvalidProtocolBufferException {

+

+		Optional<MeasuredValues> mv = mvl.getValuesList().stream()

+				.filter(m -> "Channel_RAW_POLYNOMIAL".equals(m.getName())).findFirst();

+

+		List<Double> rawPolynomialValues = DOUBLES.stream().mapToDouble(d -> 1 + 2 * d + 3 * d * d).boxed()

+				.collect(Collectors.toList());

+

+		softly.assertThat(mv.get())

+				.isEqualToIgnoringGivenFields(Mdm.MeasuredValues.newBuilder().setName("Channel_RAW_POLYNOMIAL")

+						.setUnit(getTestDataValue(UnitResourceIntegrationTest.class, TESTDATA_ENTITY_NAME))

+						.setLength(LENGTH).setAxisType(Mdm.AxisType.XY_AXIS).setScalarType(Mdm.ScalarType.DOUBLE)

+						.setDoubleArray(DoubleArray.newBuilder().addAllValues(rawPolynomialValues)).addAllFlags(FLAGS)

+						.build(), "values", "values_", "memoizedIsInitialized");

+

+		softly.assertThat(mv.get().getDoubleArray().getValuesList()).usingElementComparator(DOUBLE_COMP)

+				.isEqualTo(rawPolynomialValues);

+	}

+

+	private static Map<String, DataCase> createChannelsForDataCase() {

+		// Create channels

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

+		for (DataCase dataCase : WriteRequest.DataCase.values()) {

+			if (dataCase == DataCase.DATA_NOT_SET) {

+				continue;

+			}

+			JsonObject json = new JsonObject();

+			json.add(ResourceConstants.ENTITYATTRIBUTE_NAME, new JsonPrimitive("Channel_" + dataCase.name()));

+			json.add("Measurement",

+					new JsonPrimitive(getTestDataValue(MeasurementResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+			json.add("Quantity",

+					new JsonPrimitive(getTestDataValue(QuantityResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+

+			ExtractableResponse<io.restassured.response.Response> response = given().contentType(ContentType.JSON)

+					.body(json.toString()).post("/channels").then().log().ifError().contentType(ContentType.JSON)

+					.extract();

+

+			LOGGER.debug("Channel created " + response.asString());

+			channelIds.put(response.path("data.first().id"), dataCase);

+		}

+		return channelIds;

+	}

+

+	private static Map<String, ScalarType> createChannelsForScalarTypes() {

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

+		for (ScalarType scalarType : scalarTypes) {

+			JsonObject json = new JsonObject();

+			json.add(ResourceConstants.ENTITYATTRIBUTE_NAME, new JsonPrimitive("Channel_" + scalarType.name()));

+			json.add("Measurement",

+					new JsonPrimitive(getTestDataValue(MeasurementResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+			json.add("Quantity",

+					new JsonPrimitive(getTestDataValue(QuantityResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+

+			ExtractableResponse<io.restassured.response.Response> response = given().contentType(ContentType.JSON)

+					.body(json.toString()).post("/channels").then().log().ifError().contentType(ContentType.JSON)

+					.extract();

+

+			LOGGER.debug("Channel created " + response.asString());

+			channelIds.put(response.path("data.first().id"), scalarType);

+		}

+		return channelIds;

+	}

+

+	private static String createChannelGroup() {

+		// Create channelGroup

+		JsonObject json = new JsonObject();

+		json.add(ResourceConstants.ENTITYATTRIBUTE_NAME, new JsonPrimitive("ChannelGroup1"));

+		json.add(TESTDATA_NUMBER_OF_VALUES, new JsonPrimitive(LENGTH));

+		json.add("Measurement",

+				new JsonPrimitive(getTestDataValue(MeasurementResourceIntegrationTest.class, TESTDATA_ENTITY_ID)));

+

+		ExtractableResponse<io.restassured.response.Response> response = given().contentType(ContentType.JSON)

+				.body(json.toString()).post("/channelgroups").then().log().ifError().contentType(ContentType.JSON)

+				// do not check for name equality as that might be created randomly

+				.and().body("data.first().type", equalTo("ChannelGroup")).extract();

+

+		LOGGER.debug("ChannelGroup created " + response.asString());

+

+		return response.path("data.first().id");

+	}

+

+	private static Comparator<Double> DOUBLE_COMP = new Comparator<Double>() {

+

+		private double precision = 1E-6;

+

+		@Override

+		public int compare(Double o1, Double o2) {

+			double diff = Math.abs(o1.doubleValue() - o2.doubleValue());

+			if (diff <= precision)

+				return 0;

+			return diff < 0.0 ? -1 : 1;

+		}

+

+	};

+

+	@AfterClass

+	public static void tearDownAfterClass() {

+		// call tearDownAfterClass() of parent entity

+		QuantityResourceIntegrationTest.tearDownAfterClass();

+		MeasurementResourceIntegrationTest.tearDownAfterClass();

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/service/EntityServiceTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/service/EntityServiceTest.java
index c43f951..02c0b27 100644
--- a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/service/EntityServiceTest.java
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/service/EntityServiceTest.java
@@ -22,30 +22,68 @@
 import org.eclipse.mdm.api.base.model.ContextRoot;
 import org.junit.Test;
 
+import io.vavr.collection.List;
+
 public class EntityServiceTest {
 
 	@Test
 	public void testSatisfiesParameters() throws Exception {
 		EntityService entityService = new EntityService();
 
-		assertThat(entityService.satisfiesParameters(new Class[] { java.util.List.class, ContextRoot.class },
-				io.vavr.collection.List.of(ArrayList.class, ContextRoot.class))).isTrue();
+		Class<?>[] parameter = new Class[] { java.util.List.class, ContextRoot.class };
+		List<Class<?>> required = List.of(ArrayList.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isTrue();
 	}
 
 	@Test
 	public void testSatisfiesParametersNotSubClassSuperClass() throws Exception {
 		EntityService entityService = new EntityService();
 
-		assertThat(entityService.satisfiesParameters(new Class[] { ArrayList.class, ContextRoot.class },
-				io.vavr.collection.List.of(java.util.List.class, ContextRoot.class))).isFalse();
+		Class<?>[] parameter = new Class[] { ArrayList.class, ContextRoot.class };
+		List<Class<?>> required = List.of(java.util.List.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isFalse();
 	}
 
 	@Test
 	public void testSatisfiesParametersNot() throws Exception {
 		EntityService entityService = new EntityService();
 
-		assertThat(entityService.satisfiesParameters(new Class[] { HashMap.class, ContextRoot.class },
-				io.vavr.collection.List.of(java.util.List.class, ContextRoot.class))).isFalse();
+		Class<?>[] parameter = new Class[] { HashMap.class, ContextRoot.class };
+		List<Class<? extends Object>> required = List.of(java.util.List.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isFalse();
+	}
+
+	@Test
+	public void testSatisfiesParametersAutoBoxing() throws Exception {
+		EntityService entityService = new EntityService();
+
+		Class<?>[] parameter = new Class[] { boolean.class, ContextRoot.class };
+		List<Class<?>> required = List.of(Boolean.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isTrue();
+	}
+
+	@Test
+	public void testSatisfiesParametersAutoUnboxing() throws Exception {
+		EntityService entityService = new EntityService();
+
+		Class<?>[] parameter = new Class[] { Boolean.class, ContextRoot.class };
+		List<Class<?>> required = List.of(boolean.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isTrue();
+	}
+
+	@Test
+	public void testSatisfiesParametersNotLength() throws Exception {
+		EntityService entityService = new EntityService();
+
+		Class<?>[] parameter = new Class[] { Boolean.class, ContextRoot.class, boolean.class };
+		List<Class<?>> required = List.of(boolean.class, ContextRoot.class);
+
+		assertThat(entityService.satisfiesParameters(parameter, required)).isFalse();
 	}
 
 }
diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/PreviewHelperTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/PreviewHelperTest.java
new file mode 100644
index 0000000..3ecbc24
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/PreviewHelperTest.java
@@ -0,0 +1,274 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

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

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

+

+import java.time.Instant;

+import java.time.LocalDateTime;

+import java.time.ZoneOffset;

+import java.util.Arrays;

+import java.util.List;

+

+import org.assertj.core.util.DoubleComparator;

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

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

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

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

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

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

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

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

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

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

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

+import org.junit.Test;

+

+import com.google.common.primitives.Doubles;

+import com.google.common.primitives.Floats;

+import com.google.common.primitives.Ints;

+import com.google.common.primitives.Longs;

+import com.google.protobuf.ByteString;

+import com.google.protobuf.Timestamp;

+

+public class PreviewHelperTest {

+

+	private static final DoubleComparator COMP = new DoubleComparator(1.0E-6);

+

+	private PreviewHelper helper = new PreviewHelper();

+

+	private final List<String> stringValues = Arrays.asList("one", "two", "three", "four", "five", "six");

+	private final MeasuredValues mv6Strings = MeasuredValues.newBuilder()

+			.setStringArray(StringArray.newBuilder().addAllValues(stringValues)).setLength(stringValues.size()).build();

+

+	private final ByteString byteValues = ByteString

+			.copyFrom(new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 1, (byte) 4, (byte) 2 });

+	private final MeasuredValues mv6Bytes = MeasuredValues.newBuilder()

+			.setByteArray(ByteArray.newBuilder().setValues(byteValues)).setLength(byteValues.size()).build();

+

+	private final List<Integer> shortValues = Ints.asList(1, 2, 3, 1, 4, 2);

+	private final MeasuredValues mv6Shorts = MeasuredValues.newBuilder()

+			.setShortArray(ShortArray.newBuilder().addAllValues(shortValues)).setLength(shortValues.size()).build();

+

+	private final List<Integer> intValues = Ints.asList(1, 2, 3, 1, 4, 2);

+	private final MeasuredValues mv6Ints = MeasuredValues.newBuilder()

+			.setIntegerArray(IntegerArray.newBuilder().addAllValues(intValues)).setLength(intValues.size()).build();

+

+	private final List<Long> longValues = Longs.asList(1L, 2L, 3L, 1L, 4L, 2L);

+	private final MeasuredValues mv6Longs = MeasuredValues.newBuilder()

+			.setLongArray(LongArray.newBuilder().addAllValues(longValues)).setLength(longValues.size()).build();

+

+	private final List<Float> floatValues = Floats.asList(1.0f, 2.0f, 3.0f, 1.1f, 4.4f, 2.2f);

+	private final MeasuredValues mv6Floats = MeasuredValues.newBuilder()

+			.setFloatArray(FloatArray.newBuilder().addAllValues(floatValues)).setLength(floatValues.size()).build();

+

+	private final List<Double> doubleValues = Doubles.asList(1.0, 2.0, 3.0, 1.1, 4.4, 2.2);

+	private final MeasuredValues mv6Doubles = MeasuredValues.newBuilder()

+			.setDoubleArray(DoubleArray.newBuilder().addAllValues(doubleValues)).setLength(doubleValues.size()).build();

+

+	@Test

+	public void testInvalidNumberOfChunks() {

+

+		assertThatThrownBy(() -> helper.calculatePreview(mv6Doubles, 0)).isInstanceOf(PreviewException.class)

+				.hasMessage("Number of chunks requested must be positive!");

+

+		assertThatThrownBy(() -> helper.calculatePreview(mv6Doubles, -1)).isInstanceOf(PreviewException.class)

+				.hasMessage("Number of chunks requested must be positive!");

+	}

+

+	@Test

+	public void testChunkSizeInteger() {

+		PreviewValuesList pv = helper.calculatePreview(mv6Doubles, 2).build();

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.56666666666);

+	}

+

+	@Test

+	public void testChunkSizeFloat() {

+		PreviewValuesList pv = helper.calculatePreview(mv6Doubles, 4).build();

+

+		assertThat(pv.getMinCount()).isEqualTo(1);

+		assertThat(pv.getMaxCount()).isEqualTo(1);

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0, 3.0,

+				1.1, 2.2);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0, 3.0,

+				4.4, 2.2);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.5, 3.0,

+				2.75, 2.2);

+	}

+

+	@Test

+	public void testChunkSizeFloat2() {

+		PreviewValuesList pv = helper.calculatePreview(mv6Doubles, 5).build();

+

+		assertThat(pv.getMinCount()).isEqualTo(1);

+		assertThat(pv.getMaxCount()).isEqualTo(1);

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0, 2.0,

+				1.1, 4.4, 2.2);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0, 2.0,

+				3.0, 4.4, 2.2);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0, 2.0,

+				2.05, 4.4, 2.2);

+	}

+

+	@Test

+	public void testNumberOfChunksEqualsNumberOfValues() {

+		PreviewValuesList pv = helper.calculatePreview(mv6Doubles, 6).build();

+

+		assertThat(pv.getMinCount()).isEqualTo(1);

+		assertThat(pv.getMaxCount()).isEqualTo(1);

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+	}

+

+	@Test

+	public void testNumberOfChunksLargerThanNumberOfValues() {

+		PreviewValuesList pv = helper.calculatePreview(mv6Doubles, 7).build();

+

+		assertThat(pv.getMinCount()).isEqualTo(1);

+		assertThat(pv.getMaxCount()).isEqualTo(1);

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).isEqualTo(doubleValues);

+	}

+

+	@Test

+	public void testMultipleChannels() {

+		PreviewValuesList pv = helper.calculatePreview(

+				Arrays.asList(mv6Bytes, mv6Shorts, mv6Ints, mv6Longs, mv6Floats, mv6Doubles, mv6Strings), 2);

+

+		assertThat(pv.getMinCount()).isEqualTo(7);

+		assertThat(pv.getMaxCount()).isEqualTo(7);

+		assertThat(pv.getAvgCount()).isEqualTo(7);

+

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.0);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.0);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.33333333333);

+

+		assertThat(pv.getMin(1).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.0);

+		assertThat(pv.getMax(1).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.0);

+		assertThat(pv.getAvg(1).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.33333333333);

+

+		assertThat(pv.getMin(2).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.0);

+		assertThat(pv.getMax(2).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.0);

+		assertThat(pv.getAvg(2).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.33333333333);

+

+		assertThat(pv.getMin(3).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.0);

+		assertThat(pv.getMax(3).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.0);

+		assertThat(pv.getAvg(3).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.33333333333);

+

+		assertThat(pv.getMin(4).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.1);

+		assertThat(pv.getMax(4).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.4);

+		assertThat(pv.getAvg(4).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.56666666666);

+

+		assertThat(pv.getMin(5).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(1.0,

+				1.1);

+		assertThat(pv.getMax(5).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				4.4);

+		assertThat(pv.getAvg(5).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				2.56666666666);

+

+		assertThat(pv.getMin(6)).isEqualTo(MeasuredValues.newBuilder().setScalarType(ScalarType.DOUBLE).build());

+		assertThat(pv.getMax(6)).isEqualTo(MeasuredValues.newBuilder().setScalarType(ScalarType.DOUBLE).build());

+		assertThat(pv.getAvg(6)).isEqualTo(MeasuredValues.newBuilder().setScalarType(ScalarType.DOUBLE).build());

+	}

+

+	@Test

+	public void testFlags() {

+		MeasuredValues mv6IntsWithFlags = mv6Ints.toBuilder()

+				.addAllFlags(Arrays.asList(false, true, true, true, false, false)).build();

+

+		// 1, 2, 3, 1, 4, 2

+		PreviewValuesList pv = helper.calculatePreview(mv6IntsWithFlags, 2).build();

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+		assertThat(pv.getAvg(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.5,

+				1.0);

+		assertThat(pv.getMin(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(2.0,

+				1.0);

+		assertThat(pv.getMax(0).getDoubleArray().getValuesList()).usingElementComparator(COMP).containsExactly(3.0,

+				1.0);

+	}

+

+	@Test

+	public void testTimestamps() {

+

+		final List<Timestamp> dateValues = Arrays.asList(toTimestamp("2019-02-01T06:30:00.500"),

+				toTimestamp("2019-02-02T06:30:00.500"), toTimestamp("2019-02-03T06:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"), toTimestamp("2019-04-01T07:40:00.200"),

+				toTimestamp("2019-04-01T09:10:00.300"));

+		final MeasuredValues mv6Dates = MeasuredValues.newBuilder()

+				.setDateArray(DateArray.newBuilder().addAllValues(dateValues)).setLength(dateValues.size()).build();

+

+		PreviewValuesList pv = helper.calculatePreview(mv6Dates, 2).build();

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+		assertThat(pv.getMin(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-01T06:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"));

+		assertThat(pv.getMax(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-03T06:30:00.500"),

+				toTimestamp("2019-04-01T09:10:00.300"));

+		assertThat(pv.getAvg(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-02T06:30:00.500"),

+				toTimestamp("2019-04-01T07:40:00.200"));

+	}

+

+	@Test

+	public void testTimestampFlags() {

+		final List<Timestamp> dateValues = Arrays.asList(toTimestamp("2019-02-01T06:30:00.500"),

+				toTimestamp("2019-02-02T06:30:00.500"), toTimestamp("2019-02-03T06:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"), toTimestamp("2019-04-01T07:40:00.200"),

+				toTimestamp("2019-04-01T09:10:00.300"));

+		final MeasuredValues mv6Dates = MeasuredValues.newBuilder()

+				.setDateArray(DateArray.newBuilder().addAllValues(dateValues)).setLength(dateValues.size())

+				.addAllFlags(Arrays.asList(false, true, true, true, false, false)).build();

+

+		PreviewValuesList pv = helper.calculatePreview(mv6Dates, 2).build();

+		assertThat(pv.getAvgCount()).isEqualTo(1);

+		assertThat(pv.getMin(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-02T06:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"));

+		assertThat(pv.getMax(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-03T06:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"));

+		assertThat(pv.getAvg(0).getDateArray().getValuesList()).containsExactly(toTimestamp("2019-02-02T18:30:00.500"),

+				toTimestamp("2019-04-01T06:10:00.100"));

+	}

+

+	private Timestamp toTimestamp(String datetime) {

+		Instant instant = LocalDateTime.parse(datetime).toInstant(ZoneOffset.UTC);

+

+		return Timestamp.newBuilder().setSeconds(instant.getEpochSecond()).setNanos(instant.getNano()).build();

+	}

+}

diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverterTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverterTest.java
new file mode 100644
index 0000000..7e7777d
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ProtobufConverterTest.java
@@ -0,0 +1,152 @@
+/********************************************************************************

+ * 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.businessobjects.utils;

+

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

+import static org.mockito.ArgumentMatchers.anyList;

+import static org.mockito.ArgumentMatchers.anyString;

+import static org.mockito.ArgumentMatchers.eq;

+import static org.mockito.Mockito.mock;

+import static org.mockito.Mockito.when;

+

+import java.util.Arrays;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Optional;

+

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

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

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

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

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

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

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

+import org.junit.Before;

+import org.junit.Test;

+

+public class ProtobufConverterTest {

+

+	ChannelGroup channelGroup = mock(ChannelGroup.class);

+	Channel channel1 = mock(Channel.class);

+	Channel channel2 = mock(Channel.class);

+	Unit unit1 = mock(Unit.class);

+	Unit unit2 = mock(Unit.class);

+	EntityManager em = mock(EntityManager.class);

+	ApplicationContext context = mock(ApplicationContext.class);

+

+	@Before

+	public void init() {

+		when(channelGroup.getID()).thenReturn("1");

+		when(unit1.getID()).thenReturn("1");

+		when(unit2.getID()).thenReturn("2");

+		when(channel1.getID()).thenReturn("1");

+		when(channel1.getUnit()).thenReturn(unit1);

+		when(channel2.getID()).thenReturn("2");

+		when(channel2.getUnit()).thenReturn(unit2);

+

+		when(em.load(eq(ChannelGroup.class), anyString())).thenReturn(channelGroup);

+		when(em.load(eq(Channel.class), anyList())).thenReturn(Arrays.asList(channel1, channel2));

+		when(em.load(eq(Unit.class), anyList())).thenReturn(Arrays.asList(unit1, unit2));

+

+		when(context.getEntityManager()).thenReturn(Optional.of(em));

+	}

+

+	@Test

+	public void testRequestChannelWithDefaultUnit() {

+		ReadRequest readRequest = ProtobufConverter.convert(context,

+				Mdm.ReadRequest.newBuilder().setChannelGroupId("1").addChannelIds("1").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit1);

+		assertThat(readRequest).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+	@Test

+	public void testRequestChannelWithExplicitUnit() {

+		ReadRequest channelWithUnit = ProtobufConverter.convert(context,

+				Mdm.ReadRequest.newBuilder().setChannelGroupId("1").addChannelIds("1").addUnitIds("1").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit1);

+		assertThat(channelWithUnit).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+	@Test

+	public void testRequestAllChannelsWithDefaultUnits() {

+		ReadRequest allChannelWithoutUnits = ProtobufConverter.convert(context,

+				Mdm.ReadRequest.newBuilder().setChannelGroupId("1").build());

+

+		assertThat(allChannelWithoutUnits).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", Collections.emptyMap())

+				.hasFieldOrPropertyWithValue("loadAllChannels", true);

+	}

+

+	@Test

+	public void testRequestAllChannelsWithExplicitUnits() {

+		ReadRequest allChannelWithUnits = ProtobufConverter.convert(context, Mdm.ReadRequest.newBuilder()

+				.setChannelGroupId("1").addChannelIds("1").addChannelIds("2").addUnitIds("2").addUnitIds("1").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit2);

+		channels.put(channel2, unit1);

+		assertThat(allChannelWithUnits).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+	@Test

+	public void testSuperfluousUnitsAreIgnored() {

+		ReadRequest allChannelWithUnits = ProtobufConverter.convert(context, Mdm.ReadRequest.newBuilder()

+				.setChannelGroupId("1").addChannelIds("1").addUnitIds("2").addUnitIds("1").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit2);

+		assertThat(allChannelWithUnits).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+	@Test

+	public void testLessUnitsThenChannels() {

+		ReadRequest allChannelWithUnits = ProtobufConverter.convert(context, Mdm.ReadRequest.newBuilder()

+				.setChannelGroupId("1").addChannelIds("1").addChannelIds("2").addUnitIds("2").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit2);

+		channels.put(channel2, unit2); // no unitId was supplied -> default unit

+		assertThat(allChannelWithUnits).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+	@Test

+	public void testEmptyStringAsUnitIdIsInterpretedAsDefaultUnit() {

+		ReadRequest allChannelWithUnits = ProtobufConverter.convert(context, Mdm.ReadRequest.newBuilder()

+				.setChannelGroupId("1").addChannelIds("1").addChannelIds("2").addUnitIds("").addUnitIds("1").build());

+

+		Map<Channel, Unit> channels = new HashMap<>();

+		channels.put(channel1, unit1); // empty unitId was supplied -> default unit

+		channels.put(channel2, unit1);

+		assertThat(allChannelWithUnits).hasFieldOrPropertyWithValue("channelGroup", channelGroup)

+				.hasFieldOrPropertyWithValue("channels", channels)

+				.hasFieldOrPropertyWithValue("loadAllChannels", false);

+	}

+

+}

diff --git a/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ReflectUtilTest.java b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ReflectUtilTest.java
new file mode 100644
index 0000000..1423355
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/java/org/eclipse/mdm/businessobjects/utils/ReflectUtilTest.java
@@ -0,0 +1,117 @@
+/********************************************************************************
+ * 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.businessobjects.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+public class ReflectUtilTest {
+
+	@Test
+	public void testApplyValueBoolean() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Boolean.class, boolean.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(boolean.class, Boolean.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(boolean.class, boolean.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Boolean.class, Boolean.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Boolean.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Boolean.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(boolean.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, boolean.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueByte() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Byte.class, byte.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(byte.class, Byte.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(byte.class, byte.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Byte.class, Byte.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Byte.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Byte.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(byte.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, byte.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueShort() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Short.class, short.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(short.class, Short.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(short.class, short.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Short.class, Short.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Short.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Short.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(short.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, short.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueInt() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Integer.class, int.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(int.class, Integer.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(int.class, int.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Integer.class, Integer.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Integer.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Integer.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(int.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, int.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueLong() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Long.class, long.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(long.class, Long.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(long.class, long.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Long.class, Long.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Long.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Long.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(long.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, long.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueFloat() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Float.class, float.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(float.class, Float.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(float.class, float.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Float.class, Float.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Float.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Float.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(float.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, float.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueDouble() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Double.class, double.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(double.class, Double.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(double.class, double.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Double.class, Double.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Double.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Double.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(double.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, double.class)).isFalse();
+	}
+
+	@Test
+	public void testApplyValueChar() throws Exception {
+		assertThat(ReflectUtil.isAssignable(Character.class, char.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(char.class, Character.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(char.class, char.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(Character.class, Character.class)).isTrue();
+		assertThat(ReflectUtil.isAssignable(String.class, Character.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(Character.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(char.class, String.class)).isFalse();
+		assertThat(ReflectUtil.isAssignable(String.class, char.class)).isFalse();
+	}
+}
diff --git a/org.eclipse.mdm.businessobjects/src/test/resources/logback-test.xml b/org.eclipse.mdm.businessobjects/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..07612dd
--- /dev/null
+++ b/org.eclipse.mdm.businessobjects/src/test/resources/logback-test.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<configuration debug="false" scan="true">

+

+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

+    <!-- encoders are assigned the type

+         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->

+    <encoder>

+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>

+    </encoder>

+  </appender>

+

+  <logger name="org.eclipse.mdm" level="DEBUG" />

+  

+  <root level="INFO">

+    <appender-ref ref="STDOUT" />

+  </root>

+</configuration>
\ No newline at end of file
diff --git a/release_notes.md b/release_notes.md
index 647db98..0880f35 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -4,6 +4,28 @@
 * [mdmbl Eclipse Git Repositories](http://git.eclipse.org/c/?q=mdmbl)
 * [mdmbl nightly builds - last stable version](http://download.eclipse.org/mdmbl/nightly_master/?d)
 
+## Version 5.1.0M7, 2019/11/08 ##
+
+New Features:
+* Support for reading and writing measurement data.
+* Support for reading preview measurement values.
+* Measurement data can be transferred as Protocol Buffers or JSON
+
+### API changes ###
+
+* REST-API has a new endpoints read (/mdm/environments/{SOURCENAME}/values/read) and write (/mdm/environments/{SOURCENAME}/values/write) measurement values.
+* REST-API has a new endpoint to request preview values (/mdm/environments/{SOURCENAME}/values/preview)
+* o.e.m.a.b.m.ReadRequestBuilder now additionally has methods ReadRequestBuilder.values(int requestSize) and ReadRequestBuilder.values(int startIndex, int requestSize) as terminal operations.
+
+### Changes ###
+
+* [548162](https://bugs.eclipse.org/bugs/show_bug.cgi?id=548162) - Add support for reading/writing measurement data over REST API
+
+### Bugzilla Bugs fixed ###
+
+* [547141](https://bugs.eclipse.org/bugs/show_bug.cgi?id=547141) - ReadRequestBuilder does not allow to create ReadRequests with startIndex or requestSize != 0
+* [547115](https://bugs.eclipse.org/bugs/show_bug.cgi?id=547115) - ClassCastException in ReadRequestHandler#getODSColumns
+
 ## Version 5.1.0M6, 2019/10/18 ##
 
 New Features: