diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java new file mode 100644 index 000000000..59fed0ddd --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/ElementNotAFileException.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * 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.digitaltwin.basyx.core.exceptions; + +/** + * Indicates that the requested submodel element is not a File SubmodelElement + * + * @author danish + * + */ +@SuppressWarnings("serial") +public class ElementNotAFileException extends RuntimeException { + public ElementNotAFileException() { + } + + public ElementNotAFileException(String elementId) { + super(getMsg(elementId)); + } + + private static String getMsg(String elementId) { + return "SubmodelElement with Id " + elementId + " is not a File"; + } +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java new file mode 100644 index 000000000..1d13ab9b3 --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FeatureNotImplementedException.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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.digitaltwin.basyx.core.exceptions; + +/** + * This exception is used for features where certain functionalities are not implemented yet. + * + * @author zhangzai + * + */ +@SuppressWarnings("serial") +public class FeatureNotImplementedException extends RuntimeException { + + public FeatureNotImplementedException() { + super(); + } + + public FeatureNotImplementedException(String featureName) { + super(getMessage(featureName)); + } + + private static String getMessage(String featureName) { + return "Feature " + featureName + " is not implemented yet"; + } +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java new file mode 100644 index 000000000..bc1ae701f --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileDoesNotExistException.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * 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.digitaltwin.basyx.core.exceptions; + +/** + * Indicates that the requested file does not exist + * + * @author danish + * + */ +@SuppressWarnings("serial") +public class FileDoesNotExistException extends RuntimeException { + public FileDoesNotExistException() { + } + + public FileDoesNotExistException(String elementId) { + super(getMsg(elementId)); + } + + private static String getMsg(String elementId) { + return "Requested File inside File SubmodelElement with ID : " + elementId + " does not exist"; + } + +} diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java new file mode 100644 index 000000000..8d7b9c11b --- /dev/null +++ b/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/exceptions/FileHandlingException.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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.digitaltwin.basyx.core.exceptions; + +/** + * Indicates that the provided file could not be handled + * + * @author zhangzai + * + */ +@SuppressWarnings("serial") +public class FileHandlingException extends RuntimeException { + + public FileHandlingException() { + super(); + } + + public FileHandlingException(String fileName) { + super(getMessage(fileName)); + } + + private static String getMessage(String fileName) { + return "Exception occurred while handling the file: " + fileName; + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml index 2e0148c9e..170fe105e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml @@ -45,5 +45,13 @@ org.springframework.boot spring-boot-starter + + org.apache.tika + tika-core + + + commons-io + commons-io + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java index e22bbc98e..165ac560b 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelRepository.java @@ -25,6 +25,12 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -39,23 +45,30 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSerializer; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.apache.commons.io.IOUtils; +import org.apache.tika.utils.StringUtils; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; /** * In-memory implementation of the SubmodelRepository * - * @author schnicke, danish, kammognie + * @author schnicke, danish, kammognie, zhangzai * */ public class InMemorySubmodelRepository implements SubmodelRepository { @@ -64,6 +77,7 @@ public class InMemorySubmodelRepository implements SubmodelRepository { private Map submodelServices = new LinkedHashMap<>(); private SubmodelServiceFactory submodelServiceFactory; private String smRepositoryName; + private String tmpDirectory = getTemporaryDirectoryPath(); /** * Creates the InMemorySubmodelRepository utilizing the passed @@ -74,13 +88,14 @@ public class InMemorySubmodelRepository implements SubmodelRepository { public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory) { this.submodelServiceFactory = submodelServiceFactory; } - + /** * Creates the InMemorySubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices * - * @param submodelServiceFactory - * @param smRepositoryName Name of the SubmodelRepository + * @param submodelServiceFactory + * @param smRepositoryName + * Name of the SubmodelRepository */ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, String smRepositoryName) { this(submodelServiceFactory); @@ -101,15 +116,16 @@ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, submodelServices = createServices(submodels); } - + /** * Creates the InMemorySubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and preconfiguring * it with the passed Submodels * - * @param submodelServiceFactory - * @param submodels - * @param smRepositoryName Name of the SubmodelRepository + * @param submodelServiceFactory + * @param submodels + * @param smRepositoryName + * Name of the SubmodelRepository */ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName) { this(submodelServiceFactory, submodels); @@ -119,13 +135,9 @@ public InMemorySubmodelRepository(SubmodelServiceFactory submodelServiceFactory, private void throwIfHasCollidingIds(Collection submodelsToCheck) { Set ids = new HashSet<>(); - submodelsToCheck.stream() - .map(submodel -> submodel.getId()) - .filter(id -> !ids.add(id)) - .findAny() - .ifPresent(id -> { - throw new CollidingIdentifierException(id); - }); + submodelsToCheck.stream().map(submodel -> submodel.getId()).filter(id -> !ids.add(id)).findAny().ifPresent(id -> { + throw new CollidingIdentifierException(id); + }); } private Map createServices(Collection submodels) { @@ -137,13 +149,9 @@ private Map createServices(Collection submode @Override public CursorResult> getAllSubmodels(PaginationInfo pInfo) { - List allSubmodels = submodelServices.values() - .stream() - .map(service -> service.getSubmodel()) - .collect(Collectors.toList()); + List allSubmodels = submodelServices.values().stream().map(service -> service.getSubmodel()).collect(Collectors.toList()); - TreeMap submodelMap = allSubmodels.stream() - .collect(Collectors.toMap(Submodel::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + TreeMap submodelMap = allSubmodels.stream().collect(Collectors.toMap(Submodel::getId, aas -> aas, (a, b) -> a, TreeMap::new)); PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, Submodel::getId); CursorResult> paginatedSubmodels = paginationSupport.getPaged(pInfo); @@ -202,8 +210,7 @@ public void deleteSubmodel(String submodelId) throws ElementDoesNotExistExceptio public void createSubmodelElement(String submodelId, SubmodelElement smElement) { throwIfSubmodelDoesNotExist(submodelId); - submodelServices.get(submodelId) - .createSubmodelElement(smElement); + submodelServices.get(submodelId).createSubmodelElement(smElement); } @Override @@ -235,7 +242,7 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { throw new RuntimeException("Unable to serialize the Submodel", e); } } - + @Override public String getName() { return smRepositoryName == null ? SubmodelRepository.super.getName() : smRepositoryName; @@ -246,6 +253,68 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath return getSubmodelService(submodelId).invokeOperation(idShortPath, input); } + @Override + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { + throwIfSubmodelDoesNotExist(submodelId); + + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + String filePath = getFilePath(submodelElement); + + throwIfFileDoesNotExist((File) submodelElement, filePath); + + return new java.io.File(filePath); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) { + throwIfSubmodelDoesNotExist(submodelId); + + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + deleteExistingFile(fileSmElement); + String filePath = createFilePath(submodelId, idShortPath, fileName); + + createFileAtSpecifiedPath(fileName, inputStream, filePath); + + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + @Override + public void deleteFileValue(String submodelId, String idShortPath) { + throwIfSubmodelDoesNotExist(submodelId); + + SubmodelElement submodelElement = submodelServices.get(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSubmodelElement = (File) submodelElement; + String filePath = fileSubmodelElement.getValue(); + + throwIfFileDoesNotExist(fileSubmodelElement, filePath); + + java.io.File tmpFile = new java.io.File(filePath); + tmpFile.delete(); + + FileBlobValue fileValue = new FileBlobValue(StringUtils.EMPTY, StringUtils.EMPTY); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + private String createFilePath(String submodelId, String idShortPath, String fileName) { + return tmpDirectory + "/" + submodelId + "-" + idShortPath.replace("/", "-") + "-" + fileName; + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + if (!(submodelElement instanceof File)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { String newSubmodelId = newSubmodel.getId(); @@ -254,7 +323,6 @@ private void throwIfMismatchingIds(String smId, Submodel newSubmodel) { throw new IdentificationMismatchException(); } - private SubmodelService getSubmodelService(String submodelId) { throwIfSubmodelDoesNotExist(submodelId); @@ -271,4 +339,53 @@ private void throwIfSubmodelDoesNotExist(String id) { throw new ElementDoesNotExistException(id); } + private void throwIfFileDoesNotExist(File fileSmElement, String filePath) { + if (fileSmElement.getValue().isBlank() || !isFilePathValid(filePath)) + throw new FileDoesNotExistException(fileSmElement.getIdShort()); + } + + private boolean isFilePathValid(String filePath) { + try { + Paths.get(filePath); + } catch (InvalidPathException | NullPointerException ex) { + return false; + } + return true; + } + + private String getTemporaryDirectoryPath() { + String tempDirectoryPath = ""; + try { + tempDirectoryPath = Files.createTempDirectory("basyx-temp").toAbsolutePath().toString(); + } catch (IOException e) { + e.printStackTrace(); + } + return tempDirectoryPath; + } + + private String getFilePath(SubmodelElement submodelElement) { + return ((File) submodelElement).getValue(); + } + + private void deleteExistingFile(File fileSmElement) { + String filePath = fileSmElement.getValue(); + if(filePath.isEmpty()) return; + + try { + Files.deleteIfExists(Paths.get(filePath, "")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void createFileAtSpecifiedPath(String fileName, InputStream inputStream, String filePath) { + java.io.File targetFile = new java.io.File(filePath); + + try (FileOutputStream outStream = new FileOutputStream(targetFile)) { + IOUtils.copy(inputStream, outStream); + } catch (IOException e) { + throw new FileHandlingException(fileName); + } + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java index 1d9cf5a3d..4fc12db12 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestInMemorySubmodelRepository.java @@ -44,7 +44,7 @@ * */ public class TestInMemorySubmodelRepository extends SubmodelRepositorySuite { - + private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; @Override @@ -58,12 +58,12 @@ protected SubmodelRepository getSubmodelRepository(Collection submodel } @Test - public void getConfiguredInMemorySmRepositoryName() { + public void getConfiguredInMemorySmRepositoryName() { SubmodelRepository repo = new InMemorySubmodelRepository(new InMemorySubmodelServiceFactory(), CONFIGURED_SM_REPO_NAME); - + assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } - + @Test(expected = CollidingIdentifierException.class) public void idCollisionDuringConstruction() { Collection submodelsWithCollidingIds = createSubmodelCollectionWithCollidingIds(); @@ -83,4 +83,5 @@ private Collection createSubmodelCollectionWithCollidingIds() { private Collection createSubmodelCollectionWithUniqueIds() { return Arrays.asList(DummySubmodelFactory.createSimpleDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json new file mode 100644 index 000000000..f1ce92ea3 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/test/resources/SampleJsonFile.json @@ -0,0 +1,5 @@ +{ + "name": "SampleJsonFile", + "description": "A JSON file for verification", + "version": 123 +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml index afc17f693..2d38ea0cd 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml @@ -37,5 +37,9 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-core + + org.apache.tika + tika-core + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java index de3b22829..5b490a62d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepository.java @@ -24,21 +24,35 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.submodelrepository; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.function.Function; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; +import org.apache.commons.io.FileUtils; +import org.apache.tika.mime.MimeType; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; +import org.apache.tika.utils.StringUtils; +import org.bson.types.ObjectId; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotSupportedException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; import org.springframework.data.domain.Sort; @@ -47,25 +61,30 @@ import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; - +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import com.mongodb.client.gridfs.model.GridFSFile; import com.mongodb.client.result.DeleteResult; /** * MongoDB implementation of the SubmodelRepository * - * @author jungjan, kammognie + * @author jungjan, kammognie, zhangzai, danish * */ public class MongoDBSubmodelRepository implements SubmodelRepository { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); - private static final String ID = "_id"; - private static String ID_JSON_PATH = "id"; + private static final String MONGO_ID = "_id"; + private static final String TEMP_DIR_PREFIX = "basyx-temp"; + private static final String GRIDFS_ID_DELIMITER = "#"; + private static final String ID_JSON_PATH = "id"; private MongoTemplate mongoTemplate; private String collectionName; private SubmodelServiceFactory submodelServiceFactory; private String smRepositoryName; + private GridFsTemplate gridFsTemplate; + /** * Creates the MongoDBSubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and uses a @@ -75,12 +94,13 @@ public class MongoDBSubmodelRepository implements SubmodelRepository { * @param collectionName * @param submodelServiceFactory */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory) { this.mongoTemplate = mongoTemplate; this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; configureIndexForSubmodelId(mongoTemplate); + + configureDefaultGridFsTemplate(this.mongoTemplate); } /** @@ -88,15 +108,21 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * SubmodelServiceFactory for creating new SubmodelServices and uses a * collectionName and a mongoTemplate for operating MongoDB * - * @param mongoTemplate - * @param collectionName - * @param submodelServiceFactory - * @param smRepositoryName Name of the SubmodelRepository + * @param mongoTemplate + * @param collectionName + * @param submodelServiceFactory + * @param smRepositoryName + * Name of the SubmodelRepository */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, String smRepositoryName) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); this.smRepositoryName = smRepositoryName; + this.gridFsTemplate = gridFsTemplate; + + if (this.gridFsTemplate == null) + configureDefaultGridFsTemplate(mongoTemplate); + + configureIndexForSubmodelId(mongoTemplate); } /** @@ -108,30 +134,35 @@ public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionN * @param submodelServiceFactory * @param submodels */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, Collection submodels) { this(mongoTemplate, collectionName, submodelServiceFactory); initializeRemoteCollection(submodels); + + configureDefaultGridFsTemplate(this.mongoTemplate); } /** * Creates the MongoDBSubmodelRepository utilizing the passed * SubmodelServiceFactory for creating new SubmodelServices and uses a * collectionName and a mongoTemplate for operating MongoDB. Additionally - * initializes the MongoDB collection with a collection of submodels. - * And configures the SubmodelRepository name. + * initializes the MongoDB collection with a collection of submodels. And + * configures the SubmodelRepository name. * * @param mongoTemplate * @param collectionName * @param submodelServiceFactory * @param submodels - * @param smRepositoryName Name of the SubmodelRepository + * @param smRepositoryName + * Name of the SubmodelRepository */ - public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName) { + public MongoDBSubmodelRepository(MongoTemplate mongoTemplate, String collectionName, SubmodelServiceFactory submodelServiceFactory, Collection submodels, String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory, submodels); - + this.smRepositoryName = smRepositoryName; + this.gridFsTemplate = gridFsTemplate; + + if (this.gridFsTemplate == null) + configureDefaultGridFsTemplate(mongoTemplate); } private void initializeRemoteCollection(Collection submodels) { @@ -143,8 +174,7 @@ private void initializeRemoteCollection(Collection submodels) { private void configureIndexForSubmodelId(MongoTemplate mongoTemplate) { Index idIndex = new Index().on(ID_JSON_PATH, Direction.ASC); - mongoTemplate.indexOps(Submodel.class) - .ensureIndex(idIndex); + mongoTemplate.indexOps(Submodel.class).ensureIndex(idIndex); } @Override @@ -160,8 +190,7 @@ public CursorResult> getAllSubmodels(PaginationInfo pInfo) { @Override public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistException { - Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), - Submodel.class, collectionName); + Submodel submodel = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), Submodel.class, collectionName); if (submodel == null) { throw new ElementDoesNotExistException(submodelId); } @@ -200,8 +229,7 @@ public void createSubmodel(Submodel submodel) throws CollidingIdentifierExceptio } private void throwIfCollidesWithRemoteId(Submodel submodel) { - if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodel.getId())), - Submodel.class, collectionName)) { + if (mongoTemplate.exists(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodel.getId())), Submodel.class, collectionName)) { throw new CollidingIdentifierException(submodel.getId()); } } @@ -211,26 +239,22 @@ private SubmodelService getSubmodelService(String submodelId) { } @Override - public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) - throws ElementDoesNotExistException { + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElements(pInfo); } @Override - public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) - throws ElementDoesNotExistException { + public SubmodelElement getSubmodelElement(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElement(submodelElementIdShort); } @Override - public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) - throws ElementDoesNotExistException { + public SubmodelElementValue getSubmodelElementValue(String submodelId, String submodelElementIdShort) throws ElementDoesNotExistException { return getSubmodelService(submodelId).getSubmodelElementValue(submodelElementIdShort); } @Override - public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) - throws ElementDoesNotExistException { + public void setSubmodelElementValue(String submodelId, String submodelElementIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.setSubmodelElementValue(submodelElementIdShort, value); @@ -239,8 +263,7 @@ public void setSubmodelElementValue(String submodelId, String submodelElementIdS @Override public void deleteSubmodel(String submodelId) throws ElementDoesNotExistException { - DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), - Submodel.class, collectionName); + DeleteResult result = mongoTemplate.remove(new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)), Submodel.class, collectionName); if (result.getDeletedCount() == 0) { throw new ElementDoesNotExistException(submodelId); @@ -257,8 +280,7 @@ public void createSubmodelElement(String submodelId, SubmodelElement submodelEle } @Override - public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) - throws ElementDoesNotExistException { + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelService(submodelId); submodelService.createSubmodelElement(idShortPath, submodelElement); @@ -288,10 +310,9 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot @Override public String getName() { return smRepositoryName == null ? SubmodelRepository.super.getName() : smRepositoryName; - } - - private String resolveCursor(PaginationInfo pRequest, List foundDescriptors, - Function idResolver) { + } + + private String resolveCursor(PaginationInfo pRequest, List foundDescriptors, Function idResolver) { if (foundDescriptors.isEmpty() || !pRequest.isPaged()) { return null; } @@ -300,12 +321,12 @@ private String resolveCursor(PaginationInfo pRequest, List foundDescripto } private void applySorting(Query query, PaginationInfo pInfo) { - query.with(Sort.by(Direction.ASC, ID)); + query.with(Sort.by(Direction.ASC, MONGO_ID)); } private void applyPagination(Query query, PaginationInfo pInfo) { if (pInfo.getCursor() != null) { - query.addCriteria(Criteria.where(ID).gt(pInfo.getCursor())); + query.addCriteria(Criteria.where(MONGO_ID).gt(pInfo.getCursor())); } if (pInfo.getLimit() != null) { query.limit(pInfo.getLimit()); @@ -317,4 +338,151 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath throw new FeatureNotSupportedException("Operation Invocation"); } + @Override + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + throwIfFileDoesNotExist(fileSmElement); + + String fileId = getFileId(fileSmElement.getValue()); + + GridFSFile file = gridFsTemplate.findOne(new Query(Criteria.where(MONGO_ID).is(fileId))); + + InputStream fileIs = getGridFsFileAsInputStream(file); + + return createFileInTempDirectory(idShortPath, fileSmElement, fileIs); + } + + @Override + public void deleteFileValue(String submodelId, String idShortPath) { + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + throwIfFileDoesNotExist(fileSmElement); + + String fileId = getFileId(fileSmElement.getValue()); + + gridFsTemplate.delete(new Query(Criteria.where(MONGO_ID).is(fileId))); + + FileBlobValue fileValue = new FileBlobValue(StringUtils.EMPTY, StringUtils.EMPTY); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) { + Query query = new Query().addCriteria(Criteria.where(ID_JSON_PATH).is(submodelId)); + + throwIfSubmodelDoesNotExist(query, submodelId); + + SubmodelElement submodelElement = getSubmodelService(submodelId).getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + ObjectId id = gridFsTemplate.store(inputStream, fileSmElement.getValue(), fileSmElement.getContentType()); + + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), appendFsIdToFileValue(fileSmElement, id)); + + setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + private void configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { + this.gridFsTemplate = new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); + } + + private String appendFsIdToFileValue(File fileSmElement, ObjectId id) { + return id.toString() + GRIDFS_ID_DELIMITER + fileSmElement.getValue(); + } + + private java.io.File createFileInTempDirectory(String idShortPath, File fileSmElement, InputStream fileIs) { + + Path tempDir = createTempDirectory(TEMP_DIR_PREFIX); + + String absolutePath = tempDir.toAbsolutePath().toString(); + + String filePath = getFilePath(absolutePath, idShortPath, fileSmElement.getContentType()); + + java.io.File targetFile = new java.io.File(filePath); + + try { + FileUtils.copyInputStreamToFile(fileIs, targetFile); + } catch (IOException e) { + e.printStackTrace(); + } + + return targetFile; + } + + private Path createTempDirectory(String prefix) { + Path tempDir = null; + try { + tempDir = Files.createTempDirectory(prefix); + } catch (IOException e) { + e.printStackTrace(); + } + return tempDir; + } + + private InputStream getGridFsFileAsInputStream(GridFSFile file) { + InputStream fileIs = null; + + try { + fileIs = gridFsTemplate.getResource(file).getInputStream(); + } catch (IllegalStateException | IOException e1) { + e1.printStackTrace(); + } + return fileIs; + } + + private String getFileId(String value) { + return value.substring(0, value.indexOf(GRIDFS_ID_DELIMITER)); + } + + private void throwIfFileDoesNotExist(File fileSmElement) { + if (fileSmElement.getValue().isBlank() || fileSmElement.getValue().indexOf(GRIDFS_ID_DELIMITER) == -1) + throw new FileDoesNotExistException(fileSmElement.getIdShort()); + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + + if (!(submodelElement instanceof File)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + + private String getFilePath(String tmpDirectory, String idShortPath, String contentType) { + String fileName = idShortPath.replace("/", "-"); + + String extension = getFileExtension(contentType); + + return tmpDirectory + "/" + fileName + extension; + } + + private String getFileExtension(String contentType) { + MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); + try { + MimeType mimeType = allTypes.forName(contentType); + return mimeType.getExtension(); + } catch (MimeTypeException e) { + e.printStackTrace(); + return ""; + } + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java index c3659566c..542282db6 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryFactory.java @@ -32,12 +32,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.stereotype.Component; /** * SubmodelRepository factory returning a MongoDb backend SubmodelRepository * - * @author jungjan + * @author jungjan, zhangzai * */ @Component @@ -48,46 +49,52 @@ public class MongoDBSubmodelRepositoryFactory implements SubmodelRepositoryFacto private String collectionName; private SubmodelServiceFactory submodelServiceFactory; private Collection submodels; - + private String smRepositoryName; + private GridFsTemplate gridFsTemplate; @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory) { + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory) { this.mongoTemplate = mongoTemplate; this.collectionName = collectionName; this.submodelServiceFactory = submodelServiceFactory; } - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName) { + @Autowired(required = false) + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory); + this.gridFsTemplate = gridFsTemplate; + } + + @Autowired(required = false) + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + GridFsTemplate gridFsTemplate, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName) { + this(mongoTemplate, collectionName, submodelServiceFactory, gridFsTemplate); this.smRepositoryName = smRepositoryName; } @Autowired(required = false) - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels) { + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + Collection submodels) { this(mongoTemplate, collectionName, submodelServiceFactory); this.submodels = submodels; } - - public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, - @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, - SubmodelServiceFactory submodelServiceFactory, Collection submodels, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName) { + + @Autowired(required = false) + public MongoDBSubmodelRepositoryFactory(MongoTemplate mongoTemplate, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, SubmodelServiceFactory submodelServiceFactory, + Collection submodels, @Value("${basyx.smrepo.name:sm-repo}") String smRepositoryName, GridFsTemplate gridFsTemplate) { this(mongoTemplate, collectionName, submodelServiceFactory, submodels); this.smRepositoryName = smRepositoryName; + this.gridFsTemplate = gridFsTemplate; } @Override public SubmodelRepository create() { if (this.submodels == null || this.submodels.isEmpty()) { - return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, smRepositoryName); + return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, smRepositoryName, gridFsTemplate); } - return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, submodels, smRepositoryName); + return new MongoDBSubmodelRepository(mongoTemplate, collectionName, submodelServiceFactory, submodels, smRepositoryName, gridFsTemplate); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java index 27f0d29be..3fb882337 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java @@ -36,7 +36,8 @@ import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.junit.Test; import org.springframework.data.mongodb.core.MongoTemplate; -import org.junit.Test; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; + import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -45,6 +46,7 @@ public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); + private final GridFsTemplate GRIDFS_TEMPLATE = new GridFsTemplate(TEMPLATE.getMongoDatabaseFactory(), TEMPLATE.getConverter(), "TestSMEFiles"); private final InMemorySubmodelServiceFactory SUBMODEL_SERVICE_FACTORY = new InMemorySubmodelServiceFactory(); private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; @@ -52,7 +54,7 @@ public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { protected SubmodelRepository getSubmodelRepository() { MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY).create(); + return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, GRIDFS_TEMPLATE).create(); } @Override @@ -62,13 +64,13 @@ protected SubmodelRepository getSubmodelRepository(Collection submodel // TODO: Remove this after MongoDB uses AAS4J serializer submodels.forEach(this::removeInvokableFromInvokableOperation); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels).create(); + return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels, CONFIGURED_SM_REPO_NAME, GRIDFS_TEMPLATE).create(); } - + @Test public void getConfiguredMongoDBSmRepositoryName() { - SubmodelRepository repo = new MongoDBSubmodelRepository(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME); - + SubmodelRepository repo = new MongoDBSubmodelRepository(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME, GRIDFS_TEMPLATE); + assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } @@ -85,10 +87,7 @@ public void invokeNonOperation() { } private void removeInvokableFromInvokableOperation(Submodel sm) { - sm.getSubmodelElements().stream() - .filter(InvokableOperation.class::isInstance) - .map(InvokableOperation.class::cast) - .forEach(o -> o.setInvokable(null)); + sm.getSubmodelElements().stream().filter(InvokableOperation.class::isInstance).map(InvokableOperation.class::cast).forEach(o -> o.setInvokable(null)); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json new file mode 100644 index 000000000..f1ce92ea3 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/SampleJsonFile.json @@ -0,0 +1,5 @@ +{ + "name": "SampleJsonFile", + "description": "A JSON file for verification", + "version": 123 +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml index a704f5c1c..1fa44a1ee 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-core/pom.xml @@ -25,10 +25,14 @@ tests test - org.eclipse.digitaltwin.aas4j model + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java index 5d2fa71ba..bf0d1947f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelRepository.java @@ -26,12 +26,15 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; import java.util.List; +import java.io.InputStream; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; @@ -208,4 +211,48 @@ public default String getName() { * @throws ElementDoesNotExistException */ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException; + + /** + * Retrieves the file of a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort path of the file element + * @return + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException + */ + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; + + /** + * Uploads a file to a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort path of the file element + * @param file + * the file object to upload + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + */ + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException; + + /** + * Deletes the file of a file submodelelement + * + * @param submodelId + * the Submodel id + * @param idShortPath + * the IdShort path of the file element + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException + */ + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 37ba5d562..3a1503dea 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -29,9 +29,17 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.Collection; import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXSD; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Property; @@ -41,6 +49,8 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; @@ -50,15 +60,17 @@ import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; import org.eclipse.digitaltwin.basyx.submodelservice.value.PropertyValue; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; /** * Testsuite for implementations of the SubmodelRepository interface * - * @author schnicke, danish, kammognie + * @author schnicke, danish, kammognie, zhangzai * */ public abstract class SubmodelRepositorySuite { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static final String DUMMY_FILE_CONTENT = "this is a file"; protected abstract SubmodelRepository getSubmodelRepository(); @@ -185,7 +197,6 @@ public void getSubmodelElements() { public void getSubmodelElementsOfNonExistingSubmodel() { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); repo.getSubmodelElements("notExisting", NO_LIMIT_PAGINATION_INFO).getResult(); - } @Test @@ -242,6 +253,111 @@ public void setPropertyValue() { assertEquals(expected, retrievedValue.getValue()); } + + @Test + public void updateFile() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + // Set the value of the file-submodelelement for the first time + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } + + // Set the value of the file-submodel element again with a dummy text file + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "newFile.txt", getInputStreamOfDummyFile()); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } + + // Get the file from the file submodel element + File retrievedValue = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + try { + String actual = new String(FileUtils.openInputStream(retrievedValue).readAllBytes()); + assertEquals(DUMMY_FILE_CONTENT, actual); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void getFile(){ + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + String expectedFileExtension = "json"; + + InputStream expectedFile = null; + try { + expectedFile = getInputStreamOfFileFromClasspath("SampleJsonFile.json"); + } catch (IOException e1) { + e1.printStackTrace(); + } + + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e) { + fail(); + e.printStackTrace(); + } + + File retrievedValue = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + assertEquals(expectedFileExtension, getExtension(retrievedValue.getName())); + + try { + assertTrue(IOUtils.contentEquals(expectedFile, FileUtils.openInputStream(retrievedValue))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test(expected = FileDoesNotExistException.class) + public void getNonExistingFile(){ + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + deleteFileIfExisted(repo); + + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } + + + @Test(expected = ElementNotAFileException.class) + public void getFileFromNonFileSME(){ + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + } + + @Test + public void deleteFile() { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + + try { + repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); + } catch (IOException e1) { + fail(); + e1.printStackTrace(); + } + + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + try { + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + fail(); + } catch (Exception e) { + } + } + + @Test(expected = FileDoesNotExistException.class) + public void deleteNonExistingFile() throws IOException { + SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); + deleteFileIfExisted(repo); + + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } @Test(expected = ElementDoesNotExistException.class) public void setNonExistingSubmodelElementValue() { @@ -386,7 +502,16 @@ public void invokeNonOperation() { submodelRepo.invokeOperation(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, new OperationVariable[0]); } - + + private void deleteFileIfExisted(SubmodelRepository repo) { + try { + repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + repo.deleteFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + }catch(FileDoesNotExistException e) { + return; + } + + } private SubmodelElement getExpectedSubmodelElement() { return DummySubmodelFactory.createOperationalDataSubmodel() @@ -420,5 +545,19 @@ private void assertIsEmpty(Collection submodels) { private String generateIdShortPath() { return DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ELEMENT_COLLECTION_ID_SHORT + "." + DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ELEMENT_LIST_ID_SHORT + "[0]"; } + + private InputStream getInputStreamOfFileFromClasspath(String fileName) throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource(fileName); + + return classPathResource.getInputStream(); + } + + private InputStream getInputStreamOfDummyFile() throws FileNotFoundException, IOException { + return new ByteArrayInputStream(DUMMY_FILE_CONTENT.getBytes()); + } + + private String getExtension(String filename) { + return FilenameUtils.getExtension(filename); + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index fa06bcf22..d44fcd465 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -1,6 +1,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; import java.util.List; +import java.io.InputStream; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -9,6 +10,7 @@ import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelSerializer; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -186,4 +188,19 @@ public OperationVariable[] invokeOperation(String submodelId, String idShortPath return decorated.invokeOperation(submodelId, idShortPath, input); } + @Override + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { + throw new FeatureNotImplementedException(); + } + + @Override + public void deleteFileValue(String identifier, String idShortPath) { + throw new FeatureNotImplementedException(); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream){ + throw new FeatureNotImplementedException(); + } + } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java index 90249c9de..7cde8a23e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryApiHTTPController.java @@ -25,6 +25,10 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -38,6 +42,9 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.model.OperationRequest; import org.eclipse.digitaltwin.basyx.http.model.OperationResult; @@ -49,11 +56,14 @@ import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.InputStreamResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.ParameterIn; @@ -90,7 +100,6 @@ public ResponseEntity deleteSubmodelElementByPathSubmodelRepo( return new ResponseEntity(HttpStatus.NO_CONTENT); } - @Override public ResponseEntity getAllSubmodels(@Size(min = 1, max = 3072) @Valid Base64UrlEncodedIdentifier semanticId, @Valid String idShort, @Min(1) @Valid Integer limit, @Valid String cursor, @Valid String level, @Valid String extent) { @@ -128,7 +137,7 @@ public ResponseEntity getAllSubmodelElements(Base64UrlEncodedIdenti PaginationInfo pInfo = new PaginationInfo(limit, cursor); CursorResult> submodelElements = repository .getSubmodelElements(submodelIdentifier.getIdentifier(), pInfo); - + GetSubmodelElementsResult paginatedSubmodelElement = new GetSubmodelElementsResult(); paginatedSubmodelElement.setResult(submodelElements.getResult()); paginatedSubmodelElement @@ -139,7 +148,7 @@ public ResponseEntity getAllSubmodelElements(Base64UrlEncodedIdenti @Override public ResponseEntity getSubmodelElementByPathSubmodelRepo(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, @Valid String level, @Valid String extent) { - return handleSubmodelElementValueNormalGetRequest(submodelIdentifier.getIdentifier(), idShortPath); + return handleSubmodelElementValueNormalGetRequest(submodelIdentifier.getIdentifier(), idShortPath); } @Override @@ -174,6 +183,53 @@ public ResponseEntity getSubmodelByIdMetadata(Base64UrlEncodedIdentifi return new ResponseEntity(repository.getSubmodelByIdMetadata(submodelIdentifier.getIdentifier()), HttpStatus.OK); } + @Override + public ResponseEntity getFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { + try { + FileInputStream fileInputStream = new FileInputStream(repository.getFileByPathSubmodel(submodelIdentifier.getIdentifier(), idShortPath)); + Resource resource = new InputStreamResource(fileInputStream); + return new ResponseEntity(resource, HttpStatus.OK); + } catch (FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + } catch (ElementNotAFileException e) { + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); + } catch(FileNotFoundException e) { + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity putFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, String fileName, @Valid MultipartFile file) { + InputStream fileInputstream = null; + try { + fileInputstream = file.getInputStream(); + repository.setFileValue(submodelIdentifier.getIdentifier(), idShortPath, fileName, fileInputstream); + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.OK); + } catch (ElementDoesNotExistException e) { + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.NOT_FOUND); + } catch (ElementNotAFileException e) { + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); + } catch (IOException e) { + closeInputStream(fileInputstream); + return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity deleteFileByPath(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath) { + try { + repository.deleteFileValue(submodelIdentifier.getIdentifier(), idShortPath); + return new ResponseEntity(HttpStatus.OK); + } catch (FileDoesNotExistException | ElementDoesNotExistException e) { + return new ResponseEntity(HttpStatus.NOT_FOUND); + } catch (ElementNotAFileException e) { + return new ResponseEntity(HttpStatus.PRECONDITION_FAILED); + } + } + private ResponseEntity handleSubmodelElementValueSetRequest(Base64UrlEncodedIdentifier submodelIdentifier, String idShortPath, SubmodelElementValue body) { repository.setSubmodelElementValue(submodelIdentifier.getIdentifier(), idShortPath, body); return new ResponseEntity(HttpStatus.NO_CONTENT); @@ -202,4 +258,15 @@ private OperationResult createOperationResult(OperationVariable[] result) { operationResult.setOutputArguments(Arrays.asList(result)); return operationResult; } + + private void closeInputStream(InputStream fileInputstream) { + if (fileInputstream == null) + return; + + try { + fileInputstream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java index 29e96a93e..0c93517b6 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositoryHTTPApi.java @@ -46,6 +46,7 @@ import org.eclipse.digitaltwin.basyx.submodelrepository.http.pagination.GetSubmodelsResult; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; +import org.springframework.core.io.Resource; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PathVariable; @@ -53,6 +54,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -372,6 +375,68 @@ ResponseEntity postSubmodelElementSubmodelRepo( ResponseEntity deleteSubmodelElementByPathSubmodelRepo( @Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required = true, schema = @Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required = true, schema = @Schema()) @PathVariable("idShortPath") String idShortPath); + + @Operation(summary = "Downloads file content from a specific submodel element from the Submodel at a specified path", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Requested file", content = @Content(mediaType = "application/octet-stream", schema = @Schema(implementation = Resource.class))), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/octet-stream", "application/json" }, + method = RequestMethod.GET) + ResponseEntity getFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); + + @Operation(summary = "Uploads file content to an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Submodel element updated successfully"), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/json" }, + consumes = { "multipart/form-data" }, + method = RequestMethod.PUT) + ResponseEntity putFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath, @Parameter(in = ParameterIn.DEFAULT, description = "", required=true,schema=@Schema()) @RequestParam(value="fileName", required=true) String fileName, @Parameter(description = "file detail") @Valid @RequestPart("file") MultipartFile file); + + @Operation(summary = "Deletes file content of an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags={ "Asset Administration Shell API" }) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Submodel element updated successfully"), + + @ApiResponse(responseCode = "400", description = "Bad Request, e.g. the request parameters of the format of the request body is wrong.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "401", description = "Unauthorized, e.g. the server refused the authorization attempt.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "403", description = "Forbidden", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))), + + @ApiResponse(responseCode = "200", description = "Default error handling for unmentioned status codes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Result.class))) }) + @RequestMapping(value = "/submodels/{submodelIdentifier}/submodel-elements/{idShortPath}/attachment", + produces = { "application/json" }, + method = RequestMethod.DELETE) + ResponseEntity deleteFileByPath(@Parameter(in = ParameterIn.PATH, description = "The Submodel’s unique id (UTF8-BASE64-URL-encoded)", required=true, schema=@Schema()) @PathVariable("submodelIdentifier") Base64UrlEncodedIdentifier submodelIdentifier, @Parameter(in = ParameterIn.PATH, description = "IdShort path to the submodel element (dot-separated)", required=true, schema=@Schema()) @PathVariable("idShortPath") String idShortPath); + @Operation(summary = "Synchronously or asynchronously invokes an Operation at a specified path", description = "", tags = { "Submodel Repository API" }) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Operation result object", content = @Content(mediaType = "application/json", schema = @Schema(implementation = OperationResult.class))), diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java index 271786dff..9bc325618 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/DummySubmodelRepositoryComponent.java @@ -28,8 +28,6 @@ import org.springframework.boot.SpringApplication; -import org.eclipse.digitaltwin.basyx.submodelrepository.InMemorySubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; /** diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java index d92c062d6..4ba0991a0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/SubmodelRepositorySubmodelHTTPTestSuite.java @@ -29,11 +29,24 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import org.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpPut; +import org.apache.hc.client5.http.entity.mime.FileBody; +import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; @@ -42,6 +55,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -70,8 +84,7 @@ public void getAllSubmodelsPreconfigured() throws IOException, ParseException { @Test public void getSpecificSubmodel() throws ParseException, IOException { - String submodelJSON = requestSpecificSubmodelJSON(DummySubmodelFactory.createTechnicalDataSubmodel() - .getId()); + String submodelJSON = requestSpecificSubmodelJSON(DummySubmodelFactory.createTechnicalDataSubmodel().getId()); String expectedSubmodelJSON = getSingleSubmodelJSON(); BaSyxHttpTestUtils.assertSameJSONContent(expectedSubmodelJSON, submodelJSON); @@ -81,8 +94,7 @@ public void getSpecificSubmodel() throws ParseException, IOException { public void getSpecificSubmodelMetadata() throws ParseException, IOException { String expectedSubmodelJSON = getSingleSubmodelMetadataJSON(); - CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSubmodelMetadataURL(DummySubmodelFactory.createTechnicalDataSubmodel() - .getId())); + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSubmodelMetadataURL(DummySubmodelFactory.createTechnicalDataSubmodel().getId())); assertEquals(HttpStatus.OK.value(), response.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(expectedSubmodelJSON, BaSyxHttpTestUtils.getResponseAsString(response)); @@ -149,8 +161,7 @@ public void createSubmodelCollidingId() throws IOException { @Test public void deleteSubmodel() throws IOException { - String existingSubmodelId = DummySubmodelFactory.createTechnicalDataSubmodel() - .getId(); + String existingSubmodelId = DummySubmodelFactory.createTechnicalDataSubmodel().getId(); CloseableHttpResponse deletionResponse = deleteSubmodelById(existingSubmodelId); assertEquals(HttpStatus.NO_CONTENT.value(), deletionResponse.getCode()); @@ -168,16 +179,145 @@ public void deleteNonExistingSubmodel() throws IOException { @Test public void getPaginatedSubmodel() throws ParseException, IOException { - String submodelsJSON = BaSyxSubmodelHttpTestUtils - .requestAllSubmodels(getURL() + "?limit=1&cursor=7A7104BDAB57E184"); - String expected = getSingleSubmodelPaginatedJson(); + String submodelsJSON = BaSyxSubmodelHttpTestUtils.requestAllSubmodels(getURL() + "?limit=1&cursor=7A7104BDAB57E184"); + String expected = getSubmodelsPaginatedJson(); BaSyxHttpTestUtils.assertSameJSONContent(expected, submodelsJSON); } + @Test + public void uploadFileToFileSubmodelElement() throws IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist"); + + assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void deleteFile() throws FileNotFoundException, IOException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void deleteFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void getFile() throws FileNotFoundException, IOException, ParseException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + String received = BaSyxHttpTestUtils.getResponseAsString(response); + + String fileName = "BaSyx-Logo.png"; + + assertEquals(readFile("src/test/resources/" + fileName, Charset.defaultCharset()), new String(received.getBytes(), Charset.defaultCharset())); + } + + @Test + public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, DummySubmodelFactory.SUBMODEL_ELEMENT_NON_FILE_ID_SHORT)); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_FOR_FILE_TEST, "ElementNotExist")); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + private String createSMEFileDeleteURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private String createSMEFileGetURL(String submodelId, String submodelElementIdShort) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelId, String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + String fileName = "BaSyx-Logo.png"; + + java.io.File file = ResourceUtils.getFile("src/test/resources/" + fileName); + + HttpPut putRequest = createPutRequestWithFile(submodelId, submodelElementIdShort, fileName, file); + + return executePutRequest(client, putRequest); + } + + private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + private HttpPut createPutRequestWithFile(String submodelId, String submodelElementIdShort, String fileName, java.io.File file) { + HttpPut putRequest = new HttpPut(createSMEFileUploadURL(submodelId, submodelElementIdShort, fileName)); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file)); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + putRequest.setEntity(multipart); + return putRequest; + } + + private String createSMEFileUploadURL(String submodelId, String submodelElementIdShort, String fileName) { + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath(getURL(), submodelId) + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; + } + + private static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + + return new String(encoded, encoding); + } + private void assertSubmodelCreationReponse(String submodelJSON, CloseableHttpResponse creationResponse) throws IOException, ParseException, JsonProcessingException, JsonMappingException { assertEquals(HttpStatus.CREATED.value(), creationResponse.getCode()); + String response = BaSyxHttpTestUtils.getResponseAsString(creationResponse); + BaSyxHttpTestUtils.assertSameJSONContent(submodelJSON, response); } @@ -227,8 +367,8 @@ private String getAllSubmodelJSON() throws IOException { return BaSyxHttpTestUtils.readJSONStringFromClasspath("MultipleSubmodels.json"); } - private String getSingleSubmodelPaginatedJson() throws FileNotFoundException, IOException { - return BaSyxHttpTestUtils.readJSONStringFromClasspath("SingleSubmodelPaginated.json"); + private String getSubmodelsPaginatedJson() throws FileNotFoundException, IOException { + return BaSyxHttpTestUtils.readJSONStringFromClasspath("SubmodelsPaginated.json"); } protected List createSubmodels() { diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java index eee08a9d4..2d3e9f00c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelElementsHTTP.java @@ -26,6 +26,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.http.SubmodelServiceSubmodelElementsTestSuiteHTTP; import org.junit.After; @@ -60,8 +61,7 @@ public void createSubmodelOnRepo() { @After public void removeSubmodelFromRepo() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); - repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()) - .forEach(repo::deleteSubmodel); + repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()).forEach(repo::deleteSubmodel); } @AfterClass @@ -72,8 +72,7 @@ public static void shutdownAASRepo() { @Override protected String getURL() { - return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath("http://localhost:8080/submodels", - createSubmodel().getId()); + return BaSyxSubmodelHttpTestUtils.getSpecificSubmodelAccessPath("http://localhost:8080/submodels", createSubmodel().getId()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java index b7c267719..a60d8deda 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/TestSubmodelRepositorySubmodelHTTP.java @@ -25,10 +25,13 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -43,6 +46,7 @@ */ public class TestSubmodelRepositorySubmodelHTTP extends SubmodelRepositorySubmodelHTTPTestSuite { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static final String SUBMODEL_JSON = "SingleSubmodel4FileTest.json"; private static ConfigurableApplicationContext appContext; @BeforeClass @@ -53,8 +57,7 @@ public static void startAASRepo() throws Exception { @Override public void resetRepository() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); - repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()) - .forEach(repo::deleteSubmodel); + repo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult().stream().map(s -> s.getId()).forEach(repo::deleteSubmodel); } @Override @@ -62,6 +65,12 @@ public void populateRepository() { SubmodelRepository repo = appContext.getBean(SubmodelRepository.class); Collection submodels = createSubmodels(); submodels.forEach(repo::createSubmodel); + try { + createSubmodel4FileTest(); + } catch (IOException e) { + System.out.println("Cannot load " + SUBMODEL_JSON + " for testing file-upload feature."); + e.printStackTrace(); + } } @AfterClass @@ -73,4 +82,11 @@ public static void shutdownAASRepo() { protected String getURL() { return "http://localhost:8080/submodels"; } + + private void createSubmodel4FileTest() throws FileNotFoundException, IOException { + String submodelJSON = BaSyxHttpTestUtils.readJSONStringFromClasspath(SUBMODEL_JSON); + + BaSyxSubmodelHttpTestUtils.createSubmodel(getURL(), submodelJSON); + } + } \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png new file mode 100644 index 000000000..da613e94c Binary files /dev/null and b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/BaSyx-Logo.png differ diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json index 482e499ec..3c1f6b33a 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/MultipleSubmodels.json @@ -406,6 +406,49 @@ } }, { + "id": "8A6344BDAB57E184", + "idShort": "FileTests", + "modelType": "Submodel", + "semanticId": { + "keys": [{ + "type": "GlobalReference", + "value": "0173-1#01-AFZ615#016" + } + ], + "type": "ExternalReference" + }, + "submodelElements": [{ + "category": "PARAMETER", + "contentType": "application/json", + "idShort": "FileData", + "modelType": "File", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "BaSyx-Logo.png" + }, { + "category": "PARAMETER", + "idShort": "NonFileParameter", + "modelType": "Property", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "5000", + "valueType": "xs:integer" + } + ] + }, + { "modelType": "Submodel", "id": "AC69B1CB44F07935", "idShort": "OperationalData", diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodel4FileTest.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodel4FileTest.json new file mode 100644 index 000000000..9b0585616 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodel4FileTest.json @@ -0,0 +1,48 @@ +{ + "modelType": "Submodel", + "semanticId": { + "keys": [ + { + "type": "GlobalReference", + "value": "0173-1#01-AFZ615#016" + } + ], + "type": "ExternalReference" + }, + "id": "8A6344BDAB57E184", + "idShort": "FileTests", + "submodelElements": [ + { + "modelType": "File", + "contentType": "application/json", + "value": "BaSyx-Logo.png", + "semanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "category": "PARAMETER", + "idShort": "FileData" + }, + { + "modelType": "Property", + "semanticId": { + "keys": [ + { + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "5000", + "valueType": "xs:integer", + "category": "PARAMETER", + "idShort": "NonFileParameter" + } + ] +} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json deleted file mode 100644 index 342f34006..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SingleSubmodelPaginated.json +++ /dev/null @@ -1 +0,0 @@ -{"paging_metadata":{"cursor":"AC69B1CB44F07935"},"result":[{"modelType":"Submodel","id":"AC69B1CB44F07935","idShort":"OperationalData","submodelElements":[{"modelType":"Property","value":"4370","valueType":"xs:integer","category":"VARIABLE","idShort":"RotationSpeed","semanticId":{"keys":[{"type":"ConceptDescription","value":"http://customer.com/cd/1/1/18EBD56F6B43D895"}],"type":"ExternalReference"}}]}]} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json new file mode 100644 index 000000000..af1ffc054 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/resources/SubmodelsPaginated.json @@ -0,0 +1,49 @@ +{ + "paging_metadata": { + "cursor": "8A6344BDAB57E184" + }, + "result": [{ + "id": "8A6344BDAB57E184", + "idShort": "FileTests", + "modelType": "Submodel", + "semanticId": { + "keys": [{ + "type": "GlobalReference", + "value": "0173-1#01-AFZ615#016" + } + ], + "type": "ExternalReference" + }, + "submodelElements": [{ + "category": "PARAMETER", + "contentType": "application/json", + "idShort": "FileData", + "modelType": "File", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "BaSyx-Logo.png" + }, { + "category": "PARAMETER", + "idShort": "NonFileParameter", + "modelType": "Property", + "semanticId": { + "keys": [{ + "type": "ConceptDescription", + "value": "0173-1#02-BAA120#008" + } + ], + "type": "ExternalReference" + }, + "value": "5000", + "valueType": "xs:integer" + } + ] + } + ] +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java index 65e7929a4..87af62974 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java @@ -101,6 +101,7 @@ public SubmodelElementValue getSubmodelElementValue(String idShort) throws Eleme return submodelElementValueFactory.create(getSubmodelElement(idShort)).getValue(); } + @SuppressWarnings("unchecked") @Override public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java index 21eae1308..a2f38af38 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummySubmodelFactory.java @@ -97,6 +97,11 @@ public class DummySubmodelFactory { public static final String SUBMODEL_SIMPLE_DATA_ID_SHORT = "simpleSubmodel001"; public static final String SUBMODEL_SIMPLE_DATA_ID = "simpleSubmodel001"; public static final String SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT = "elementToDelete"; + + //SUBMODEL_FOR_FILE_TEST + public static final String SUBMODEL_FOR_FILE_TEST = "8A6344BDAB57E184"; + public static final String SUBMODEL_ELEMENT_FILE_ID_SHORT = "FileData"; + public static final String SUBMODEL_ELEMENT_NON_FILE_ID_SHORT = "NonFileParameter"; public static Collection getSubmodels() { return Arrays.asList(createTechnicalDataSubmodel(), createOperationalDataSubmodel(), createSimpleDataSubmodel()); diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java index 7dace4e10..5e81bf028 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceSubmodelElementsTestSuiteHTTP.java @@ -68,8 +68,7 @@ public void getSubmodelElements() throws FileNotFoundException, IOException, Par @Test public void getSubmodelElement() throws FileNotFoundException, IOException, ParseException { String expectedElement = getSubmodelElementJSON(); - CloseableHttpResponse response = requestSubmodelElement( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElement(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(expectedElement, BaSyxHttpTestUtils.getResponseAsString(response)); @@ -79,8 +78,7 @@ public void getSubmodelElement() throws FileNotFoundException, IOException, Pars public void getPropertyValue() throws IOException, ParseException { String expectedValue = wrapStringValue("5000"); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -98,20 +96,17 @@ public void getNonExistingSubmodelElementValue() throws IOException { public void setPropertyValue() throws IOException, ParseException { String expectedValue = wrapStringValue("2567"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getMultiLanguagePropertyValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -124,20 +119,17 @@ public void getMultiLanguagePropertyValue() throws IOException, ParseException { public void setMultiLanguagePropertyValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setMultiLanguagePropertyValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getRangeValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -150,20 +142,17 @@ public void getRangeValue() throws IOException, ParseException { public void setRangeValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setRangeValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RANGE_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getFileValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -176,20 +165,16 @@ public void getFileValue() throws IOException, ParseException { public void setFileValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setFileValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); - + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getBlobValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -202,20 +187,17 @@ public void getBlobValue() throws IOException, ParseException { public void setBlobValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setBlobValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getEntityValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -228,12 +210,10 @@ public void getEntityValue() throws IOException, ParseException { public void setEntityValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setEntityValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @@ -243,20 +223,17 @@ public void setEntityValueMRP() throws IOException, ParseException { String minimumRequestPayloadValue = getJSONValueAsString("value/setEntityValueMRP.json"); String expectedValue = getJSONValueAsString("value/expectedUpdatedMRPEntityValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, minimumRequestPayloadValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT, minimumRequestPayloadValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ENTITY_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getReferenceElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -269,20 +246,17 @@ public void getReferenceElementValue() throws IOException, ParseException { public void setReferenceElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setReferenceElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_REFERENCE_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getRelationshipElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -295,20 +269,17 @@ public void getRelationshipElementValue() throws IOException, ParseException { public void setRelationshipElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setRelationshipElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_RELATIONSHIP_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getAnnotatedRelationshipElementValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -321,20 +292,17 @@ public void getAnnotatedRelationshipElementValue() throws IOException, ParseExce public void setAnnotatedRelationshipElementValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setAnnotatedRelationshipElementValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getSubmodelElementCollectionValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -347,20 +315,17 @@ public void getSubmodelElementCollectionValue() throws IOException, ParseExcepti public void setSubmodelElementCollectionValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setSubmodelElementCollectionValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @Test public void getSubmodelElementListValue() throws IOException, ParseException { - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); assertEquals(HttpStatus.OK.value(), response.getCode()); @@ -373,12 +338,10 @@ public void getSubmodelElementListValue() throws IOException, ParseException { public void setSubmodelElementListValue() throws IOException, ParseException { String expectedValue = getJSONValueAsString("value/setSubmodelElementListValue.json"); - CloseableHttpResponse writeResponse = writeSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT, expectedValue); + CloseableHttpResponse writeResponse = writeSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT, expectedValue); assertEquals(HttpStatus.NO_CONTENT.value(), writeResponse.getCode()); - CloseableHttpResponse response = requestSubmodelElementValue( - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); + CloseableHttpResponse response = requestSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_LIST_ID_SHORT); BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } @@ -386,8 +349,7 @@ public void setSubmodelElementListValue() throws IOException, ParseException { @Test public void createSubmodelElementCollidingId() throws IOException { String element = getJSONValueAsString("SubmodelElement.json"); - CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), - element); + CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), element); assertEquals(HttpStatus.CONFLICT.value(), createdResponse.getCode()); } @@ -395,59 +357,45 @@ public void createSubmodelElementCollidingId() throws IOException { @Test public void createSubmodelElement() throws FileNotFoundException, IOException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), - element); + CloseableHttpResponse createdResponse = BaSyxHttpTestUtils.executePostOnURL(createSubmodelElementsURL(), element); - CloseableHttpResponse fetchedResponse = requestSubmodelElement( - "MaxRotationSpeedNew"); + CloseableHttpResponse fetchedResponse = requestSubmodelElement("MaxRotationSpeedNew"); assertEquals(HttpStatus.CREATED.value(), createdResponse.getCode()); BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedResponse)); } @Test public void deleteSubmodelElement() throws FileNotFoundException, IOException, ParseException { - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT)); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); - CloseableHttpResponse fetchedResponse = requestSubmodelElement( - DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT); + CloseableHttpResponse fetchedResponse = requestSubmodelElement(DummySubmodelFactory.SUBMODEL_ELEMENT_SIMPLE_DATA_ID_SHORT); assertEquals(HttpStatus.NOT_FOUND.value(), fetchedResponse.getCode()); } @Test - public void createNestedSubmodelElementInSubmodelElementCollection() - throws FileNotFoundException, IOException, ParseException { + public void createNestedSubmodelElementInSubmodelElementCollection() throws FileNotFoundException, IOException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdInCollectionResponse = BaSyxHttpTestUtils.executePostOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_COLLECTION_SIMPLE), element); + CloseableHttpResponse createdInCollectionResponse = BaSyxHttpTestUtils.executePostOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_COLLECTION_SIMPLE), element); assertEquals(HttpStatus.CREATED.value(), createdInCollectionResponse.getCode()); - CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement( - createCollectionNestedIdShortPath("MaxRotationSpeedNew")); - BaSyxHttpTestUtils.assertSameJSONContent(element, - BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInCollectionResponse)); + CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement(createCollectionNestedIdShortPath("MaxRotationSpeedNew")); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInCollectionResponse)); } @Test - public void createNestedSubmodelElementInSubmodelElementList() - throws IOException, JsonProcessingException, JsonMappingException, ParseException { + public void createNestedSubmodelElementInSubmodelElementList() throws IOException, JsonProcessingException, JsonMappingException, ParseException { String element = getJSONValueAsString("SubmodelElementNew.json"); - CloseableHttpResponse createdInListResponse = BaSyxHttpTestUtils.executePostOnURL( - createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_LIST_SIMPLE), element); + CloseableHttpResponse createdInListResponse = BaSyxHttpTestUtils.executePostOnURL(createSpecificSubmodelElementURL(DummySubmodelFactory.SUBMODEL_ELEMENT_LIST_SIMPLE), element); assertEquals(HttpStatus.CREATED.value(), createdInListResponse.getCode()); CloseableHttpResponse fetchedNestedInListResponse = requestSubmodelElement(createListNestedIdShortPath(1)); - BaSyxHttpTestUtils.assertSameJSONContent(element, - BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInListResponse)); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedNestedInListResponse)); } @Test - public void deleteNestedSubmodelElementFromSubmodelElementCollection() - throws FileNotFoundException, IOException, ParseException { - String nestedIdShortPathInCollection = createCollectionNestedIdShortPath( - DummySubmodelFactory.SUBMODEL_ELEMENT_FIRST_ID_SHORT); - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils - .executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortPathInCollection)); + public void deleteNestedSubmodelElementFromSubmodelElementCollection() throws FileNotFoundException, IOException, ParseException { + String nestedIdShortPathInCollection = createCollectionNestedIdShortPath(DummySubmodelFactory.SUBMODEL_ELEMENT_FIRST_ID_SHORT); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortPathInCollection)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); CloseableHttpResponse fetchedNestedInCollectionResponse = requestSubmodelElement(nestedIdShortPathInCollection); @@ -455,11 +403,9 @@ public void deleteNestedSubmodelElementFromSubmodelElementCollection() } @Test - public void deleteNestedSubmodelElementFromSubmodelElementList() - throws IOException { + public void deleteNestedSubmodelElementFromSubmodelElementList() throws IOException { String nestedIdShortSmeList = createListNestedIdShortPath(0); - CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils - .executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortSmeList)); + CloseableHttpResponse deleteResponse = BaSyxHttpTestUtils.executeDeleteOnURL(createSpecificSubmodelElementURL(nestedIdShortSmeList)); assertEquals(HttpStatus.NO_CONTENT.value(), deleteResponse.getCode()); CloseableHttpResponse fetchedNestedInListResponse = requestSubmodelElement(createListNestedIdShortPath(1)); diff --git a/pom.xml b/pom.xml index 51fcae462..0c20a12b6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,6 @@ - + 4.0.0 org.springframework.boot @@ -245,6 +247,17 @@ + + org.apache.tika + tika-core + 2.8.0 + + + + commons-io + commons-io + 2.13.0 + org.eclipse.digitaltwin.basyx