From 917848f27ac2b247df031f235341346a752866ad Mon Sep 17 00:00:00 2001 From: Emil Dinchev Date: Mon, 22 Apr 2024 14:18:20 +0200 Subject: [PATCH 1/5] feat: implement value-only serialization --- .../valueonly/AbstractCollectionMapper.java | 79 ++++ .../json/valueonly/AbstractListMapper.java | 28 ++ .../json/valueonly/AbstractMapper.java | 90 +++++ .../AnnotatedRelationshipMapper.java | 68 ++++ .../valueonly/BasicEventElementMapper.java | 46 +++ .../dataformat/json/valueonly/BlobMapper.java | 72 ++++ .../valueonly/ElementsCollectionMapper.java | 46 +++ .../json/valueonly/ElementsListMapper.java | 66 ++++ .../json/valueonly/EntityMapper.java | 133 +++++++ .../dataformat/json/valueonly/FileMapper.java | 70 ++++ .../valueonly/JsonValueOnlyDeserialiser.java | 110 ++++++ .../valueonly/JsonValueOnlySerialiser.java | 133 +++++++ .../MultiLanguagePropertyMapper.java | 77 ++++ .../json/valueonly/PropertyMapper.java | 47 +++ .../json/valueonly/RangeMapper.java | 57 +++ .../valueonly/ReferenceElementMapper.java | 42 +++ .../valueonly/RelationshipElementMapper.java | 53 +++ .../json/valueonly/SubmodelMapper.java | 23 ++ .../json/valueonly/ValueConverter.java | 83 +++++ .../json/valueonly/ValueOnlyMapper.java | 86 +++++ .../ValueOnlySerializationException.java | 54 +++ .../JsonValueOnlyDeserialiserTest.java | 154 ++++++++ .../JsonValueOnlySerialiserTest.java | 137 +++++++ .../dataformat/json/valueonly/TestData.java | 337 ++++++++++++++++++ .../valueonly/ann_relationship_element.json | 42 +++ .../src/test/resources/valueonly/blob.json | 4 + .../valueonly/date_time_property.json | 3 + .../valueonly/element_collection.json | 47 +++ .../resources/valueonly/element_list.json | 64 ++++ .../src/test/resources/valueonly/entity.json | 11 + .../src/test/resources/valueonly/file.json | 4 + .../valueonly/multi_lang_property.json | 4 + .../resources/valueonly/property_double.json | 3 + .../resources/valueonly/property_int.json | 3 + .../resources/valueonly/property_string.json | 3 + .../src/test/resources/valueonly/range.json | 6 + .../valueonly/ref_element_global.json | 20 ++ .../test/resources/valueonly/submodel.json | 46 +++ 38 files changed, 2351 insertions(+) create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractCollectionMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractListMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AnnotatedRelationshipMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BasicEventElementMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BlobMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsCollectionMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsListMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/FileMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiser.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiser.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/MultiLanguagePropertyMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/PropertyMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RangeMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ReferenceElementMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RelationshipElementMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/SubmodelMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueConverter.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlyMapper.java create mode 100644 dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlySerializationException.java create mode 100644 dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiserTest.java create mode 100644 dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiserTest.java create mode 100644 dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/TestData.java create mode 100644 dataformat-json/src/test/resources/valueonly/ann_relationship_element.json create mode 100644 dataformat-json/src/test/resources/valueonly/blob.json create mode 100644 dataformat-json/src/test/resources/valueonly/date_time_property.json create mode 100644 dataformat-json/src/test/resources/valueonly/element_collection.json create mode 100644 dataformat-json/src/test/resources/valueonly/element_list.json create mode 100644 dataformat-json/src/test/resources/valueonly/entity.json create mode 100644 dataformat-json/src/test/resources/valueonly/file.json create mode 100644 dataformat-json/src/test/resources/valueonly/multi_lang_property.json create mode 100644 dataformat-json/src/test/resources/valueonly/property_double.json create mode 100644 dataformat-json/src/test/resources/valueonly/property_int.json create mode 100644 dataformat-json/src/test/resources/valueonly/property_string.json create mode 100644 dataformat-json/src/test/resources/valueonly/range.json create mode 100644 dataformat-json/src/test/resources/valueonly/ref_element_global.json create mode 100644 dataformat-json/src/test/resources/valueonly/submodel.json diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractCollectionMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractCollectionMapper.java new file mode 100644 index 000000000..ecbd2555b --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractCollectionMapper.java @@ -0,0 +1,79 @@ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Base class for mapping collections of submodel elements as for + * + * @param + */ +public abstract class AbstractCollectionMapper extends AbstractListMapper { + /** + * @param element the submodel element that has to be mapped. + * @param values + * @param idShortPath the idShort path is a dot separated chain of idShorts, that can be used in case of + * troubleshooting. + */ + protected AbstractCollectionMapper(T element, List values, String idShortPath) { + super(element, values, idShortPath); + } + + ObjectNode valuesToJson() throws ValueOnlySerializationException { + ObjectNode elementsNode = JsonNodeFactory.instance.objectNode(); + for (SubmodelElement submodelElement : values) { + String idShort = submodelElement.getIdShort(); + if (elementsNode.has(idShort)) { + throw new ValueOnlySerializationException("Duplicated idShort name '" + idShort + + "' under idShort path '" + idShortPath + "'.", idShortPath); + } + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(submodelElement, idShortPath + "." + idShort); + if (mapper == null) { + // This type of submodel elements are not serialized in value-only format. + continue; + } + JsonNode mapperNode = mapper.toJson(); + if (elementsNode.isObject()) { + elementsNode.setAll((ObjectNode) mapperNode); + } else { + elementsNode.set(idShort, mapperNode); + } + } + return elementsNode; + } + + public void updateFromJson(JsonNode value) throws ValueOnlySerializationException { + if (!value.isObject()) { + throw new ValueOnlySerializationException( + "Cannot update the submodel elements collection at idShort path '" + idShortPath + + "', as the corresponding value-only is not a JSON object.", idShortPath); + } + ObjectNode objNode = (ObjectNode) value; + for (Iterator it = objNode.fieldNames(); it.hasNext(); ) { + String idShort = it.next(); + SubmodelElement submodelElement = findElementByIdShort(idShort); + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(submodelElement, idShortPath + "." + idShort); + //mapper.update(objNode.get(idShort)); + mapper.update(JsonNodeFactory.instance.objectNode().set(idShort, objNode.get(idShort))); + } + } + + private SubmodelElement findElementByIdShort(String idShort) throws ValueOnlySerializationException { + for (SubmodelElement submodelElement : values) { + if (idShort.equals(submodelElement.getIdShort())) { + return submodelElement; + } + } + throw new ValueOnlySerializationException("Cannot find submodel element with idShort '" + idShort + + "' at idShort path '" + idShortPath + "'.", idShortPath); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractListMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractListMapper.java new file mode 100644 index 000000000..aa28217e4 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractListMapper.java @@ -0,0 +1,28 @@ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; + +/** + * Base class for mapping elements providing list of submodel elements as for + *
    + *
  • {@link ElementsListMapper}
  • + *
  • {@link ElementsCollectionMapper}
  • + *
+ * @param + */ +public abstract class AbstractListMapper extends AbstractMapper { + protected final List values; + + /** + * @param element the submodel element that has to be mapped. + * @param idShortPath the idShort path is a dot separated chain of idShorts, that can be used in case of + * troubleshooting. + */ + protected AbstractListMapper(T element, List values, String idShortPath) { + super(element, idShortPath); + this.values = values; + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractMapper.java new file mode 100644 index 000000000..7c6abffcc --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AbstractMapper.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.Iterator; + +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +/** + * The abstract base class for all value-only mappers. + * @param The type of the mapped elements. + */ +abstract class AbstractMapper implements ValueOnlyMapper { + protected final T element; + protected final String idShortPath; + + /** + * + * @param element the submodel element that has to be mapped. + * @param idShortPath the idShort path is a dot separated chain of idShorts, that can be used in case of + * troubleshooting. + */ + protected AbstractMapper(T element, String idShortPath) { + this.element = element; + this.idShortPath = idShortPath; + } + + JsonNode asValueNode(JsonNode value) { + return JsonNodeFactory.instance.objectNode().set(element.getIdShort(), value); + } + + static JsonNode valueFromNode(String msg, String idShortPath, JsonNode valueOnly) { + Iterator fieldNames = valueOnly.fieldNames(); + if (!fieldNames.hasNext()) { + // throw exception as value-only nodes must have exactly one field! + throw new ValueOnlySerializationException( + msg + " at idShort path '" + idShortPath + + "', as the passed value does have no fields!", idShortPath); + } + String fieldName = fieldNames.next(); + if (fieldNames.hasNext()) { + // throw exception as value-only nodes must have exactly one field! + throw new ValueOnlySerializationException( + msg + " at idShort path '" + idShortPath + + "', as the passed value has more than one field!", idShortPath); + } + return valueOnly.get(fieldName); + } + + /** + * Verifies the given object is neither an object nor an array + * @param msg Prefix for exception messages. + * @param idShortPath short path of ID being included to exception messages. + * @param value the node to return as text + * @return given {@code value} as text if it's neither an object nor an array, otherwise throws exception. + * @throws ValueOnlySerializationException throw if node is object or array. + */ + String readValueAsString(String msg, String idShortPath, JsonNode value) + throws ValueOnlySerializationException { + if (value == null || value.isNull()) { + return null; + } + if (value.isObject()) { + throw new ValueOnlySerializationException( + msg + " at idShort path '" + idShortPath + + "', as the passed value is a JSON object.", idShortPath); + } + if (value.isArray()) { + throw new ValueOnlySerializationException( + msg + " at idShort path '" + idShortPath + + "', as the passed value is a JSON array.", idShortPath); + } + return value.asText(); + } +} \ No newline at end of file diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AnnotatedRelationshipMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AnnotatedRelationshipMapper.java new file mode 100644 index 000000000..b38d2040e --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/AnnotatedRelationshipMapper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.DataElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * AnnotatedRelationshipElement is serialized according to the serialization of a ReleationshipElement. Additionally, a + * third named JSON object is introduced with "annotations" as the name of the containing JSON property. The value is + * ${AnnotatedRelationshipElement/annotations}. The values of the array items are serialized depending on the type of + * the annotation data element. + */ +class AnnotatedRelationshipMapper extends RelationshipElementMapper { + private static final String ANNOTATIONS = "annotations"; + AnnotatedRelationshipMapper(AnnotatedRelationshipElement relationship, String idShortPath) { + super(relationship, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode valueNode = (ObjectNode) super.toJson(); + AnnotatedRelationshipElement annotatedRelationshipElement = (AnnotatedRelationshipElement) element; + List annotations = new ArrayList<>(annotatedRelationshipElement.getAnnotations()); + if(!annotations.isEmpty()) { + ElementsListMapper listMapper = new ElementsListMapper<>( + annotatedRelationshipElement, annotations, idShortPath + "." + ANNOTATIONS); + ObjectNode dataNode = (ObjectNode) valueNode.get(element.getIdShort()); + dataNode.set(ANNOTATIONS, listMapper.toJson()); + } + return valueNode; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + super.update(valueOnly); + AnnotatedRelationshipElement annotatedRelationshipElement = (AnnotatedRelationshipElement) element; + List annotations = annotatedRelationshipElement.getAnnotations(); + JsonNode value = valueFromNode("Cannot update the annotated relationship", idShortPath, valueOnly); + JsonNode annotationsNode = value.get(ANNOTATIONS); + if(annotationsNode == null || annotationsNode.isNull()) { + annotations.clear(); + } else { + List elements = new ArrayList<>(annotations); + ElementsListMapper listMapper = new ElementsListMapper<>(annotatedRelationshipElement, elements, idShortPath + "." + ANNOTATIONS); + listMapper.update(annotationsNode); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BasicEventElementMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BasicEventElementMapper.java new file mode 100644 index 000000000..d5a12b99b --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BasicEventElementMapper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.BasicEventElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * BasicEventElement is serialized as named JSON object with ${BasicEventElement/idShort} as the name of the containing + * JSON property. The JSON object contains one JSON property named “observed” with the corresponding value of + * ${BasicEventElement/observed} as the standard serialization of the Reference class. + */ +class BasicEventElementMapper extends AbstractMapper { + private static final String OBSERVED = "observed"; + + BasicEventElementMapper(BasicEventElement event, String idShortPath) { + super(event, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + node.set(OBSERVED, new JsonValueOnlyDeserialiser().toJson(element.getObserved())); + return node; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + element.setObserved(new JsonValueOnlyDeserialiser().deserialiseReference(valueOnly.get(OBSERVED), idShortPath)); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BlobMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BlobMapper.java new file mode 100644 index 000000000..6dae8a659 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/BlobMapper.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.Base64; + +import org.eclipse.digitaltwin.aas4j.v3.model.Blob; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * Blob is serialized as named JSON object with ${Blob/idShort} as the name of the containing JSON property. The JSON + * object contains two JSON properties. The first refers to the content type named "contentType" and value + * ${Blob/contentType}. The latter refers to the value named “value” and value ${Blob/value}. The resulting value-only + * object is indistinguishable whether it contains File or Blob attributes. Therefore, the receiver needs to take the + * type of the target resource into account. Since the receiver knows in advance if a File or a Blob SubmodelElement + * shall be manipulated, it can parse the transferred value-only object accordingly as a File or Blob object. + */ +class BlobMapper extends AbstractMapper { + private static final String CONTENT_TYPE = "contentType"; + private static final String VALUE = "value"; + + BlobMapper(Blob blob, String idShortPath) { + super(blob, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + node.set(CONTENT_TYPE, new TextNode(element.getContentType())); + node.set(VALUE, new TextNode(Base64.getEncoder().encodeToString(element.getValue()))); + return node; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode contentNode = valueOnly.get(CONTENT_TYPE); + if(contentNode == null || contentNode.isNull()) { + element.setContentType(null); + } else if(contentNode.isTextual()) { + element.setContentType(contentNode.asText()); + } else { + throw new ValueOnlySerializationException( + "Invalid Blob contentType at idShort path '" + idShortPath + "'.", idShortPath); + } + + JsonNode valueNode = valueOnly.get(VALUE); + if(valueNode == null || valueNode.isNull()) { + element.setValue(null); + } else if(contentNode != null && contentNode.isTextual()) { + element.setValue(Base64.getDecoder().decode(valueNode.asText())); + } else { + throw new ValueOnlySerializationException( + "Invalid Blob value at idShort path '" + idShortPath + "'.", idShortPath); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsCollectionMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsCollectionMapper.java new file mode 100644 index 000000000..80f732f5a --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsCollectionMapper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * SubmodelElementCollection is serialized as named JSON object with ${SubmodelElementCollection/idShort} as the name of + * the containing JSON property. The elements contained within the struct are serialized according to their respective + * type with ${SubmodelElement/idShort} as the name of the containing JSON property. + */ +class ElementsCollectionMapper extends AbstractCollectionMapper { + ElementsCollectionMapper(Referable elementCollection, List values, String idShortPath) { + super(elementCollection, values, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode elementsNode = valuesToJson(); + return asValueNode(elementsNode); + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode value = valueFromNode("Cannot update submodel elements collection", idShortPath, valueOnly); + updateFromJson(value); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsListMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsListMapper.java new file mode 100644 index 000000000..a4425273a --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ElementsListMapper.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; + +/** + * SubmodelElementList is serialized as a JSON array with the index of the contained SubmodelElement in the list as the + * position in the JSON array. The elements contained within the list are serialized according to their respective type. + */ +class ElementsListMapper extends AbstractListMapper { + ElementsListMapper(T elementList, List values, String idShortPath) { + super(elementList, values, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode(); + for (int i = 0; i < values.size(); i++) { + SubmodelElement submodelElement = values.get(i); + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(submodelElement, idShortPath + "[" + i + "]"); + arrayNode.add(mapper == null ? NullNode.instance : mapper.toJson()); + } + return arrayNode; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + if(!valueOnly.isArray()) { + throw new ValueOnlySerializationException( + "Cannot update the submodel elements list at idShort path '" + idShortPath + + "', as the corresponding value-only is not a JSON array.", idShortPath); + } + ArrayNode arrayNode = (ArrayNode) valueOnly; + if (arrayNode.size() != values.size()) { + throw new ValueOnlySerializationException( + "Cannot update the submodel elements list at idShort path '" + idShortPath + + "', as the corresponding value-only array has different size.", idShortPath); + } + for (int i = 0; i < arrayNode.size(); i++ ) { + SubmodelElement submodelElement = values.get(i); + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(submodelElement, idShortPath + "[" + i + "]"); + mapper.update(arrayNode.get(i)); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java new file mode 100644 index 000000000..d923a48eb --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * Entity is serialized as named JSON object with ${Entity/idShort} as the name of the containing JSON property. The + * JSON object contains three JSON properties. The first is named “statements” ${Entity/statements} and contains an + * array of the serialized submodel elements according to their respective serialization mentioned in this clause. The + * second is named either “globalAssetId” or “specificAssetId” and contains either a Reference (see above) or a + * SpecificAssetId. SpecificAssetId is serialized as named JSON object with the values of the properties “name” for the + * JSON key and “value” for the JSON value. The third property is named “entityType” and contains a string + * representation of ${Entity/entityType}. + */ +class EntityMapper extends AbstractMapper { + private static final String STATEMENTS = "statements"; + private static final String GLOBAL_ASSET_ID = "globalAssetId"; + private static final String SPECIFIC_ASSET_ID = "specificAssetId"; + private static final String ENTITY_TYPE = "entityType"; + + EntityMapper(Entity entity, String idShortPath) { + super(entity, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + ElementsListMapper statementsMapper = new ElementsListMapper<>( + element, element.getStatements(), idShortPath + "." + STATEMENTS); + node.set(STATEMENTS, statementsMapper.toJson()); + String globalAssetId = element.getGlobalAssetId(); + if(globalAssetId != null) { + node.set(GLOBAL_ASSET_ID, new TextNode(globalAssetId)); + } + List specificAssetIds = element.getSpecificAssetIds(); + if(specificAssetIds != null && !specificAssetIds.isEmpty()) { + ObjectNode assetIdNode = JsonNodeFactory.instance.objectNode(); + for (SpecificAssetId assetId : specificAssetIds) { + assetIdNode.set(assetId.getValue(), new TextNode(assetId.getName())); + } + node.set(SPECIFIC_ASSET_ID, assetIdNode); + } + node.set(ENTITY_TYPE, new TextNode(EnumSerializer.serializeEnumName(element.getEntityType().name()))); + return asValueNode(node); + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode value = valueFromNode("Cannot update entity", idShortPath, valueOnly); + JsonNode statementsNode = value.get(STATEMENTS); + if(statementsNode == null) { + element.getStatements().clear(); + } else { + ElementsListMapper statementsMapper = new ElementsListMapper<>( + element, element.getStatements(), idShortPath + "." + STATEMENTS); + statementsMapper.update(statementsNode); + } + JsonNode globalAssetIdNode = value.get(GLOBAL_ASSET_ID); + if(globalAssetIdNode == null || globalAssetIdNode.isNull()) { + element.setGlobalAssetId(null); + } else if(globalAssetIdNode.isTextual()) { + element.setGlobalAssetId(globalAssetIdNode.asText()); + } else { + throw new ValueOnlySerializationException("Cannot update the Entity at idShort path '" + + idShortPath + "', as the passed " + GLOBAL_ASSET_ID + " is not a string.", idShortPath); + } + JsonNode specificAssetIdNode = value.get(SPECIFIC_ASSET_ID); + if(specificAssetIdNode != null) { + if(!specificAssetIdNode.isObject()) { + throw new ValueOnlySerializationException("Cannot update the Entity at idShort path '" + + idShortPath + "', as the passed " + SPECIFIC_ASSET_ID + " is not an object.", idShortPath); + } + updateSpecificAssetIds(element.getSpecificAssetIds(), (ObjectNode) specificAssetIdNode); + } + JsonNode entityTypeNode = value.get(ENTITY_TYPE); + if(entityTypeNode == null || !entityTypeNode.isTextual()) { + throw new ValueOnlySerializationException("Cannot update the Entity at idShort path '" + + idShortPath + "', as its type is not set as string property '" + ENTITY_TYPE + "'.", idShortPath); + } + element.setEntityType(EntityType.valueOf(EnumDeserializer.deserializeEnumName(entityTypeNode.textValue()))); + } + + private void updateSpecificAssetIds(List specificAssetIds, ObjectNode objNode) + throws ValueOnlySerializationException { + for (Iterator it = objNode.fieldNames(); it.hasNext(); ) { + String name = it.next(); + SpecificAssetId specificAssetId = findSpecificAssetIdByName(specificAssetIds, name); + JsonNode valueNode = objNode.get(name); + if(!valueNode.isTextual()) { + throw new ValueOnlySerializationException("Cannot update the SpecificAssetId at IdShort path '" + + idShortPath + "." + SPECIFIC_ASSET_ID + "." + name + "' as its value is not set as string property.", + idShortPath); + } + specificAssetId.setValue(valueNode.textValue()); + } + } + + private SpecificAssetId findSpecificAssetIdByName(List specificAssetIds, String name) + throws ValueOnlySerializationException { + for (SpecificAssetId saId : specificAssetIds) { + if(name.equals(saId.getName())) { + return saId; + } + } + throw new ValueOnlySerializationException( + "Cannot find the SpecificAssetId with name '" + name + "'.", idShortPath + "." + SPECIFIC_ASSET_ID); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/FileMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/FileMapper.java new file mode 100644 index 000000000..5f0403de9 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/FileMapper.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.File; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * File is serialized as named JSON object with ${File/idShort} as the name of the containing JSON property. The JSON + * object contains two JSON properties. The first refers to the content type named "contentType" and value + * ${File/contentType}. The latter refers to the value named “value” and value ${File/value}. The resulting value-only + * object is indistinguishable whether it contains File or Blob attributes. Therefore, the receiver needs to take the + * type of the target resource into account. Since the receiver knows in advance if a File or a Blob SubmodelElement + * shall be manipulated, it can parse the transferred value-only object accordingly as a File or Blob object. + */ +class FileMapper extends AbstractMapper { + private static final String CONTENT_TYPE = "contentType"; + private static final String VALUE = "value"; + + FileMapper(File file, String idShortPath) { + super(file, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + node.set(CONTENT_TYPE, new TextNode(element.getContentType())); + node.set(VALUE, new TextNode(element.getValue())); + return node; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode contentNode = valueOnly.get(CONTENT_TYPE); + if(contentNode == null || contentNode.isNull()) { + element.setContentType(null); + } else if(contentNode.isTextual()) { + element.setContentType(contentNode.textValue()); + } else { + throw new ValueOnlySerializationException( + "Invalid File " + CONTENT_TYPE + " at idShort path '" + idShortPath + "'.", idShortPath); + } + + JsonNode valueNode = valueOnly.get(VALUE); + if(valueNode == null || valueNode.isNull()) { + element.setValue(null); + } else if(contentNode != null && contentNode.isTextual()) { + element.setValue(valueNode.textValue()); + } else { + throw new ValueOnlySerializationException( + "Invalid File " + VALUE + " at idShort path '" + idShortPath + "'.", idShortPath); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiser.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiser.java new file mode 100644 index 000000000..d588fa6a4 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiser.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * This class implements the value-only Serialization in JSON format, as described in section 11.4.2 of + * AAS Specification Part 2
+ * + * Values are only available for: + *
    + *
  1. All subtypes of abstract type DataElement
  2. + *
  3. SubmodelElementList and SubmodelElementCollection respectively for their included SubmodelElements
  4. + *
  5. ReferenceElement
  6. + *
  7. RelationshipElement + AnnotatedRelationshipElement
  8. + *
  9. Entity
  10. + *
  11. BasicEventElement
  12. + *
+ */ +public class JsonValueOnlyDeserialiser extends JsonDeserializer { + + JsonNode toJson(Reference reference) { + return mapper.valueToTree(reference) ; + } + + JsonNode readTree(String jsonString) throws ValueOnlySerializationException { + try { + return mapper.readTree(jsonString); + } catch (JsonProcessingException e) { + throw new ValueOnlySerializationException("Cannot parse the value only string: ", e, "$"); + } + + } + + + public Reference deserialiseReference(JsonNode refNode, String idShortPath) throws ValueOnlySerializationException { + if(refNode == null) { + return null; + } + try { + return mapper.treeToValue(refNode, Reference.class); + } catch (JsonProcessingException e) { + throw new ValueOnlySerializationException( + "Cannot deserialize a reference at idShort path + '" + idShortPath + "'.", e, idShortPath); + } + } + + + + /** + * The default constructor creates a value-only mapper which serializes and deserializes submodels and submodel + * elements to a compact value-only JSON string. + */ + public JsonValueOnlyDeserialiser() { + + } + + + + /** + * Update an existing submodel with the given value-only JSON string. + *
Note:The update is not an atomic operation and if an exception is thrown, the corresponding submodel + * will be in an inconsistent state. If you cannot handle such situations, pass a copy of the original submodel. + * @param submodel The submodel to be updated. If you want to prevent the direct modification of the original + * submodel, just use the corresponding copy constructor, when you pass this argument. Not null. + * @param valueOnly the valueOnly string. Not null. + * + */ + public void deserialise(Submodel submodel, String valueOnly) throws ValueOnlySerializationException { + JsonNode node = readTree(valueOnly); + SubmodelMapper mapper = new SubmodelMapper(submodel, "$"); + mapper.update(node); + } + + + /** + * Update an existing submodel element with the given valueOnly. + *
Note:The update is not an atomic operation and if an exception is thrown, the corresponding element + * will be in an inconsistent state. If you cannot handle such situations, pass a copy of the original element. + * @param element The submodel element to be updated. If you want to prevent the direct modification of the original + * submodel element, just use the corresponding copy constructor, when you pass this argument. + * Not null. + * @param valueOnly the valueOnly string. Not null. + */ + public void deserialise(SubmodelElement element, String valueOnly) throws ValueOnlySerializationException { + JsonNode node = readTree(valueOnly); + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(element, "$"); + mapper.update(node); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiser.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiser.java new file mode 100644 index 000000000..1f9e150ed --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiser.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSerializer; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * This class implements the value-only Serialization in JSON format, as described in section 11.4.2 of + * AAS Specification Part 2
+ * + * Values are only available for: + *
    + *
  1. All subtypes of abstract type DataElement
  2. + *
  3. SubmodelElementList and SubmodelElementCollection respectively for their included SubmodelElements
  4. + *
  5. ReferenceElement
  6. + *
  7. RelationshipElement + AnnotatedRelationshipElement
  8. + *
  9. Entity
  10. + *
  11. BasicEventElement
  12. + *
+ */ +public class JsonValueOnlySerialiser extends JsonSerializer { + + private final boolean prettyString; + + JsonNode toJson(Reference reference) { + return mapper.valueToTree(reference) ; + } + + JsonNode readTree(String jsonString) throws ValueOnlySerializationException { + try { + return mapper.readTree(jsonString); + } catch (JsonProcessingException e) { + throw new ValueOnlySerializationException("Cannot parse the value only string: ", e, "$"); + } + + } + + /** + * The default constructor creates a value-only mapper which serializes and deserializes submodels and submodel + * elements to a compact value-only JSON string. + */ + public JsonValueOnlySerialiser() { + this(false); + } + + /** + * Creates a value-only mapper. + * @param prettyString pass true, if you want to have a pretty formatted value-only JSON strings. + */ + public JsonValueOnlySerialiser(boolean prettyString) { + this.prettyString = prettyString; + } + + /** + * Serializes a submodel in value-only JSON format. + * @param submodel the submodel to be serialized. Not null. + * @return the corresponding value-only JSON string. + */ + public String serialise(Submodel submodel) throws ValueOnlySerializationException { + SubmodelMapper mapper = new SubmodelMapper(submodel, "$"); + JsonNode node = mapper.toJson(); + return stringify(node); + } + + /** + * Update an existing submodel with the given value-only JSON string. + *
Note:The update is not an atomic operation and if an exception is thrown, the corresponding submodel + * will be in an inconsistent state. If you cannot handle such situations, pass a copy of the original submodel. + * @param submodel The submodel to be updated. If you want to prevent the direct modification of the original + * submodel, just use the corresponding copy constructor, when you pass this argument. Not null. + * @param valueOnly the valueOnly string. Not null. + * + */ + public void update(Submodel submodel, String valueOnly) throws ValueOnlySerializationException { + JsonNode node = readTree(valueOnly); + SubmodelMapper mapper = new SubmodelMapper(submodel, "$"); + mapper.update(node); + } + + /** + * Serializes a submodel element in value-only JSON format. + * @param element the submodel element to be serialized. Not null. + * @return the corresponding value-only JSON string. + */ + public String serialise(SubmodelElement element) throws ValueOnlySerializationException { + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(element, "$"); + if(mapper == null) { + throw new ValueOnlySerializationException( + "Value-only serialization is not allowed for submodel elements of type '" + element.getClass() + "'.", + "$"); + } + JsonNode node = mapper.toJson(); + return stringify(node); + } + + /** + * Update an existing submodel element with the given valueOnly. + *
Note:The update is not an atomic operation and if an exception is thrown, the corresponding element + * will be in an inconsistent state. If you cannot handle such situations, pass a copy of the original element. + * @param element The submodel element to be updated. If you want to prevent the direct modification of the original + * submodel element, just use the corresponding copy constructor, when you pass this argument. + * Not null. + * @param valueOnly the valueOnly string. Not null. + */ + public void update(SubmodelElement element, String valueOnly) throws ValueOnlySerializationException { + JsonNode node = readTree(valueOnly); + ValueOnlyMapper mapper = ValueOnlyMapper.createMapper(element, "$"); + mapper.update(node); + } + + private String stringify(JsonNode node) { + return prettyString ? node.toPrettyString() : node.toString(); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/MultiLanguagePropertyMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/MultiLanguagePropertyMapper.java new file mode 100644 index 000000000..11b978413 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/MultiLanguagePropertyMapper.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * MultiLanguageProperty is serialized as named JSON object with ${MultiLanguageProperty/idShort} as the name of the + * containing JSON property. The JSON object contains an array of JSON objects for each language of the + * MultiLanguageProperty with the language as name and the corresponding localized string as value of the respective + * JSON property. The language name is defined as two chars according to ISO 639-1. + */ +class MultiLanguagePropertyMapper extends AbstractMapper { + + MultiLanguagePropertyMapper(MultiLanguageProperty property, String idShortPath) { + super(property, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + List langTexts = element.getValue(); + if(langTexts == null || langTexts.isEmpty()) { + return NullNode.instance; + } + ObjectNode node = JsonNodeFactory.instance.objectNode(); + for (LangStringTextType langText: langTexts) { + node.set(langText.getLanguage(), new TextNode(langText.getText())); + } + return node; + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + if(!valueOnly.isObject()) { + throw new ValueOnlySerializationException( + "Cannot update the multi-language property at idShort path '" + idShortPath + + "', as the passed value-only is not a JSON object.", idShortPath); + } + ObjectNode propNode = (ObjectNode)valueOnly; + List value = element.getValue(); + value.clear(); + for (Iterator it = propNode.fieldNames(); it.hasNext(); ) { + String language = it.next(); + JsonNode textNode = propNode.get(language); + if(!textNode.isTextual()) { + String fullPath = idShortPath + "." + language; + throw new ValueOnlySerializationException( + "Cannot update the multi-language property at idShort path '" + fullPath + + "', as the passed value is not a string.", idShortPath); + } + value.add(new DefaultLangStringTextType.Builder().language(language).text(textNode.textValue()).build()); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/PropertyMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/PropertyMapper.java new file mode 100644 index 000000000..c3cacafe7 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/PropertyMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Property is serialized as ${Property/idShort}: ${Property/value} where ${Property/value} is the JSON serialization + * of the respective property’s value in accordance with the data type to value mapping. + * @see ValueConverter + */ +class PropertyMapper extends AbstractMapper { + PropertyMapper(Property property, String idShortPath) { + super(property, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + try { + JsonNode value = ValueConverter.convert(element.getValueType(), element.getValue()); + return asValueNode(value); + } catch (NumberFormatException ex) { + throw new ValueOnlySerializationException("Cannot serialize the property with idShort path '" + + idShortPath + "': " + ex.getMessage(), idShortPath); + } + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode valueNode = valueFromNode("Cannot update the property", idShortPath, valueOnly); + element.setValue(readValueAsString("Cannot update the property", idShortPath, valueNode)); + } +} \ No newline at end of file diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RangeMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RangeMapper.java new file mode 100644 index 000000000..2d1167d7c --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RangeMapper.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Range; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Range is serialized as named JSON object with ${Range/idShort} as the name of the containing JSON property. The JSON + * object contains two JSON properties. The first is named "min". The second is named "max". Their corresponding values + * are ${Range/min} and ${Range/max}. + */ +class RangeMapper extends AbstractMapper { + private static final String MIN = "min"; + private static final String MAX = "max"; + + RangeMapper(Range range, String idShortPath) { + super(range, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + try { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + DataTypeDefXsd valueType = element.getValueType(); + node.set(MIN, ValueConverter.convert(valueType, element.getMin())); + node.set(MAX, ValueConverter.convert(valueType, element.getMax())); + return asValueNode(node); + } catch (NumberFormatException ex) { + throw new ValueOnlySerializationException("Cannot serialize the range with idShort path '" + + idShortPath + "': " + ex.getMessage(), idShortPath); + } + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode valueNode = valueFromNode("Cannot update Range", idShortPath, valueOnly); + element.setMax(readValueAsString("Cannot update Range." + MAX, idShortPath, valueNode.get(MAX))); + element.setMin(readValueAsString("Cannot update Range." + MIN, idShortPath, valueNode.get(MIN))); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ReferenceElementMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ReferenceElementMapper.java new file mode 100644 index 000000000..9dd4df1ec --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ReferenceElementMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * ReferenceElement is serialized as ${ReferenceElement/idShort}: ${ReferenceElement/value} where + * ${ReferenceElement/value} is the serialization of the Reference class. + */ +class ReferenceElementMapper extends AbstractMapper { + + ReferenceElementMapper(ReferenceElement element, String idShortPath) { + super(element, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + JsonNode value = new JsonValueOnlySerialiser().toJson(element.getValue()); + return asValueNode(value); + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonNode reference = valueFromNode("Cannot update ReferenceElement", idShortPath, valueOnly); + element.setValue(new JsonValueOnlyDeserialiser().deserialiseReference(reference, idShortPath)); + } +} \ No newline at end of file diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RelationshipElementMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RelationshipElementMapper.java new file mode 100644 index 000000000..9646bc5ff --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/RelationshipElementMapper.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * RelationshipElement is serialized as named JSON object with ${RelationshipElement/idShort} as the name of the + * containing JSON property. The JSON object contains two JSON properties. The first is named "first". The second is + * named "second". Their corresponding values are ${RelationshipElement/first} resp. ${Relationship/second}. The values + * are serialized according to the serialization of a Reference. + */ +class RelationshipElementMapper extends AbstractMapper { + private static final String FIRST = "first"; + private static final String SECOND = "second"; + + RelationshipElementMapper(RelationshipElement relationship, String idShortPath) { + super(relationship, idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + ObjectNode node = JsonNodeFactory.instance.objectNode(); + JsonValueOnlySerialiser serialiser = new JsonValueOnlySerialiser(); + node.set(FIRST, serialiser.toJson(element.getFirst())); + node.set(SECOND, serialiser.toJson(element.getSecond())); + return asValueNode(node); + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + JsonValueOnlyDeserialiser deserialiser = new JsonValueOnlyDeserialiser(); + JsonNode value = valueFromNode("Cannot update the relationship element", idShortPath, valueOnly); + element.setFirst(deserialiser.deserialiseReference(value.get(FIRST), idShortPath + "." + FIRST)); + element.setSecond(deserialiser.deserialiseReference(value.get(SECOND), idShortPath + "." + SECOND)); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/SubmodelMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/SubmodelMapper.java new file mode 100644 index 000000000..277015229 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/SubmodelMapper.java @@ -0,0 +1,23 @@ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A submodel is serialized as an unnamed JSON object. + */ +public class SubmodelMapper extends AbstractCollectionMapper { + SubmodelMapper(Submodel submodel, String idShortPath) { + super(submodel, submodel.getSubmodelElements(), idShortPath); + } + + @Override + public JsonNode toJson() throws ValueOnlySerializationException { + return valuesToJson(); + } + + @Override + public void update(JsonNode valueOnly) throws ValueOnlySerializationException { + updateFromJson(valueOnly); + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueConverter.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueConverter.java new file mode 100644 index 000000000..b215d78b4 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueConverter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.FloatNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.TextNode; + +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; + +import java.math.BigDecimal; + +/** + * This is a helper class for the serialization of string values depending of their data type. The data types are + * defined according to the W3C XML Schema (https://www.w3.org/TR/xmlschema-2/#built-in-datatypes and + * https://www.w3.org/TR/xmlschema-2/#built-in-derived). + * Note that for xs:decimal, xs:unsignedLong, xs:positiveInteger, xs:nonNegativeInteger, xs:negativeInteger and + * xs:nonPositiveInteger exists the possibility for lost of precision during the conversion. + */ +class ValueConverter { + private ValueConverter() {} + + /** + * + * @param dataType The data type. + * @param value + * @return + * @throws NumberFormatException + */ + static JsonNode convert(DataTypeDefXsd dataType, String value) throws NumberFormatException { + if(dataType == null) { + return new TextNode(value); + } + + switch (dataType) { + case BOOLEAN: + return Boolean.parseBoolean(value) ? BooleanNode.TRUE : BooleanNode.FALSE; + case DECIMAL: + // According to the AAS spec, this type is serialized as a number. + // There is a possibility for lost of precision. + return new DoubleNode(new BigDecimal(value).doubleValue()); + case BYTE: + case UNSIGNED_BYTE: + case SHORT: + case UNSIGNED_SHORT: + case INT: + return new IntNode(Integer.parseInt(value)); + case UNSIGNED_INT: + case POSITIVE_INTEGER: + case NEGATIVE_INTEGER: + case NON_NEGATIVE_INTEGER: + case NON_POSITIVE_INTEGER: + case LONG: + return new LongNode(Long.parseLong(value)); + case UNSIGNED_LONG: + // According to the spec, it should be serialized as number. There is a possibility for precision lost. + case DOUBLE: + return new DoubleNode(Double.parseDouble(value)); + case FLOAT: + return new FloatNode(Float.parseFloat(value)); + default: + // All other types have no need to be converted from string. + return new TextNode(value); + } + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlyMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlyMapper.java new file mode 100644 index 000000000..061944b91 --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlyMapper.java @@ -0,0 +1,86 @@ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.BasicEventElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Blob; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.File; +import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Range; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import com.fasterxml.jackson.databind.JsonNode; + +public interface ValueOnlyMapper { + + /** + * This method converts the corresponding element to a value-only JSON node. + * + * @return the corresponding JSON node. + * @throws ValueOnlySerializationException with information about the idShort path. + */ + JsonNode toJson() throws ValueOnlySerializationException; + + /** + * Updates the corresponding element according the passed valueOnly JSON node. + * + * @param valueOnly the value only JSON node. + * @throws ValueOnlySerializationException with information about the idShort path. + *
Note:The update is not an atomic operation and if an exception is thrown, the corresponding element + * will be in an inconsistent state. If you cannot handle such situations, pass a copy of the original element. + */ + void update(JsonNode valueOnly) throws ValueOnlySerializationException; + + /** + * Creates the corresponding mapper. + * + * @param element the submodel element. + * @param idShortPath the idShort path. + * @return the corresponding mapper or null if this type cannot be serialized to value-only JSON string. + */ + static ValueOnlyMapper createMapper(SubmodelElement element, String idShortPath) { + if (element instanceof Blob) { + return new BlobMapper((Blob) element, idShortPath); + } + if (element instanceof File) { + return new FileMapper((File) element, idShortPath); + } + if (element instanceof MultiLanguageProperty) { + return new MultiLanguagePropertyMapper((MultiLanguageProperty) element, idShortPath); + } + if (element instanceof Property) { + return new PropertyMapper((Property) element, idShortPath); + } + if (element instanceof Range) { + return new RangeMapper((Range) element, idShortPath); + } + if (element instanceof ReferenceElement) { + return new ReferenceElementMapper((ReferenceElement) element, idShortPath); + } + if (element instanceof Entity) { + return new EntityMapper((Entity) element, idShortPath); + } + if (element instanceof BasicEventElement) { + return new BasicEventElementMapper((BasicEventElement) element, idShortPath); + } + if (element instanceof SubmodelElementCollection) { + SubmodelElementCollection elementCollection = (SubmodelElementCollection) element; + return new ElementsCollectionMapper(elementCollection, elementCollection.getValue(), idShortPath); + } + if (element instanceof SubmodelElementList) { + SubmodelElementList elementList = (SubmodelElementList) element; + return new ElementsListMapper<>(elementList, elementList.getValue(), idShortPath); + } + if (element instanceof AnnotatedRelationshipElement) { + return new AnnotatedRelationshipMapper((AnnotatedRelationshipElement) element, idShortPath); + } + if (element instanceof RelationshipElement) { + return new RelationshipElementMapper((RelationshipElement) element, idShortPath); + } + return null; + } +} diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlySerializationException.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlySerializationException.java new file mode 100644 index 000000000..6222dec1a --- /dev/null +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/ValueOnlySerializationException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +/** + * This exception is thrown during the value-only serialization or deserialization. + */ +public class ValueOnlySerializationException extends RuntimeException { + private final String idShortPath; + + /** + * The constructor. + * @param msg The exception message + * @param idShortPath the idShort path is a dot separated chain of idShorts, that can be used in case of + * troubleshooting. + */ + public ValueOnlySerializationException(String msg, String idShortPath) { + super(msg); + this.idShortPath = idShortPath; + } + + /** + * The constructor. + * @param msg The exception message. + * @param cause The root cause. + * @param idShortPath the idShort path is a dot separated chain of idShorts, that can be used in case of + * troubleshooting. + */ + public ValueOnlySerializationException(String msg, Throwable cause, String idShortPath) { + super(msg, cause); + this.idShortPath = idShortPath; + } + + /** + * Return the corresponding idShort path. + * @return the idShort path is a dot separated chain of idShorts, that can be used in case of troubleshooting. + */ + public String getIdShortPath() { + return idShortPath; + } +} diff --git a/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiserTest.java b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiserTest.java new file mode 100644 index 000000000..09cffa276 --- /dev/null +++ b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlyDeserialiserTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + + +import org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Blob; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.File; +import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Range; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import static org.junit.Assert.assertEquals; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class JsonValueOnlyDeserialiserTest { + + private static final JsonValueOnlyDeserialiser deserialiser = new JsonValueOnlyDeserialiser(); + private static final JsonValueOnlySerialiser serialiser = new JsonValueOnlySerialiser(); + + @Test + public void testUpdateSubmodel() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.SUBMODEL_UPDATED); + Submodel actual = TestData.SUBMODEL.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.SUBMODEL_UPDATED, actual); + } + + @Test + public void testUpdateEntity() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.ENTITY_UPDATED); + Entity actual = TestData.ENTITY.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.ENTITY_UPDATED, actual); + } + + @Test + public void testUpdateProperty() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_INT_UPDATED); + Property actual = TestData.PROPERTY_INT.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.PROPERTY_INT_UPDATED, actual); + } + + @Test + public void testUpdateRange() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.RANGE_DOUBLE_UPDATED); + Range actual = TestData.RANGE_DOUBLE.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.RANGE_DOUBLE_UPDATED, actual); + } + + @Test + public void testUpdateBlob() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.BLOB_UPDATED); + Blob actual = TestData.BLOB.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.BLOB_UPDATED, actual); + } + + @Test + public void testUpdateFile() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.FILE_UPDATED); + File actual = TestData.FILE.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.FILE_UPDATED, actual); + } + + @Test + public void testUpdateMultiLangProperty() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.MULTI_LANGUAGE_PROPERTY_UPDATED); + MultiLanguageProperty actual = TestData.MULTI_LANGUAGE_PROPERTY.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.MULTI_LANGUAGE_PROPERTY_UPDATED, actual); + } + + @Test + public void testUpdatePropertyDouble() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_DOUBLE_UPDATED); + Property actual = TestData.PROPERTY_DOUBLE.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.PROPERTY_DOUBLE_UPDATED, actual); + } + + @Test + public void testUpdatePropertyDatetime() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_DATETIME_UPDATED); + Property actual = TestData.PROPERTY_DATETIME.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.PROPERTY_DATETIME_UPDATED, actual); + } + + @Test + public void testUpdatePropertyString() { + String valueOnly = serialiser.serialise(TestData.PROPERTY_STRING_UPDATED); + Property actual = TestData.PROPERTY_STRING.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.PROPERTY_STRING_UPDATED, actual); + } + + @Test + public void testUpdateRefElementGlobal() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.REFERENCE_ELEMENT_GLOBAL_UPDATED); + ReferenceElement actual = TestData.REFERENCE_ELEMENT_GLOBAL.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.REFERENCE_ELEMENT_GLOBAL_UPDATED, actual); + } + + @Test + public void testUpdateAnnotatedRelationshipElement() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.ANNOTATED_RELATIONSHIP_ELEMENT_UPDATED); + AnnotatedRelationshipElement actual = TestData.ANNOTATED_RELATIONSHIP_ELEMENT.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.ANNOTATED_RELATIONSHIP_ELEMENT_UPDATED, actual); + } + + @Test + public void testUpdateCollectionElement() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.ELEMENT_COLLECTION_UPDATED); + SubmodelElementCollection actual = TestData.ELEMENT_COLLECTION.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.ELEMENT_COLLECTION_UPDATED, actual); + } + + @Test + public void testUpdateListElement() throws ValueOnlySerializationException { + String valueOnly = serialiser.serialise(TestData.ELEMENT_LIST_UPDATED); + SubmodelElementList actual = TestData.ELEMENT_LIST.get(); + deserialiser.deserialise(actual, valueOnly); + assertEquals(TestData.ELEMENT_LIST_UPDATED, actual); + } + +} \ No newline at end of file diff --git a/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiserTest.java b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiserTest.java new file mode 100644 index 000000000..bf4c9cf2e --- /dev/null +++ b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/JsonValueOnlySerialiserTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.json.JSONException; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class JsonValueOnlySerialiserTest { + private static final JsonValueOnlySerialiser serialiser = new JsonValueOnlySerialiser(true); + + + @Test + public void testSerializeSubmodel() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.SUBMODEL.get()); + String expected = readValueOnlyFile("submodel.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeEntity() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.ENTITY.get()); + String expected = readValueOnlyFile("entity.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeIntProperty() throws ValueOnlySerializationException, IOException, JSONException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_INT.get()); + String expected = readValueOnlyFile("property_int.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeRange() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.RANGE_DOUBLE.get()); + String expected = readValueOnlyFile("range.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeBlob() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.BLOB.get()); + String expected = readValueOnlyFile("blob.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeFile() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.FILE.get()); + String expected = readValueOnlyFile("file.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeMultiLangProperty() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.MULTI_LANGUAGE_PROPERTY.get()); + String expected = readValueOnlyFile("multi_lang_property.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializePropertyDouble() throws ValueOnlySerializationException, JSONException, IOException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_DOUBLE.get()); + String expected = readValueOnlyFile("property_double.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializePropertyDatetime() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_DATETIME.get()); + String expected = readValueOnlyFile("date_time_property.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializePropertyString() throws IOException, JSONException { + String valueOnly = serialiser.serialise(TestData.PROPERTY_STRING.get()); + String expected = readValueOnlyFile("property_string.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeRefElementGlobal() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.REFERENCE_ELEMENT_GLOBAL.get()); + String expected = readValueOnlyFile("ref_element_global.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeAnnotatedRelationshipElement() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.ANNOTATED_RELATIONSHIP_ELEMENT.get()); + String expected = readValueOnlyFile("ann_relationship_element.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + @Test + public void testSerializeCollectionElement() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.ELEMENT_COLLECTION.get()); + String expected = readValueOnlyFile("element_collection.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + // failing + } + + @Test + public void testSerializeListElement() throws IOException, ValueOnlySerializationException, JSONException { + String valueOnly = serialiser.serialise(TestData.ELEMENT_LIST.get()); + String expected = readValueOnlyFile("element_list.json"); + JSONAssert.assertEquals(expected, valueOnly, JSONCompareMode.NON_EXTENSIBLE); + } + + + private String readValueOnlyFile(String valueOnlyFile) throws IOException { + return new String(getClass().getClassLoader().getResourceAsStream( + "valueonly/" + valueOnlyFile).readAllBytes(), StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/TestData.java b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/TestData.java new file mode 100644 index 000000000..dd0446a88 --- /dev/null +++ b/dataformat-json/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/TestData.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2023 SAP SE or an SAP affiliate company. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.digitaltwin.aas4j.v3.dataformat.json.valueonly; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.function.Supplier; + +import org.eclipse.digitaltwin.aas4j.v3.model.AnnotatedRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Blob; +import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; +import org.eclipse.digitaltwin.aas4j.v3.model.File; +import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.ModellingKind; +import org.eclipse.digitaltwin.aas4j.v3.model.MultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.Property; +import org.eclipse.digitaltwin.aas4j.v3.model.Range; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.RelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAnnotatedRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultBlob; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEntity; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultFile; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultMultiLanguageProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperation; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultRange; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReferenceElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultRelationshipElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; + +public class TestData { + public static final Supplier ENTITY = () -> new DefaultEntity.Builder() + .idShort("entity1") + .entityType(EntityType.SELF_MANAGED_ENTITY) + .globalAssetId("Global Asset Id") + .statements(new DefaultProperty.Builder() + .idShort("maxRotationSpeed") + .valueType(DataTypeDefXsd.INT) + .value("5000") + .build()) + .build(); + + public static final Entity ENTITY_UPDATED = new DefaultEntity.Builder() + .idShort("entity1") + .entityType(EntityType.CO_MANAGED_ENTITY) + .globalAssetId("Global Asset Id Updated") + .statements(new DefaultProperty.Builder() + .idShort("maxRotationSpeed") + .valueType(DataTypeDefXsd.INT) + .value("5001") + .build()) + .build(); + + public static final Supplier PROPERTY_STRING = () -> new DefaultProperty.Builder() + .category("category") + .idShort("propString") + .value("foo") + .build(); + + public static final Property PROPERTY_STRING_UPDATED = new DefaultProperty.Builder() + .category("category") + .idShort("propString") + .value("foo updated") + .build(); + + public static final Supplier RANGE_DOUBLE = () -> new DefaultRange.Builder() + .idShort("rangeDouble") + .valueType(DataTypeDefXsd.DOUBLE) + .min("3.0") + .max("5.0") + .build(); + + public static final Range RANGE_DOUBLE_UPDATED = new DefaultRange.Builder() + .idShort("rangeDouble") + .valueType(DataTypeDefXsd.DOUBLE) + .min("3.0") + .max("5.0") + .build(); + + public static final Supplier BLOB = () -> new DefaultBlob.Builder() + .idShort("blob1") + .contentType("application/octet-stream") + .value("example-data".getBytes()) + .build(); + + public static final Blob BLOB_UPDATED = new DefaultBlob.Builder() + .idShort("blob1") + .contentType("application/json") + .value("{ value: 42 }".getBytes()) + .build(); + + public static final Supplier FILE = () -> new DefaultFile.Builder() + .idShort("file1") + .contentType("application/pdf") + .value("SafetyInstructions.pdf") + .build(); + + public static final File FILE_UPDATED = new DefaultFile.Builder() + .idShort("file1") + .contentType("application/json") + .value("SafetyInstructions.json") + .build(); + + public static final Supplier MULTI_LANGUAGE_PROPERTY = () -> new DefaultMultiLanguageProperty.Builder() + .idShort("multiLanguageProp1") + .value(new DefaultLangStringTextType.Builder().text("foo").language("de").build()) + .value(new DefaultLangStringTextType.Builder() .text("bar").language("en").build()) + .build(); + + public static final MultiLanguageProperty MULTI_LANGUAGE_PROPERTY_UPDATED = new DefaultMultiLanguageProperty.Builder() + .idShort("multiLanguageProp1") + .value(new DefaultLangStringTextType.Builder().text("foo updated").language("fr").build()) + .value(new DefaultLangStringTextType.Builder() .text("bar updated").language("de").build()) + .build(); + + public static final Supplier PROPERTY_DOUBLE = () -> new DefaultProperty.Builder() + .category("category") + .idShort("propDouble") + .valueType(DataTypeDefXsd.DOUBLE) + .value("42.17") + .build(); + + public static final Property PROPERTY_DOUBLE_UPDATED = new DefaultProperty.Builder() + .category("category") + .idShort("propDouble") + .valueType(DataTypeDefXsd.DOUBLE) + .value("24.71") + .build(); + + public static final Supplier PROPERTY_DATETIME = () -> new DefaultProperty.Builder() + .category("category") + .idShort("propDateTime") + .valueType(DataTypeDefXsd.DATE_TIME) + .value(ZonedDateTime.of(2022, 7, 31, 17, 8, 51, 0, ZoneOffset.UTC).toString()) + .build(); + + public static final Property PROPERTY_DATETIME_UPDATED = new DefaultProperty.Builder() + .category("category") + .idShort("propDateTime") + .valueType(DataTypeDefXsd.DATE_TIME) + .value(ZonedDateTime.of(2023, 7, 31, 17, 8, 51, 0, ZoneOffset.UTC).toString()) + .build(); + + public static final Supplier PROPERTY_INT = () -> new DefaultProperty.Builder() + .category("category") + .idShort("propInt") + .valueType(DataTypeDefXsd.INT) + .value("42") + .build(); + + public static final Property PROPERTY_INT_UPDATED = new DefaultProperty.Builder() + .category("category") + .idShort("propInt") + .valueType(DataTypeDefXsd.INT) + .value("24") + .build(); + + public static final Supplier RANGE_INT = () -> new DefaultRange.Builder() + .idShort("rangeInt") + .valueType(DataTypeDefXsd.INT) + .min("17") + .max("42") + .build(); + + public static final Range RANGE_INT_UPDATED = new DefaultRange.Builder() + .idShort("rangeInt") + .valueType(DataTypeDefXsd.INT) + .min("24") + .max("50") + .build(); + + public static final Supplier REFERENCE_ELEMENT_GLOBAL = () -> new DefaultReferenceElement.Builder() + .idShort("referenceGlobal") + .value(new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE) + .referredSemanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.CONCEPT_DESCRIPTION) + .value("Concept Description key value") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("global reference key value") + .build()) + .build()) + .build(); + + public static final ReferenceElement REFERENCE_ELEMENT_GLOBAL_UPDATED = new DefaultReferenceElement.Builder() + .idShort("referenceGlobal") + .value(new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE) + .referredSemanticId(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("Global reference key value updated") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.FILE) + .value("file key value") + .build()) + .build()) + .build(); + + public static final Supplier REFERENCE_ELEMENT_MODEL = () -> new DefaultReferenceElement.Builder() + .idShort("referenceModel") + .value(new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.PROPERTY) + .value("MaxRotationSpeed") + .build()) + .build()) + .build(); + + public static final ReferenceElement REFERENCE_ELEMENT_MODEL_UPDATED = new DefaultReferenceElement.Builder() + .idShort("referenceModel") + .value(new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("Global reference key value updated") + .build()) + .build()) + .build(); + + public static final Supplier ANNOTATED_RELATIONSHIP_ELEMENT = () -> new DefaultAnnotatedRelationshipElement.Builder() + .idShort("annotatedRelationship1") + .first(REFERENCE_ELEMENT_GLOBAL.get().getValue()) + .second(REFERENCE_ELEMENT_MODEL.get().getValue()) + .annotations(PROPERTY_DATETIME.get()) + .annotations(RANGE_INT.get()) + .build(); + + public static final AnnotatedRelationshipElement ANNOTATED_RELATIONSHIP_ELEMENT_UPDATED = new DefaultAnnotatedRelationshipElement.Builder() + .idShort("annotatedRelationship1") + .first(REFERENCE_ELEMENT_GLOBAL_UPDATED.getValue()) + .second(REFERENCE_ELEMENT_MODEL_UPDATED.getValue()) + .annotations(PROPERTY_DATETIME_UPDATED) + .annotations(RANGE_INT_UPDATED) + .build(); + + public static final Supplier RELATIONSHIP_ELEMENT = () -> new DefaultRelationshipElement.Builder() + .idShort("relationship1") + .first(REFERENCE_ELEMENT_GLOBAL.get().getValue()) + .second(REFERENCE_ELEMENT_MODEL.get().getValue()) + .build(); + + + public static final RelationshipElement RELATIONSHIP_ELEMENT_UPDATED = new DefaultRelationshipElement.Builder() + .idShort("relationship1") + .first(REFERENCE_ELEMENT_GLOBAL_UPDATED.getValue()) + .second(REFERENCE_ELEMENT_MODEL_UPDATED.getValue()) + .build(); + + public static final Supplier ELEMENT_COLLECTION = () -> new DefaultSubmodelElementCollection.Builder() + .idShort("collection1") + .value(PROPERTY_STRING.get()) + .value(RANGE_DOUBLE.get()) + .value(ENTITY.get()) + .value(RELATIONSHIP_ELEMENT.get()) + .build(); + + public static final SubmodelElementCollection ELEMENT_COLLECTION_UPDATED = new DefaultSubmodelElementCollection.Builder() + .idShort("collection1") + .value(PROPERTY_STRING_UPDATED) + .value(RANGE_DOUBLE_UPDATED) + .value(ENTITY_UPDATED) + .value(RELATIONSHIP_ELEMENT_UPDATED) + .build(); + + public static final Supplier ELEMENT_LIST = () -> new DefaultSubmodelElementList.Builder() + .idShort("list1") + .value(PROPERTY_STRING.get()) + .value(RANGE_DOUBLE.get()) + .value(ENTITY.get()) + .value(ANNOTATED_RELATIONSHIP_ELEMENT.get()) + .build(); + + public static final SubmodelElementList ELEMENT_LIST_UPDATED = new DefaultSubmodelElementList.Builder() + .idShort("list1") + .value(PROPERTY_STRING_UPDATED) + .value(RANGE_DOUBLE_UPDATED) + .value(ENTITY_UPDATED) + .value(ANNOTATED_RELATIONSHIP_ELEMENT_UPDATED) + .build(); + + public static final Supplier SUBMODEL = () -> new DefaultSubmodel.Builder() + .category("category") + .idShort("submodel1") + .kind(ModellingKind.INSTANCE) + .submodelElements(PROPERTY_STRING.get()) + .submodelElements(RANGE_DOUBLE.get()) + .submodelElements(ELEMENT_COLLECTION.get()) + .submodelElements(new DefaultOperation.Builder() + .idShort("operation1") + .build()) + .build(); + + public static final Submodel SUBMODEL_UPDATED = new DefaultSubmodel.Builder() + .category("category") + .idShort("submodel1") + .kind(ModellingKind.INSTANCE) + .submodelElements(PROPERTY_STRING_UPDATED) + .submodelElements(RANGE_DOUBLE_UPDATED) + .submodelElements(ELEMENT_COLLECTION_UPDATED) + .submodelElements(new DefaultOperation.Builder() + .idShort("operation1") + .build()) + .build(); +} diff --git a/dataformat-json/src/test/resources/valueonly/ann_relationship_element.json b/dataformat-json/src/test/resources/valueonly/ann_relationship_element.json new file mode 100644 index 000000000..e0ebd5764 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/ann_relationship_element.json @@ -0,0 +1,42 @@ +{ + "annotatedRelationship1": { + "first": { + "referredSemanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "Concept Description key value" + } + ], + "type": "ExternalReference" + }, + "keys": [ + { + "type": "GlobalReference", + "value": "global reference key value" + } + ], + "type": "ExternalReference" + }, + "second": { + "keys": [ + { + "type": "Property", + "value": "MaxRotationSpeed" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "propDateTime": "2022-07-31T17:08:51Z" + }, + { + "rangeInt": { + "min": 17, + "max": 42 + } + } + ] + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/blob.json b/dataformat-json/src/test/resources/valueonly/blob.json new file mode 100644 index 000000000..c47688e6f --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/blob.json @@ -0,0 +1,4 @@ +{ + "contentType" : "application/octet-stream", + "value" : "ZXhhbXBsZS1kYXRh" +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/date_time_property.json b/dataformat-json/src/test/resources/valueonly/date_time_property.json new file mode 100644 index 000000000..eb26358e0 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/date_time_property.json @@ -0,0 +1,3 @@ +{ + "propDateTime": "2022-07-31T17:08:51Z" +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/element_collection.json b/dataformat-json/src/test/resources/valueonly/element_collection.json new file mode 100644 index 000000000..ef42617c3 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/element_collection.json @@ -0,0 +1,47 @@ +{ + "collection1": { + "propString": "foo", + "rangeDouble": { + "min": 3.0, + "max": 5.0 + }, + "entity1": { + "statements": [ + { + "maxRotationSpeed": 5000 + } + ], + "globalAssetId": "Global Asset Id", + "entityType": "SelfManagedEntity" + }, + "relationship1": { + "first": { + "referredSemanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "Concept Description key value" + } + ], + "type": "ExternalReference" + }, + "keys": [ + { + "type": "GlobalReference", + "value": "global reference key value" + } + ], + "type": "ExternalReference" + }, + "second": { + "keys": [ + { + "type": "Property", + "value": "MaxRotationSpeed" + } + ], + "type": "ModelReference" + } + } + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/element_list.json b/dataformat-json/src/test/resources/valueonly/element_list.json new file mode 100644 index 000000000..6d338fab2 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/element_list.json @@ -0,0 +1,64 @@ +[ + { + "propString": "foo" + }, + { + "rangeDouble": { + "min": 3.0, + "max": 5.0 + } + }, + { + "entity1": { + "statements": [ + { + "maxRotationSpeed": 5000 + } + ], + "globalAssetId": "Global Asset Id", + "entityType": "SelfManagedEntity" + } + }, + { + "annotatedRelationship1": { + "first": { + "referredSemanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "Concept Description key value" + } + ], + "type": "ExternalReference" + }, + "keys": [ + { + "type": "GlobalReference", + "value": "global reference key value" + } + ], + "type": "ExternalReference" + }, + "second": { + "keys": [ + { + "type": "Property", + "value": "MaxRotationSpeed" + } + ], + "type": "ModelReference" + }, + "annotations": [ + { + "propDateTime": "2022-07-31T17:08:51Z" + }, + { + "rangeInt": { + "min": 17, + "max": 42 + } + } + ] + } + } +] \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/entity.json b/dataformat-json/src/test/resources/valueonly/entity.json new file mode 100644 index 000000000..a5b8435a1 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/entity.json @@ -0,0 +1,11 @@ +{ + "entity1": { + "statements": [ + { + "maxRotationSpeed": 5000 + } + ], + "globalAssetId": "Global Asset Id", + "entityType": "SelfManagedEntity" + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/file.json b/dataformat-json/src/test/resources/valueonly/file.json new file mode 100644 index 000000000..fa87bdd03 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/file.json @@ -0,0 +1,4 @@ +{ + "contentType" : "application/pdf", + "value" : "SafetyInstructions.pdf" +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/multi_lang_property.json b/dataformat-json/src/test/resources/valueonly/multi_lang_property.json new file mode 100644 index 000000000..b39b3e33f --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/multi_lang_property.json @@ -0,0 +1,4 @@ +{ + "de" : "foo", + "en" : "bar" +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/property_double.json b/dataformat-json/src/test/resources/valueonly/property_double.json new file mode 100644 index 000000000..c97edc2f4 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/property_double.json @@ -0,0 +1,3 @@ +{ + "propDouble": 42.17 +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/property_int.json b/dataformat-json/src/test/resources/valueonly/property_int.json new file mode 100644 index 000000000..74e12fe45 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/property_int.json @@ -0,0 +1,3 @@ +{ + "propInt": 42 +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/property_string.json b/dataformat-json/src/test/resources/valueonly/property_string.json new file mode 100644 index 000000000..d7448dce7 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/property_string.json @@ -0,0 +1,3 @@ +{ + "propString": "foo" +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/range.json b/dataformat-json/src/test/resources/valueonly/range.json new file mode 100644 index 000000000..99a53d249 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/range.json @@ -0,0 +1,6 @@ +{ + "rangeDouble": { + "min": 3.0, + "max": 5.0 + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/ref_element_global.json b/dataformat-json/src/test/resources/valueonly/ref_element_global.json new file mode 100644 index 000000000..05c86e833 --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/ref_element_global.json @@ -0,0 +1,20 @@ +{ + "referenceGlobal": { + "referredSemanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "Concept Description key value" + } + ], + "type": "ExternalReference" + }, + "keys": [ + { + "type": "GlobalReference", + "value": "global reference key value" + } + ], + "type": "ExternalReference" + } +} \ No newline at end of file diff --git a/dataformat-json/src/test/resources/valueonly/submodel.json b/dataformat-json/src/test/resources/valueonly/submodel.json new file mode 100644 index 000000000..94fb51efc --- /dev/null +++ b/dataformat-json/src/test/resources/valueonly/submodel.json @@ -0,0 +1,46 @@ +{ + "propString" : "foo", + "rangeDouble" : { + "min" : 3.0, + "max" : 5.0 + }, + "collection1" : { + "propString" : "foo", + "rangeDouble" : { + "min" : 3.0, + "max" : 5.0 + }, + "entity1" : { + "statements": [ + { + "maxRotationSpeed": 5000 + } + ], + "globalAssetId": "Global Asset Id", + "entityType": "SelfManagedEntity" + }, + "relationship1" : { + "first" : { + "referredSemanticId" : { + "keys" : [ { + "type" : "ConceptDescription", + "value" : "Concept Description key value" + } ], + "type" : "ExternalReference" + }, + "keys" : [ { + "type" : "GlobalReference", + "value" : "global reference key value" + } ], + "type" : "ExternalReference" + }, + "second" : { + "keys" : [ { + "type" : "Property", + "value" : "MaxRotationSpeed" + } ], + "type" : "ModelReference" + } + } + } +} \ No newline at end of file From 9808450c449a578f6a0f569da6a305ceae9decb1 Mon Sep 17 00:00:00 2001 From: Emil Dinchev Date: Mon, 22 Apr 2024 14:27:06 +0200 Subject: [PATCH 2/5] Update range.json --- dataformat-json/src/test/resources/valueonly/range.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataformat-json/src/test/resources/valueonly/range.json b/dataformat-json/src/test/resources/valueonly/range.json index 99a53d249..90131d233 100644 --- a/dataformat-json/src/test/resources/valueonly/range.json +++ b/dataformat-json/src/test/resources/valueonly/range.json @@ -1,6 +1,6 @@ { "rangeDouble": { - "min": 3.0, + "min": 3.0, "max": 5.0 } } \ No newline at end of file From 13492b4f39735cebf64db2d41b560966b59cb144 Mon Sep 17 00:00:00 2001 From: Emil Dinchev Date: Mon, 22 Apr 2024 14:36:04 +0200 Subject: [PATCH 3/5] Update pom.xml --- dataformat-json/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dataformat-json/pom.xml b/dataformat-json/pom.xml index 04ab7de87..996cf0bc4 100644 --- a/dataformat-json/pom.xml +++ b/dataformat-json/pom.xml @@ -14,7 +14,7 @@ ${project.groupId} - dataformat-core + dataformat-core ${project.groupId} From d72908bb613929cdce8f07a04a6b7520c29fdc76 Mon Sep 17 00:00:00 2001 From: Emil Dinchev Date: Mon, 22 Apr 2024 15:24:47 +0200 Subject: [PATCH 4/5] Update EntityMapper.java --- .../aas4j/v3/dataformat/json/valueonly/EntityMapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java index d923a48eb..8263fc6d0 100644 --- a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java @@ -19,7 +19,6 @@ import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer; import org.eclipse.digitaltwin.aas4j.v3.model.Entity; import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; @@ -28,6 +27,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer.serializeEnumName; /** * Entity is serialized as named JSON object with ${Entity/idShort} as the name of the containing JSON property. The * JSON object contains three JSON properties. The first is named “statements” ${Entity/statements} and contains an @@ -65,7 +65,7 @@ public JsonNode toJson() throws ValueOnlySerializationException { } node.set(SPECIFIC_ASSET_ID, assetIdNode); } - node.set(ENTITY_TYPE, new TextNode(EnumSerializer.serializeEnumName(element.getEntityType().name()))); + node.set(ENTITY_TYPE, new TextNode(serializeEnumName(element.getEntityType().name()))); return asValueNode(node); } From 78d0e332dc1d17291501e5ec85eeff29f3468222 Mon Sep 17 00:00:00 2001 From: Emil Dinchev Date: Mon, 22 Apr 2024 15:33:55 +0200 Subject: [PATCH 5/5] Update EntityMapper.java --- .../aas4j/v3/dataformat/json/valueonly/EntityMapper.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java index 8263fc6d0..b20d94060 100644 --- a/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java +++ b/dataformat-json/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/json/valueonly/EntityMapper.java @@ -18,7 +18,6 @@ import java.util.Iterator; import java.util.List; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer; import org.eclipse.digitaltwin.aas4j.v3.model.Entity; import org.eclipse.digitaltwin.aas4j.v3.model.EntityType; import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; @@ -27,7 +26,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; -import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer.serializeEnumName; +import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.serialization.EnumSerializer.serializeEnumName; +import static org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.deserialization.EnumDeserializer.deserializeEnumName; + /** * Entity is serialized as named JSON object with ${Entity/idShort} as the name of the containing JSON property. The * JSON object contains three JSON properties. The first is named “statements” ${Entity/statements} and contains an @@ -102,7 +103,7 @@ public void update(JsonNode valueOnly) throws ValueOnlySerializationException { throw new ValueOnlySerializationException("Cannot update the Entity at idShort path '" + idShortPath + "', as its type is not set as string property '" + ENTITY_TYPE + "'.", idShortPath); } - element.setEntityType(EntityType.valueOf(EnumDeserializer.deserializeEnumName(entityTypeNode.textValue()))); + element.setEntityType(EntityType.valueOf(deserializeEnumName(entityTypeNode.textValue()))); } private void updateSpecificAssetIds(List specificAssetIds, ObjectNode objNode)