From f72c0e6fc807ec43b5c0599cbadbb59c933c87a5 Mon Sep 17 00:00:00 2001 From: jannisjung <92165516+jannisjung@users.noreply.github.com> Date: Tue, 7 Mar 2023 08:21:30 +0100 Subject: [PATCH] Feature/storage interface (#234) Signed-off-by: Jannis Jung --- .../json/BidirectionalJSONConverter.java | 106 ++++++++++ ...nectedAssetAdministrationShellManager.java | 25 ++- .../map/descriptor/AASDescriptor.java | 16 ++ .../extensions/storage/BaSyxStorageAPI.java | 186 ++++++++++++++++++ .../extensions/storage/IBaSyxStorageAPI.java | 101 ++++++++++ .../aggregator/SubmodelAggregator.java | 30 +-- .../coder/json/serialization/GSONTools.java | 108 ++++++++++ .../storage/BaSyxStorageAPISuite.java | 92 +++++++++ .../extensions/storage/VABTestType.java | 174 ++++++++++++++++ 9 files changed, 821 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/eclipse/basyx/aas/factory/json/BidirectionalJSONConverter.java create mode 100644 src/main/java/org/eclipse/basyx/extensions/storage/BaSyxStorageAPI.java create mode 100644 src/main/java/org/eclipse/basyx/extensions/storage/IBaSyxStorageAPI.java create mode 100644 src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/BaSyxStorageAPISuite.java create mode 100644 src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/VABTestType.java diff --git a/src/main/java/org/eclipse/basyx/aas/factory/json/BidirectionalJSONConverter.java b/src/main/java/org/eclipse/basyx/aas/factory/json/BidirectionalJSONConverter.java new file mode 100644 index 00000000..5b0ea908 --- /dev/null +++ b/src/main/java/org/eclipse/basyx/aas/factory/json/BidirectionalJSONConverter.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.aas.factory.json; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.basyx.aas.metamodel.map.AssetAdministrationShell; +import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor; +import org.eclipse.basyx.submodel.metamodel.api.ISubmodel; +import org.eclipse.basyx.submodel.metamodel.api.reference.IKey; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.metamodel.map.reference.Key; +import org.eclipse.basyx.vab.coder.json.serialization.DefaultTypeFactory; +import org.eclipse.basyx.vab.coder.json.serialization.GSONTools; + +/** + * Serializes and deserializes shells and submodels to and from json + * + * @author jungjan + * + */ +public class BidirectionalJSONConverter { + private static GSONTools gsonTools = new GSONTools(new DefaultTypeFactory()); + + public static String serializeSubmodel(ISubmodel submodel) { + return gsonTools.serialize(submodel); + } + + @SuppressWarnings("unchecked") + public static ISubmodel deserializeSubmodel(String jsonSubmodel) { + return Submodel.createAsFacade((Map) gsonTools.deserialize(jsonSubmodel)); + } + + public static String serializeShell(AssetAdministrationShell shell) { + return gsonTools.serialize(shell); + } + + @SuppressWarnings("unchecked") + public static AssetAdministrationShell deserializeShell(String jsonShell) { + return AssetAdministrationShell.createAsFacade((Map) gsonTools.deserialize(jsonShell)); + } + + public static String serializeObject(T object) { + return gsonTools.serialize(object); + } + + @SuppressWarnings("unchecked") + public static T deserializeJSON(String json) { + T retrieved = (T) gsonTools.deserialize(json); + return retrieved; + } + + @SuppressWarnings("unchecked") + public static AASDescriptor deserializeAASDescriptor(String json) { + return AASDescriptor.createAsFacade((Map) gsonTools.deserialize(json)); + } + + /** + * + * @param submodel + * @return the semanticId of a submodel serialized according to the following schema: + * {@code {type:;value:;idType:[/]}}. + * Example: {@code type:Submodel;value:a value;idType:Custom/type:Submodel;value:another value;idType:Custom} + */ + public static String semanticIdAsSString(ISubmodel submodel) { + if (submodel.getSemanticId() == null) { + return null; + } + List keys = submodel.getSemanticId().getKeys(); + return semanticIdKeysToString(keys); + } + + public static String semanticIdKeysToString(List keys) { + return keys.stream().map(k -> + Key.TYPE + ":" + k.getType() + ";" + /* + Key.LOCAL + ":" + k.isLocal() + ";" */ //ignoring local since it won't be relevant for V3 + + Key.VALUE + ":" + k.getValue() + ";" + + Key.IDTYPE + ":" + k.getIdType()) + .collect(Collectors.joining("/")); + } + +} diff --git a/src/main/java/org/eclipse/basyx/aas/manager/ConnectedAssetAdministrationShellManager.java b/src/main/java/org/eclipse/basyx/aas/manager/ConnectedAssetAdministrationShellManager.java index db284bde..40b48918 100644 --- a/src/main/java/org/eclipse/basyx/aas/manager/ConnectedAssetAdministrationShellManager.java +++ b/src/main/java/org/eclipse/basyx/aas/manager/ConnectedAssetAdministrationShellManager.java @@ -1,6 +1,27 @@ -/** +/******************************************************************************* + * Copyright (C) 2021 the Eclipse BaSyx Authors * - */ + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ package org.eclipse.basyx.aas.manager; import java.util.Collection; diff --git a/src/main/java/org/eclipse/basyx/aas/metamodel/map/descriptor/AASDescriptor.java b/src/main/java/org/eclipse/basyx/aas/metamodel/map/descriptor/AASDescriptor.java index b28b8d6e..376ccce3 100644 --- a/src/main/java/org/eclipse/basyx/aas/metamodel/map/descriptor/AASDescriptor.java +++ b/src/main/java/org/eclipse/basyx/aas/metamodel/map/descriptor/AASDescriptor.java @@ -227,4 +227,20 @@ public void validate(Map map) { throw new MalformedRequestException("Passed entry for " + AssetAdministrationShell.SUBMODELS + " is not a list of submodelDescriptors!"); } } + + /** + * Creates the AASDescriptor from a given map + * + * @param map + * @return + */ + public static AASDescriptor createAsFacade(Map map) { + if (map == null) { + return null; + } + + AASDescriptor ret = new AASDescriptor(); + ret.setMap(map); + return ret; + } } diff --git a/src/main/java/org/eclipse/basyx/extensions/storage/BaSyxStorageAPI.java b/src/main/java/org/eclipse/basyx/extensions/storage/BaSyxStorageAPI.java new file mode 100644 index 00000000..0ef11a8a --- /dev/null +++ b/src/main/java/org/eclipse/basyx/extensions/storage/BaSyxStorageAPI.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.extensions.storage; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.basyx.aas.metamodel.api.IAssetAdministrationShell; +import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor; +import org.eclipse.basyx.submodel.metamodel.api.ISubmodel; +import org.eclipse.basyx.submodel.metamodel.api.qualifier.IIdentifiable; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; +import org.eclipse.basyx.submodel.metamodel.facade.submodelelement.SubmodelElementFacadeFactory; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class to create a storage API. The generic type {@code } + * determines the type of Objects, this API should handle. One API can handle + * exactly one type of object i.e it manages exactly one data collection within + * the remote storage. + * + * @author jungjan + * + * @param + * The type of Objects to be handled + */ +public abstract class BaSyxStorageAPI implements IBaSyxStorageAPI { + protected Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final String COLLECTION_NAME; + protected final Class TYPE; + + /** + * + * @param collectionName + * The name of the collection, managed by this API + * @param type + * Must be the exact same type as the type of the generic parameter + * {@code } + */ + public BaSyxStorageAPI(String collectionName, Class type) { + COLLECTION_NAME = collectionName; + TYPE = type; + } + + /** + * DISCLAIMER: Currently only supports to extract keys from IIdentifiables. + * Helper method that extracts a key for persistence storage requests from an + * object. + * + * @param obj + * An object that contains a key that can be used to find the + * persisted version of the object. + * @return The key + */ + protected String getKey(T obj) { + if (!(obj instanceof IIdentifiable)) { + throw new IllegalArgumentException("Can only extract a key from a object of type " + IIdentifiable.class.getName()); + } + return ((IIdentifiable) obj).getIdentification().getId(); + } + + /** + * Retrieves an object by its key from the persistence storage. + * + * The result of this retrieval will then be further processed by the + * {@link #retrieve(String)} method to ensure it is interpreted correctly. + * + * @param key + * The key of the object to be retrieved + * @return The expected object if successful + * @param key + * @return + */ + public abstract T rawRetrieve(String key); + + /** + * Returns a Object that was originally retrieved from the abstract method + * {@code rawRetrieve}. If the object to be returned is a submodel type, it will + * be updated with a correct interpretation of is's SubmodelElements. + */ + @SuppressWarnings("unchecked") + @Override + public T retrieve(String key) { + T retrieved = rawRetrieve(key); + if (retrieved != null && isSubmodelType(retrieved.getClass())) { + return (T) handleRetrievedSubmodel((Submodel) retrieved); + } + return retrieved; + } + + /** + * Helper to bring the SubmodelElements a retrieved SubmodelObject to the + * correct Type of ISubmodelElements. Background: SubmodelsElements are + * typically retrieved from a persistence storage within a Submodel and come in + * the raw form of {@code Map>}. This method + * processes the raw SubmodelElements to a + * {@code Map}. + * + * @param retrieved + * The retrieved submodel with raw SubmodelElements + * @return An updated version of the submodel with correctly interpreted + * SubmodelElements + */ + @SuppressWarnings("unchecked") + protected Submodel handleRetrievedSubmodel(Submodel retrieved) { + Map> elementMaps = (Map>) retrieved.get(Submodel.SUBMODELELEMENT); + Map elements = forceToISubmodelElements(elementMaps); + retrieved.put(Submodel.SUBMODELELEMENT, elements); + return retrieved; + } + + /** + * Converts raw SubmodelElements from a retrieved SubmodelObject to + * ISubmodelElements. Background: SubmodelsElements are typically retrieved from + * a persistence storage within a Submodel and come in the raw form of + * {@code Map>}. This method processes the raw + * SubmodelElements to a {@code Map}. + * + * @param submodelElementObjectMap + * The raw SubmodelElements (typically retrieved within a submdoel -> + * can be get with {@code submodel.get(Submodel.SUBMODELELEMENT)})) + * @return A map in the expected form of {@code Map} + */ + private Map forceToISubmodelElements(Map> submodelElementObjectMap) { + Map elements = new HashMap<>(); + + submodelElementObjectMap.forEach((idShort, elementMap) -> { + ISubmodelElement element = SubmodelElementFacadeFactory.createSubmodelElement(elementMap); + elements.put(idShort, element); + }); + return elements; + } + + /* + * Not yet tested + */ + protected boolean isSubmodelType(Class type) { + return ISubmodel.class.isAssignableFrom(type); + } + + /* + * Not yet tested + */ + protected boolean isShellType(Class type) { + return IAssetAdministrationShell.class.isAssignableFrom(type); + } + + /* + * Not yet tested + */ + protected boolean isAASDescriptorType(Class type) { + return AASDescriptor.class.isAssignableFrom(type); + } + + /* + * Not yet tested + */ + protected boolean isBaSyxType(Class type) { + return (isShellType(type) || isSubmodelType(type) || isAASDescriptorType(type)); + } +} diff --git a/src/main/java/org/eclipse/basyx/extensions/storage/IBaSyxStorageAPI.java b/src/main/java/org/eclipse/basyx/extensions/storage/IBaSyxStorageAPI.java new file mode 100644 index 00000000..9ab97333 --- /dev/null +++ b/src/main/java/org/eclipse/basyx/extensions/storage/IBaSyxStorageAPI.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.extensions.storage; + +import java.util.Collection; + +/** + * Provides basic methods to write data into or read and delete data from a + * persistence storage + * + * @author jungjan + */ +public interface IBaSyxStorageAPI { + + /** + * Creates or updates objects in the persistence storage + * + * @param obj + * The object to be created or updated + * @return The created or updated object if successful + */ + public T createOrUpdate(T obj); + + /** + * Updates objects in the persistence storage + * + * @param obj + * The object with the updated information + * @param key + * The key of the object to be updated + * @return The updated object if successful + */ + public T update(T obj, String key); + + /** + * Retrieves an object by it's key from the persistence storage + * + * @param key + * The key of the object to be retrieved + * @return The expected object if successful + */ + public T retrieve(String key); + + /** + * Retrieves all object of one type from the persistence storage Collection + * items) + * + * @return All objects of the specified type that are persistent in the storage + */ + public Collection retrieveAll(); + + /** + * Deletes an object by it's key from the persistence storage + * + * @param key + * The key of the object to be deleted + * @return true if successful else false + */ + public boolean delete(String key); + + /** + * Creates a collection if it does not already exist, whereby "collection" is a + * data container in the context of the respective persistence storage. For the + * concrete persistence storage, another name may be common: e.g. for relational + * database systems: "table", for S3: "bucket". + * + * @param collectionName + * The name of the collection + */ + public void createCollectionIfNotExists(String collectionName); + + /** + * Deletes a collection, whereby "collection" is a data container in the context + * of the respective persistence storage. For the concrete persistence storage, + * another name may be common: e.g. for relational database systems: "table", + * for S3: "bucket". + */ + public void deleteCollection(); +} diff --git a/src/main/java/org/eclipse/basyx/submodel/aggregator/SubmodelAggregator.java b/src/main/java/org/eclipse/basyx/submodel/aggregator/SubmodelAggregator.java index d7071f0e..3e082e3c 100644 --- a/src/main/java/org/eclipse/basyx/submodel/aggregator/SubmodelAggregator.java +++ b/src/main/java/org/eclipse/basyx/submodel/aggregator/SubmodelAggregator.java @@ -50,26 +50,26 @@ public class SubmodelAggregator implements ISubmodelAggregator { private static final Logger logger = LoggerFactory.getLogger(SubmodelAggregator.class); - protected Map smApiMap = new HashMap<>(); + protected Map submodelApiMap = new HashMap<>(); /** * Store Submodel API Provider. By default, uses the VAB Submodel Provider */ - protected ISubmodelAPIFactory smApiFactory; + protected ISubmodelAPIFactory submodelApiFactory; public SubmodelAggregator() { - smApiFactory = new VABSubmodelAPIFactory(); + submodelApiFactory = new VABSubmodelAPIFactory(); } - public SubmodelAggregator(ISubmodelAPIFactory smApiFactory) { - this.smApiFactory = smApiFactory; + public SubmodelAggregator(ISubmodelAPIFactory submodelApiFactory) { + this.submodelApiFactory = submodelApiFactory; } @Override public Collection getSubmodelList() { - return smApiMap.values().stream().map(smApi -> { + return submodelApiMap.values().stream().map(submodelApi -> { try { - return smApi.getSubmodel(); + return submodelApi.getSubmodel(); } catch (final NotAuthorizedException e) { logger.info(e.getMessage(), e); } @@ -84,7 +84,7 @@ public ISubmodel getSubmodel(IIdentifier identifier) throws ResourceNotFoundExce } private String getIdShort(IIdentifier identifier) { - for (ISubmodelAPI api : smApiMap.values()) { + for (ISubmodelAPI api : submodelApiMap.values()) { ISubmodel submodel = api.getSubmodel(); String idValue = submodel.getIdentification().getId(); if (idValue.equals(identifier.getId())) { @@ -101,18 +101,18 @@ public void createSubmodel(Submodel submodel) { @Override public void updateSubmodel(Submodel submodel) throws ResourceNotFoundException { - ISubmodelAPI submodelAPI = smApiFactory.create(submodel); + ISubmodelAPI submodelAPI = submodelApiFactory.create(submodel); createSubmodel(submodelAPI); } @Override public void createSubmodel(ISubmodelAPI submodelAPI) { - smApiMap.put(submodelAPI.getSubmodel().getIdShort(), submodelAPI); + submodelApiMap.put(submodelAPI.getSubmodel().getIdShort(), submodelAPI); } @Override public ISubmodel getSubmodelbyIdShort(String idShort) throws ResourceNotFoundException { - ISubmodelAPI api = smApiMap.get(idShort); + ISubmodelAPI api = submodelApiMap.get(idShort); if (api == null) { throw new ResourceNotFoundException("The submodel with idShort '" + idShort + "' could not be found"); } else { @@ -124,25 +124,25 @@ public ISubmodel getSubmodelbyIdShort(String idShort) throws ResourceNotFoundExc public void deleteSubmodelByIdentifier(IIdentifier identifier) { try { String idShort = getIdShort(identifier); - smApiMap.remove(idShort); + submodelApiMap.remove(idShort); } catch (ResourceNotFoundException exception) { } } @Override public void deleteSubmodelByIdShort(String idShort) { - smApiMap.remove(idShort); + submodelApiMap.remove(idShort); } @Override public ISubmodelAPI getSubmodelAPIById(IIdentifier identifier) throws ResourceNotFoundException { String idShort = getIdShort(identifier); - return smApiMap.get(idShort); + return submodelApiMap.get(idShort); } @Override public ISubmodelAPI getSubmodelAPIByIdShort(String idShort) throws ResourceNotFoundException { - ISubmodelAPI api = smApiMap.get(idShort); + ISubmodelAPI api = submodelApiMap.get(idShort); if (api == null) { throw new ResourceNotFoundException("The submodel with idShort '" + idShort + "' could not be found"); } diff --git a/src/main/java/org/eclipse/basyx/vab/coder/json/serialization/GSONTools.java b/src/main/java/org/eclipse/basyx/vab/coder/json/serialization/GSONTools.java index e868a4d4..f5e71fd4 100644 --- a/src/main/java/org/eclipse/basyx/vab/coder/json/serialization/GSONTools.java +++ b/src/main/java/org/eclipse/basyx/vab/coder/json/serialization/GSONTools.java @@ -32,8 +32,10 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.math.BigInteger; +import java.util.Arrays; import java.util.Base64; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.BiConsumer; @@ -158,6 +160,8 @@ private JsonElement serializeObject(Object obj) { return serializeMap((Map) obj); } else if (obj instanceof Collection) { return serializeCollection((Collection) obj); + } else if (obj.getClass().isArray()) { + return serializeArray(obj); } else if (isFunction(obj)) { return serializeFunction(obj); } @@ -333,6 +337,110 @@ private JsonArray serializeCollection(Collection collection) { return array; } + /** + * Serializes an array to a JsonArray and adds index where appropriate + * + * @param array + * @return + */ + private JsonArray serializeArray(Object array) { + if (!isPrimitiveArray(array)) { + List arrayList = Arrays.asList(array); + return serializeCollection(arrayList); + } + return serializePrimitiveJsonArray(array); + } + + private JsonArray serializePrimitiveJsonArray(Object array) { + if (array instanceof boolean[]) { + return serializeBooleanArray((boolean[]) array); + } + if (array instanceof byte[]) { + return serializeBytenArray((byte[]) array); + } + if (array instanceof char[]) { + return serializeCharArray((char[]) array); + } + if (array instanceof short[]) { + return serializeShortnArray((short[]) array); + } + if (array instanceof int[]) { + return serializeIntArray((int[]) array); + } + if (array instanceof long[]) { + return serializeLongArray((long[]) array); + } + if (array instanceof float[]) { + return serializeFloatArray((float[]) array); + } + if (array instanceof double[]) { + return serializeDoubleArray((double[]) array); + } + throw new RuntimeException("Array is not primitive!"); + } + + private JsonArray serializeBooleanArray(boolean[] array) { + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.add(array[i]); + } + return jsonArray; + } + + private JsonArray serializeBytenArray(byte[] array) { + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.add(array[i]); + } + return jsonArray; + } + + private JsonArray serializeCharArray(char[] array) { + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.add(array[i]); + } + return jsonArray; + } + + private JsonArray serializeShortnArray(short[] array) { + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.add(array[i]); + } + return jsonArray; + } + + private JsonArray serializeIntArray(int[] array) { + JsonArray jsonArray = new JsonArray(); + Arrays.stream(array).forEachOrdered(jsonArray::add); + return jsonArray; + } + + private JsonArray serializeLongArray(long[] array) { + JsonArray jsonArray = new JsonArray(); + Arrays.stream(array).forEachOrdered(jsonArray::add); + return jsonArray; + } + + private JsonArray serializeFloatArray(float[] array) { + JsonArray jsonArray = new JsonArray(); + for (int i = 0; i < array.length; i++) { + jsonArray.add(array[i]); + } + return jsonArray; + } + + private JsonArray serializeDoubleArray(double[] array) { + JsonArray jsonArray = new JsonArray(); + Arrays.stream(array).forEachOrdered(jsonArray::add); + return jsonArray; + } + + private boolean isPrimitiveArray(Object array) { + return array.getClass().getComponentType().isPrimitive(); + } + /** * Serializes a function if possible * diff --git a/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/BaSyxStorageAPISuite.java b/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/BaSyxStorageAPISuite.java new file mode 100644 index 00000000..c3aa15e0 --- /dev/null +++ b/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/BaSyxStorageAPISuite.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.testsuite.regression.extensions.storage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.eclipse.basyx.extensions.storage.BaSyxStorageAPI; +import org.eclipse.basyx.submodel.metamodel.api.identifier.IdentifierType; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Testsuite for implementations of the {@link BaSyxStorageAPI} abstract class + * + * DISCLAIMER: Some data created by this Testsuite is explicitly NOT removed + * from the persistence storage to ensure nothing important deleted by accident. + * + * @author jungjan, fried + * + */ +public abstract class BaSyxStorageAPISuite { + + private static final Identifier SUBMODEL_IDENTIFIER = new Identifier(IdentifierType.CUSTOM, "testSubmodelIidentifier"); + protected static Submodel testType = new Submodel("testSubmodel", SUBMODEL_IDENTIFIER); + private BaSyxStorageAPI storageAPI; + + protected abstract BaSyxStorageAPI getStorageAPI(); + + @Before + public void setUp() { + storageAPI = getStorageAPI(); + } + + @After + public void cleanUp() { + storageAPI.delete(testType.getIdentification().getId()); + } + + @Test + public void retrieve() { + storageAPI.createOrUpdate(testType); + Submodel actual = storageAPI.retrieve(testType.getIdentification().getId()); + assertEquals(testType, actual); + } + + @Test + public void update() { + storageAPI.createOrUpdate(testType); + testType.setIdShort("updated"); + Submodel actual = storageAPI.createOrUpdate(testType); + + assertEquals(testType, actual); + } + + @Test + public void delete() { + System.out.println(testType); + storageAPI.createOrUpdate(testType); + assertTrue(storageAPI.delete(testType.getIdentification().getId())); + Collection allElements = storageAPI.retrieveAll(); + assertFalse(allElements.contains(testType)); + } +} diff --git a/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/VABTestType.java b/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/VABTestType.java new file mode 100644 index 00000000..8eaa9364 --- /dev/null +++ b/src/test/java/org/eclipse/basyx/testsuite/regression/extensions/storage/VABTestType.java @@ -0,0 +1,174 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.testsuite.regression.extensions.storage; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.NotImplementedException; +import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier; +import org.eclipse.basyx.submodel.metamodel.api.qualifier.IAdministrativeInformation; +import org.eclipse.basyx.submodel.metamodel.api.qualifier.IIdentifiable; +import org.eclipse.basyx.submodel.metamodel.api.reference.IReference; +import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; +import org.eclipse.basyx.submodel.metamodel.map.qualifier.LangStrings; +import org.eclipse.basyx.vab.model.VABModelMap; + +public class VABTestType extends VABModelMap implements IIdentifiable { + // Keys + public static final String TEST_ID = "testId"; + public static final String TEST_STR = "testStr"; + public static final String TEST_INT = "testInt"; + public static final String TEST_ARRAY = "testArray"; + public static final String TEST_COLLECTION = "testCollection"; + public static final String TEST_MAP = "testMap"; + public static final String IDENTIFICATION = "identification"; + + // DefaultValues + public static final String DEFAULT_ID = "test_id"; + public static final String DEFAULT_STR = "str"; + public static final int DEFAULT_INT = 42; + public static final int[] DEFAULT_ARRAY = { -1, 0, 1 }; + + private static final String[] STRING_ARRAY = { DEFAULT_STR + 0, DEFAULT_STR + 1, DEFAULT_STR + 2 }; + public static final Collection DEFAULT_COLLECTION = Arrays.asList(STRING_ARRAY); + public static final Map DEFAULT_MAP = DEFAULT_COLLECTION.stream().collect(Collectors.toMap(entry -> entry.substring(entry.length() - 1), entry -> entry)); + + public VABTestType() { + setId(DEFAULT_ID); + setTestStr(DEFAULT_STR); + setTestInt(DEFAULT_INT); + setTestArray(DEFAULT_ARRAY); + setTestCollection(DEFAULT_COLLECTION); + setTestMap(DEFAULT_MAP); + } + + public VABTestType(String id, String testStr, int testInt, int[] testArray, Collection testCollection, Map testMap) { + setId(id); + setTestStr(testStr); + setTestInt(testInt); + setTestArray(testArray); + setTestCollection(testCollection); + setTestMap(testMap); + } + + // copy constructor + public VABTestType(VABTestType clone) { + super(clone.map); + } + + public void setId(String id) { + IIdentifier identification = getIdentification(); + if (identification == null) { + setIdentification(new Identifier()); + identification = getIdentification(); + } + ((Identifier) identification).setId(id); + } + + + public String getTestStr() { + return (String) get(TEST_STR); + } + + public void setTestStr(String testStr) { + put(TEST_STR, testStr); + } + + public int getTestInt() { + return (int) get(TEST_INT); + } + + public void setTestInt(int testInt) { + put(TEST_INT, testInt); + } + + public int[] getTestArray() { + return (int[]) get(TEST_ARRAY); + } + + public void setTestArray(int[] testArray) { + put(TEST_ARRAY, testArray); + } + + @SuppressWarnings("unchecked") + public Collection getTestCollection() { + return (Collection) get(TEST_COLLECTION); + } + + public void setTestCollection(Collection testCollection) { + put(TEST_COLLECTION, testCollection); + } + + @SuppressWarnings("unchecked") + public Map getTestMap() { + return (Map) get(TEST_MAP); + } + + public void setTestMap(Map testMap) { + put(TEST_MAP, testMap); + } + + public void setIdentification(IIdentifier identifier) { + put(IDENTIFICATION, identifier); + } + + @Override + public IIdentifier getIdentification() { + return (IIdentifier) get(IDENTIFICATION); + } + + @Override + public String getIdShort() { + throw new NotImplementedException(); + } + + @Override + public String getCategory() { + throw new NotImplementedException(); + } + + @Override + public LangStrings getDescription() { + throw new NotImplementedException(); + } + + @Override + public IReference getParent() { + throw new NotImplementedException(); + } + + @Override + public IReference getReference() { + throw new NotImplementedException(); + } + + @Override + public IAdministrativeInformation getAdministration() { + throw new NotImplementedException(); + } +}