From 7863c28ab822c48f3525e40276b5c0f375fe9880 Mon Sep 17 00:00:00 2001 From: Mateus Molina Date: Thu, 4 Apr 2024 13:14:10 +0200 Subject: [PATCH] Refactor SubmodelRepository FileHandling Methods into SubmodelService (#244) * Move FileRepository interface/implementations to Common * Add file handling methods to SubmodelService API * Move file related tests from RepositorySuite to ServiceSuite * Refactor SubmodelRepo to use file handling logic from Service * Fix name typo in MongoDBFileRepository * Implement /attachment endpoints to the SubmodelServiceHttpController * Modify injection method of FileRepository in SubmodelServiceFactory --- .../AasEnvironmentLoaderTest.java | 3 +- .../TestAASEnvironmentSerialization.java | 3 +- .../pom.xml | 25 ++ .../InMemoryFileRepository.java | 36 +-- .../pom.xml | 21 ++ .../filerepository/MongoDBFileRepository.java | 52 ++-- .../basyx.filerepository-backend/pom.xml | 16 ++ .../core/filerepository}/FileMetadata.java | 2 +- .../core/filerepository}/FileRepository.java | 2 +- basyx.common/pom.xml | 3 + .../pom.xml | 6 + .../SubmodelInMemoryBackendProvider.java | 6 - .../TestInMemorySubmodelRepository.java | 7 +- .../pom.xml | 6 + ...ongoDBSubmodelRepositoryConfiguration.java | 3 +- .../SubmodelMongoDBBackendProvider.java | 11 - .../TestMongoDBSubmodelRepository.java | 28 +- .../basyx.submodelrepository-backend/pom.xml | 4 + .../backend/CrudSubmodelRepository.java | 131 +-------- .../backend/SubmodelBackendProvider.java | 3 - .../backend/CrudSubmodelRepositoryTest.java | 5 - ...modelRepositorySubmodelServiceWrapper.java | 19 ++ .../core/SubmodelRepositorySuite.java | 250 ---------------- .../mqtt/TestMqttSubmodelObserver.java | 3 +- .../TestOperationDelegationFeature.java | 9 +- .../DummySubmodelRepositoryConfig.java | 3 +- .../pom.xml | 5 + .../InMemorySubmodelService.java | 181 ++++++++++-- .../InMemorySubmodelServiceFactory.java | 11 +- .../TestInMemorySubmodelService.java | 9 +- .../client/ConnectedSubmodelService.java | 19 ++ .../client/TestConnectedSubmodelService.java | 62 ++++ .../basyx.submodelservice-core/pom.xml | 5 + .../submodelservice/SubmodelService.java | 44 ++- .../SubmodelServiceHelper.java | 4 +- .../submodelservice/SubmodelServiceSuite.java | 267 ++++++++++++++---- .../http/SubmodelServiceHTTPApi.java | 54 ++++ .../SubmodelServiceHTTPApiController.java | 59 ++++ .../http/DummySubmodelServiceComponent.java | 3 +- ...lServiceSubmodelElementsTestSuiteHTTP.java | 187 +++++++++++- .../src/test/resources/BaSyx-Logo.png | Bin 0 -> 11433 bytes .../example/ExampleSubmodelConfiguration.java | 3 +- .../DummySubmodelServiceComponent.java | 3 +- pom.xml | 33 +++ 44 files changed, 1042 insertions(+), 564 deletions(-) create mode 100644 basyx.common/basyx.filerepository-backend-inmemory/pom.xml rename basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelFileRepository.java => basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java (91%) create mode 100644 basyx.common/basyx.filerepository-backend-mongodb/pom.xml rename basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelFileRepository.java => basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java (84%) create mode 100644 basyx.common/basyx.filerepository-backend/pom.xml rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file => basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository}/FileMetadata.java (97%) rename basyx.common/{basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file => basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository}/FileRepository.java (97%) create mode 100644 basyx.submodelservice/basyx.submodelservice-http/src/test/resources/BaSyx-Logo.png diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java index dc7e8634a..02bebe59d 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java @@ -41,6 +41,7 @@ import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -77,7 +78,7 @@ public class AasEnvironmentLoaderTest { @Before public void setUp() { - submodelRepository = Mockito.spy(new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory())); + submodelRepository = Mockito.spy(new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()))); aasRepository = Mockito.spy(new CrudAasRepository(new AasInMemoryBackendProvider(), new InMemoryAasServiceFactory())); conceptDescriptionRepository = Mockito.spy(new CrudConceptDescriptionRepository(new ConceptDescriptionInMemoryBackendProvider())); } diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestAASEnvironmentSerialization.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestAASEnvironmentSerialization.java index 9f5379d5f..b798a176c 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestAASEnvironmentSerialization.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/TestAASEnvironmentSerialization.java @@ -35,6 +35,7 @@ import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -62,7 +63,7 @@ public class TestAASEnvironmentSerialization { @Before public void setup() { - submodelRepository = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory()).create(); + submodelRepository = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); aasRepository = new SimpleAasRepositoryFactory(new AasInMemoryBackendProvider(), new InMemoryAasServiceFactory()).create(); conceptDescriptionRepository = new SimpleConceptDescriptionRepositoryFactory(new ConceptDescriptionInMemoryBackendProvider(), createDummyConceptDescriptions(), "cdRepo").create(); diff --git a/basyx.common/basyx.filerepository-backend-inmemory/pom.xml b/basyx.common/basyx.filerepository-backend-inmemory/pom.xml new file mode 100644 index 000000000..a6f8cd770 --- /dev/null +++ b/basyx.common/basyx.filerepository-backend-inmemory/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + ${revision} + + basyx.filerepository-backend-inmemory + + + org.eclipse.digitaltwin.basyx + basyx.core + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + + + 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/InMemorySubmodelFileRepository.java b/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java similarity index 91% rename from basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelFileRepository.java rename to basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java index 386be6f9d..be4ce3b90 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/InMemorySubmodelFileRepository.java +++ b/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java @@ -23,7 +23,7 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelrepository; +package org.eclipse.digitaltwin.basyx.core.filerepository; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -37,15 +37,17 @@ import org.apache.commons.io.IOUtils; import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; -import org.eclipse.digitaltwin.basyx.core.file.FileMetadata; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; /** * An InMemory implementation of the {@link FileRepository} * * @author danish */ -public class InMemorySubmodelFileRepository implements FileRepository { +@Component +@ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") +public class InMemoryFileRepository implements FileRepository { private static final String TEMP_DIRECTORY_PREFIX = "basyx-temp"; private String tmpDirectory = getTemporaryDirectoryPath(); @@ -53,7 +55,7 @@ public class InMemorySubmodelFileRepository implements FileRepository { @Override public String save(FileMetadata fileMetadata) throws FileHandlingException { String filePath = createFilePath(fileMetadata.getFileName()); - + java.io.File targetFile = new java.io.File(filePath); try (FileOutputStream outStream = new FileOutputStream(targetFile)) { @@ -61,7 +63,7 @@ public String save(FileMetadata fileMetadata) throws FileHandlingException { } catch (IOException e) { throw new FileHandlingException(fileMetadata.getFileName()); } - + fileMetadata.setFileName(filePath); return filePath; @@ -69,7 +71,7 @@ public String save(FileMetadata fileMetadata) throws FileHandlingException { @Override public InputStream find(String fileId) throws FileDoesNotExistException { - + try { return new FileInputStream(fileId); } catch (FileNotFoundException e) { @@ -79,10 +81,10 @@ public InputStream find(String fileId) throws FileDoesNotExistException { @Override public void delete(String fileId) throws FileDoesNotExistException { - + if (!exists(fileId)) throw new FileDoesNotExistException(); - + java.io.File tmpFile = new java.io.File(fileId); tmpFile.delete(); @@ -90,36 +92,36 @@ public void delete(String fileId) throws FileDoesNotExistException { @Override public boolean exists(String fileId) { - + if (fileId.isBlank() || !isFilePathValid(fileId)) return false; - + return Files.exists(Paths.get(fileId)); } - + 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(TEMP_DIRECTORY_PREFIX).toAbsolutePath().toString(); } catch (IOException e) { throw new RuntimeException(String.format("Unable to create the temporary directory with prefix: %s", TEMP_DIRECTORY_PREFIX)); } - + return tempDirectoryPath; } - + private String createFilePath(String fileName) { return tmpDirectory + "/" + fileName; } diff --git a/basyx.common/basyx.filerepository-backend-mongodb/pom.xml b/basyx.common/basyx.filerepository-backend-mongodb/pom.xml new file mode 100644 index 000000000..1d160db84 --- /dev/null +++ b/basyx.common/basyx.filerepository-backend-mongodb/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + ${revision} + + basyx.filerepository-backend-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + + + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelFileRepository.java b/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java similarity index 84% rename from basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelFileRepository.java rename to basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java index aa427294f..aa56b43e3 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelFileRepository.java +++ b/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java @@ -23,7 +23,7 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelrepository; +package org.eclipse.digitaltwin.basyx.core.filerepository; import java.io.IOException; import java.io.InputStream; @@ -34,21 +34,24 @@ import org.bson.types.ObjectId; import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; -import org.eclipse.digitaltwin.basyx.core.file.FileMetadata; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.stereotype.Component; import com.mongodb.client.gridfs.model.GridFSFile; -import org.springframework.data.mongodb.core.query.Query; - /** * A MongoDB implementation of the {@link FileRepository} * * @author danish */ -public class MongoDBSubmodelFileRepository implements FileRepository { +@Component +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +public class MongoDBFileRepository implements FileRepository { private static final String MONGO_ID = "_id"; private static final String GRIDFS_ID_DELIMITER = "#"; @@ -56,10 +59,19 @@ public class MongoDBSubmodelFileRepository implements FileRepository { private GridFsTemplate gridFsTemplate; - public MongoDBSubmodelFileRepository(GridFsTemplate gridFsTemplate) { + @Autowired + public MongoDBFileRepository(MongoTemplate mongoTemplate) { + this(buildDefaultGridFsTemplate(mongoTemplate)); + } + + public MongoDBFileRepository(GridFsTemplate gridFsTemplate) { this.gridFsTemplate = gridFsTemplate; } + public static GridFsTemplate buildDefaultGridFsTemplate(MongoTemplate mongoTemplate) { + return new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); + } + @Override public String save(FileMetadata fileMetadata) throws FileHandlingException { ObjectId id = gridFsTemplate.store(fileMetadata.getFileContent(), fileMetadata.getFileName(), fileMetadata.getContentType()); @@ -76,7 +88,7 @@ public InputStream find(String fileId) throws FileDoesNotExistException { if (!exists(fileId)) throw new FileDoesNotExistException(); - + String mongoDBfileId = getFileId(fileId); GridFSFile file = getFile(mongoDBfileId); @@ -89,7 +101,7 @@ public void delete(String fileId) throws FileDoesNotExistException { if (!exists(fileId)) throw new FileDoesNotExistException(); - + String mongoDBfileId = getFileId(fileId); gridFsTemplate.delete(new Query(Criteria.where(MONGO_ID).is(mongoDBfileId))); @@ -97,9 +109,9 @@ public void delete(String fileId) throws FileDoesNotExistException { @Override public boolean exists(String fileId) { - + String mongoDBfileId = getFileId(fileId); - + if (mongoDBfileId.isBlank()) return false; @@ -107,20 +119,20 @@ public boolean exists(String fileId) { return gridFSFile != null; } - + private String getFileId(String value) { - + if (value.isBlank()) return ""; - + String fileName = Paths.get(value).getFileName().toString(); - + try { return fileName.substring(0, fileName.indexOf(GRIDFS_ID_DELIMITER)); } catch (IndexOutOfBoundsException e) { return ""; } - + } private GridFSFile getFile(String mongoDBfileId) { @@ -138,22 +150,22 @@ private InputStream getGridFsFileAsInputStream(GridFSFile file) { } private String createFilePath(String id, String fileName) { - + Path tempDir = createTempDirectory(TEMP_DIR_PREFIX); String temporaryDirectoryPath = tempDir.toAbsolutePath().toString(); return temporaryDirectoryPath + "/" + id + GRIDFS_ID_DELIMITER + fileName; } - + private Path createTempDirectory(String prefix) { - + try { return Files.createTempDirectory(prefix); } catch (IOException e) { throw new FileHandlingException("Exception occurred while creating temporary directory with prefix '" + TEMP_DIR_PREFIX + "'." + e.getMessage()); } - + } } diff --git a/basyx.common/basyx.filerepository-backend/pom.xml b/basyx.common/basyx.filerepository-backend/pom.xml new file mode 100644 index 000000000..db8a3a238 --- /dev/null +++ b/basyx.common/basyx.filerepository-backend/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + ${revision} + + basyx.filerepository-backend + BaSyx File Repository Backend + + + org.eclipse.digitaltwin.basyx + basyx.core + + + \ No newline at end of file diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileMetadata.java b/basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileMetadata.java similarity index 97% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileMetadata.java rename to basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileMetadata.java index 0ada012f2..4f85eccfd 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileMetadata.java +++ b/basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileMetadata.java @@ -23,7 +23,7 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.core.file; +package org.eclipse.digitaltwin.basyx.core.filerepository; import java.io.InputStream; diff --git a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileRepository.java b/basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileRepository.java similarity index 97% rename from basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileRepository.java rename to basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileRepository.java index e123ada2a..95b9b40cb 100644 --- a/basyx.common/basyx.core/src/main/java/org/eclipse/digitaltwin/basyx/core/file/FileRepository.java +++ b/basyx.common/basyx.filerepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/FileRepository.java @@ -23,7 +23,7 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.core.file; +package org.eclipse.digitaltwin.basyx.core.filerepository; import java.io.InputStream; diff --git a/basyx.common/pom.xml b/basyx.common/pom.xml index 54730cd6f..16b9c06f6 100644 --- a/basyx.common/pom.xml +++ b/basyx.common/pom.xml @@ -20,5 +20,8 @@ basyx.mongocore basyx.authorization basyx.client + basyx.filerepository-backend + basyx.filerepository-backend-inmemory + basyx.filerepository-backend-mongodb \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml index 982c03d6c..11d97c975 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml @@ -55,5 +55,11 @@ commons-io commons-io + + org.eclipse.digitaltwin.basyx + + basyx.filerepository-backend-inmemory + + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java index 953ae24c9..79b69eb2a 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java @@ -26,7 +26,6 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SubmodelBackendProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.data.repository.CrudRepository; @@ -47,9 +46,4 @@ public CrudRepository getCrudRepository() { return new SubmodelInMemoryBackend(); } - @Override - public FileRepository getFileRepository() { - return new InMemorySubmodelFileRepository(); - } - } 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 3ab27ce78..53e7c15bb 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 @@ -31,6 +31,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; @@ -53,12 +54,12 @@ public class TestInMemorySubmodelRepository extends SubmodelRepositorySuite { @Override protected SubmodelRepository getSubmodelRepository() { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory()).create(); + return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); } @Override protected SubmodelRepository getSubmodelRepository(Collection submodels) { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(), submodels).create(); + return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()), submodels).create(); } @Override @@ -70,7 +71,7 @@ protected boolean fileExistsInStorage(String fileValue) { @Test public void getConfiguredInMemorySmRepositoryName() { - SubmodelRepository repo = new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(), CONFIGURED_SM_REPO_NAME); + SubmodelRepository repo = new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()), CONFIGURED_SM_REPO_NAME); assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml index aa9310aad..64732a72d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml @@ -48,5 +48,11 @@ org.apache.tika tika-core + + org.eclipse.digitaltwin.basyx + + basyx.filerepository-backend-mongodb + + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java index 43c6ef743..b8f3342f5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java @@ -24,6 +24,7 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.submodelrepository; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -45,6 +46,6 @@ public class MongoDBSubmodelRepositoryConfiguration { @Bean public SubmodelServiceFactory getInMemorySubmodelServiceFactory() { - return new InMemorySubmodelServiceFactory(); + return new InMemorySubmodelServiceFactory(new InMemoryFileRepository()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelMongoDBBackendProvider.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelMongoDBBackendProvider.java index bacf23842..cd414bb77 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelMongoDBBackendProvider.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelMongoDBBackendProvider.java @@ -27,14 +27,12 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SubmodelBackendProvider; import org.springframework.beans.factory.annotation.Autowired; 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.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; import org.springframework.data.repository.CrudRepository; @@ -71,13 +69,4 @@ public CrudRepository getCrudRepository() { return new SimpleMongoRepository<>(new MappingMongoEntityInformation<>(entity), template); } - @Override - public FileRepository getFileRepository() { - return new MongoDBSubmodelFileRepository(configureDefaultGridFsTemplate(template)); - } - - private GridFsTemplate configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { - return new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); - } - } 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 e394b86d8..5746eecb0 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 @@ -26,7 +26,6 @@ import static org.junit.Assert.assertEquals; -import java.nio.file.Paths; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -34,6 +33,8 @@ import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.core.filerepository.MongoDBFileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SubmodelBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; @@ -41,13 +42,10 @@ import org.junit.Ignore; import org.junit.Test; import org.springframework.data.mongodb.core.MongoTemplate; -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.MongoClient; import com.mongodb.client.MongoClients; -import com.mongodb.client.gridfs.model.GridFSFile; public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { private final String COLLECTION = "submodelTestCollection"; @@ -56,15 +54,15 @@ public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); private final GridFsTemplate GRIDFS_TEMPLATE = new GridFsTemplate(TEMPLATE.getMongoDatabaseFactory(), TEMPLATE.getConverter()); private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; - private static final String MONGO_ID = "_id"; - private static final String GRIDFS_ID_DELIMITER = "#"; + private FileRepository fileRepository; @Override protected SubmodelRepository getSubmodelRepository() { MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); + fileRepository = new MongoDBFileRepository(GRIDFS_TEMPLATE); SubmodelBackendProvider submodelBackendProvider = new SubmodelMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, TEMPLATE); - SubmodelRepositoryFactory submodelRepositoryFactory = new SimpleSubmodelRepositoryFactory(submodelBackendProvider, new InMemorySubmodelServiceFactory()); + SubmodelRepositoryFactory submodelRepositoryFactory = new SimpleSubmodelRepositoryFactory(submodelBackendProvider, new InMemorySubmodelServiceFactory(fileRepository)); return submodelRepositoryFactory.create(); } @@ -80,18 +78,13 @@ protected SubmodelRepository getSubmodelRepository(Collection submodel @Override protected boolean fileExistsInStorage(String fileValue) { - String fileId = getFileId(fileValue); - - GridFSFile file = GRIDFS_TEMPLATE.findOne(new Query(Criteria.where(MONGO_ID).is(fileId))); - - return file != null && GRIDFS_TEMPLATE.getResource(file).exists(); - + return fileRepository.exists(fileValue); } @Test public void getConfiguredMongoDBSmRepositoryName() { SubmodelBackendProvider submodelBackendProvider = new SubmodelMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, TEMPLATE); - SubmodelRepository repo = new SimpleSubmodelRepositoryFactory(submodelBackendProvider, new InMemorySubmodelServiceFactory(), CONFIGURED_SM_REPO_NAME).create(); + SubmodelRepository repo = new SimpleSubmodelRepositoryFactory(submodelBackendProvider, new InMemorySubmodelServiceFactory(fileRepository), CONFIGURED_SM_REPO_NAME).create(); assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } @@ -119,11 +112,4 @@ private static void addSubmodelsToRepoWithoutInvokableOperations(Collectioncommons-io commons-io + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepository.java index c8ef370e7..aa5685dd1 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepository.java @@ -25,10 +25,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.backend; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -41,7 +38,6 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; 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.File; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; @@ -49,19 +45,14 @@ 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.exceptions.MissingIdentifierException; -import org.eclipse.digitaltwin.basyx.core.file.FileMetadata; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; 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.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; 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.slf4j.Logger; @@ -72,7 +63,7 @@ * Default Implementation for the {@link SubmodelRepository} based on Spring * {@link CrudRepository} * - * @author danish + * @author danish, mateusmolina * */ public class CrudSubmodelRepository implements SubmodelRepository { @@ -82,14 +73,12 @@ public class CrudSubmodelRepository implements SubmodelRepository { private CrudRepository submodelBackend; private SubmodelServiceFactory submodelServiceFactory; - private FileRepository fileHandlingBackend; private String submodelRepositoryName = null; public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory) { this.submodelBackend = submodelBackendProvider.getCrudRepository(); this.submodelServiceFactory = submodelServiceFactory; - this.fileHandlingBackend = submodelBackendProvider.getFileRepository(); } public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, String submodelRepositoryName) { @@ -212,16 +201,6 @@ public void updateSubmodelElement(String submodelId, String idShortPath, Submode throwIfMismatchingIds(element.getIdShort(), submodelElement.getIdShort()); - if (isFileSubmodelElement(element) && !isFileSubmodelElement(submodelElement)) { - - try { - deleteFileValue(submodelId, idShortPath); - } catch (FileDoesNotExistException e) { - logger.info("The Submodel Element with idShortPath '{}' is a File Submodel Element but there is no file attachment associated with this.", idShortPath); - } - - } - submodelService.updateSubmodelElement(idShortPath, submodelElement); updateSubmodel(submodelId, submodelService.getSubmodel()); @@ -231,8 +210,6 @@ public void updateSubmodelElement(String submodelId, String idShortPath, Submode public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - deleteAssociatedFile(submodelId, idShortPath); - submodelService.deleteSubmodelElement(idShortPath); updateSubmodel(submodelId, submodelService.getSubmodel()); @@ -258,105 +235,29 @@ public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNot @Override public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - SubmodelElement submodelElement = getSubmodelElement(submodelId, idShortPath); - - throwIfSmElementIsNotAFile(submodelElement); - - File fileSmElement = (File) submodelElement; - String filePath = getFilePath(fileSmElement); - - InputStream fileContent = getFileInputStream(filePath); - - return createFile(filePath, fileContent); + return submodelService.getFileByPath(idShortPath); } @Override public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - SubmodelElement submodelElement = getSubmodelElement(submodelId, idShortPath); - - throwIfSmElementIsNotAFile(submodelElement); - - File fileSmElement = (File) submodelElement; - - if (fileHandlingBackend.exists(fileSmElement.getValue())) - fileHandlingBackend.delete(fileSmElement.getValue()); - - String uniqueFileName = createUniqueFileName(submodelId, idShortPath, fileName); - - FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); - - String filePath = fileHandlingBackend.save(fileMetadata); + SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + submodelService.setFileValue(idShortPath, fileName, inputStream); - setSubmodelElementValue(submodelId, idShortPath, fileValue); + updateSubmodel(submodelId, submodelService.getSubmodel()); } @Override public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - SubmodelElement submodelElement = getSubmodelElement(submodelId, idShortPath); - - throwIfSmElementIsNotAFile(submodelElement); - - File fileSubmodelElement = (File) submodelElement; - String filePath = fileSubmodelElement.getValue(); - - fileHandlingBackend.delete(filePath); + SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - FileBlobValue fileValue = new FileBlobValue(" ", " "); + submodelService.deleteFileValue(idShortPath); - setSubmodelElementValue(submodelId, idShortPath, fileValue); + updateSubmodel(submodelId, submodelService.getSubmodel()); } - private void deleteAssociatedFile(String submodelId, String idShortPath) { - try { - deleteFileValue(submodelId, idShortPath); - } catch (Exception e) { - return; - } - } - - private boolean isFileSubmodelElement(SubmodelElement submodelElement) { - return submodelElement instanceof File; - } - - private InputStream getFileInputStream(String filePath) { - InputStream fileContent; - - try { - fileContent = fileHandlingBackend.find(filePath); - } catch (FileDoesNotExistException e) { - throw new FileDoesNotExistException(String.format("File at path '%s' could not be found.", filePath)); - } - - return fileContent; - } - - private java.io.File createFile(String filePath, InputStream fileIs) { - - try { - byte[] content = fileIs.readAllBytes(); - fileIs.close(); - - createOutputStream(filePath, content); - - return new java.io.File(filePath); - } catch (IOException e) { - throw new FileHandlingException("Exception occurred while creating file from the InputStream." + e.getMessage()); - } - - } - - private void createOutputStream(String filePath, byte[] content) throws IOException { - - try (OutputStream outputStream = new FileOutputStream(filePath)) { - outputStream.write(content); - } catch (IOException e) { - throw new FileHandlingException("Exception occurred while creating OutputStream from byte[]." + e.getMessage()); - } - - } private void initializeRemoteCollection(Collection submodels) { if (submodels == null || submodels.isEmpty()) @@ -394,20 +295,6 @@ private Submodel getSubmodelDeepCopy(Submodel submodel) { } } - private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { - - if (!isFileSubmodelElement(submodelElement)) - throw new ElementNotAFileException(submodelElement.getIdShort()); - } - - private String getFilePath(File fileSubmodelElement) { - return fileSubmodelElement.getValue(); - } - - private String createUniqueFileName(String submodelId, String idShortPath, String fileName) { - return Base64UrlEncodedIdentifier.encodeIdentifier(submodelId) + "-" + idShortPath.replace("/", "-") + "-" + fileName; - } - private SubmodelService getSubmodelServiceOrThrow(String submodelId) { Submodel submodel = submodelBackend.findById(submodelId).orElseThrow(() -> new ElementDoesNotExistException(submodelId)); diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java index a023570f4..ae3edc9db 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java @@ -26,7 +26,6 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.backend; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; import org.springframework.data.repository.CrudRepository; /** @@ -38,6 +37,4 @@ public interface SubmodelBackendProvider { public CrudRepository getCrudRepository(); - public FileRepository getFileRepository(); - } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryTest.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryTest.java index 8b541a70c..9f3c658dd 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryTest.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryTest.java @@ -28,7 +28,6 @@ import static org.junit.Assert.assertEquals; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.core.file.FileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.junit.Test; import org.springframework.data.repository.CrudRepository; @@ -57,10 +56,6 @@ public CrudRepository getCrudRepository() { return null; } - @Override - public FileRepository getFileRepository() { - return null; - } }; } diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySubmodelServiceWrapper.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySubmodelServiceWrapper.java index 4ffeeadb8..1a0faae40 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySubmodelServiceWrapper.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySubmodelServiceWrapper.java @@ -25,12 +25,16 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.core; +import java.io.File; +import java.io.InputStream; import java.util.List; 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.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.submodelrepository.SubmodelRepository; @@ -115,4 +119,19 @@ public void patchSubmodelElements(List submodelElementList) { repoApi.patchSubmodelElements(submodelId, submodelElementList); } + @Override + public File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + return repoApi.getFileByPathSubmodel(submodelId, idShortPath); + } + + @Override + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + repoApi.setFileValue(submodelId, idShortPath, fileName, inputStream); + } + + @Override + public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + repoApi.deleteFileValue(submodelId, idShortPath); + } + } 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 fa04eaf90..13a4024a8 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 @@ -26,34 +26,22 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.core; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; 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.Arrays; import java.util.Collection; import java.util.Collections; 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; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; 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.MissingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; @@ -66,7 +54,6 @@ import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceSuite; 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 @@ -76,7 +63,6 @@ */ public abstract class SubmodelRepositorySuite extends SubmodelServiceSuite { private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); - private static final String DUMMY_FILE_CONTENT = "this is a file"; private static final String EMPTY_ID = " "; private static final String NULL_ID = null; @@ -87,8 +73,6 @@ protected SubmodelRepository getSubmodelRepository(Collection submodel submodels.forEach(repo::createSubmodel); return repo; } - - protected abstract boolean fileExistsInStorage(String fileValue); @Test public void getAllSubmodelsPreconfigured() { @@ -200,94 +184,6 @@ public void createSubmodelCollectionWithMissingId() { getSubmodelRepository(submodels); } - - @Test - public void updateNonFileSME() { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT; - - Property newProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "arbitraryValue", DataTypeDefXsd.STRING); - - repo.updateSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, newProperty); - - Property updatedProperty = (Property) repo.getSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertEquals(newProperty, updatedProperty); - } - - @Test - public void updateNonFileSMEWithFileSME() { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT; - - org.eclipse.digitaltwin.aas4j.v3.model.File newFileSME = SubmodelServiceHelper.createDummyFile(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "text/plain", "arbitraryFileValue"); - - repo.updateSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, newFileSME); - - org.eclipse.digitaltwin.aas4j.v3.model.File updatedFile = (org.eclipse.digitaltwin.aas4j.v3.model.File) repo.getSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertEquals(newFileSME, updatedFile); - } - - @Test - public void updateFileSMEWithNonFileSME() throws FileNotFoundException, IOException { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; - - String fileName = "SampleJsonFile.json"; - - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, fileName, getInputStreamOfFileFromClasspath(fileName)); - - File file = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertFileExistsOnPath(file); - - Property newProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "4005", DataTypeDefXsd.INT); - - repo.updateSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, newProperty); - - Property updatedProperty = (Property) repo.getSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertEquals(newProperty, updatedProperty); - } - - @Test - public void updateFileSMEWithFileSME() throws FileNotFoundException, IOException { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; - - String fileName = "SampleJsonFile.json"; - - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, fileName, getInputStreamOfFileFromClasspath(fileName)); - - File file = repo.getFileByPathSubmodel(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertFileExistsOnPath(file); - - org.eclipse.digitaltwin.aas4j.v3.model.File newFileSME = SubmodelServiceHelper.createDummyFile(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "text/plain", "someArbitraryPlainText"); - - repo.updateSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, newFileSME); - - org.eclipse.digitaltwin.aas4j.v3.model.File updatedFileSME = (org.eclipse.digitaltwin.aas4j.v3.model.File) repo.getSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol); - - assertEquals(newFileSME, updatedFileSME); - assertFileExistsOnPath(file); - } - - @Test(expected = ElementDoesNotExistException.class) - public void updateNonExistingSME() { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + ".NonExistingSMEIdShort"; - - Property newNonExistingProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "arbitraryPropertyValue", DataTypeDefXsd.STRING); - - repo.updateSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, idShortPathPropertyInSmeCol, newNonExistingProperty); - } @Test public void deleteSubmodel() { @@ -338,105 +234,6 @@ 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() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "SampleJsonFile.json", getInputStreamOfFileFromClasspath("SampleJsonFile.json")); - - 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 (FileDoesNotExistException expected) { - } - } - - @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 setSubmodelElementValueOfNonExistingSubmodel() { SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); @@ -446,24 +243,6 @@ public void setSubmodelElementValueOfNonExistingSubmodel() { repo.setSubmodelElementValue("nonExisting", "doesNotMatter", valueToWrite); } - @Test - public void deleteFileSubmodelElementDeletesFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { - SubmodelRepository repo = getSubmodelRepositoryWithDummySubmodels(); - - final String filename = "SampleJsonFile.json"; - - repo.setFileValue(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, filename, getInputStreamOfFileFromClasspath(filename)); - - SubmodelElement submodelElement = repo.getSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); - String fileValue = ((org.eclipse.digitaltwin.aas4j.v3.model.File) submodelElement).getValue(); - - assertTrue(fileExistsInStorage(fileValue)); - - repo.deleteSubmodelElement(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); - - assertFalse(fileExistsInStorage(fileValue)); - } - @Test public void getDefaultSubmodelRepositoryName() { SubmodelRepository repo = getSubmodelRepository(); @@ -502,21 +281,6 @@ public void invokeNonOperation() { submodelRepo.invokeOperation(DummySubmodelFactory.SUBMODEL_TECHNICAL_DATA_ID, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, new OperationVariable[0]); } - - private void assertFileExistsOnPath(File file) { - - assertTrue(file.exists()); - } - - 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 Submodel buildDummySubmodel(String id) { return new DefaultSubmodel.Builder().id(id).submodelElements(new DefaultProperty.Builder().idShort("prop").value("testValue").valueType(DataTypeDefXsd.STRING).build()).build(); @@ -536,20 +300,6 @@ private void assertIsEmpty(Collection submodels) { assertTrue(submodels.isEmpty()); } - 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); - } - @Override public SubmodelService getSubmodelService(Submodel submodel) { return new SubmodelRepositorySubmodelServiceWrapper(getSubmodelRepository(), submodel); diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java index 1156d074a..1198e9ab1 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java @@ -43,6 +43,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.common.mqttcore.encoding.Base64URLEncoder; import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelElementSerializer; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; @@ -196,7 +197,7 @@ private SubmodelElement createSubmodelElementDummy(String submodelElementId) { } private static SubmodelRepository createMqttSubmodelRepository(MqttClient client) { - SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory()); + SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())); return new MqttSubmodelRepositoryFactory(repoFactory, client, new MqttSubmodelRepositoryTopicFactory(new Base64URLEncoder())).create(); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-operation-delegation/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/operation/delegation/TestOperationDelegationFeature.java b/basyx.submodelrepository/basyx.submodelrepository-feature-operation-delegation/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/operation/delegation/TestOperationDelegationFeature.java index 8f7bad44c..1aa094112 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-operation-delegation/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/operation/delegation/TestOperationDelegationFeature.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-operation-delegation/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/operation/delegation/TestOperationDelegationFeature.java @@ -26,6 +26,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.operation.delegation; import static org.junit.Assert.assertArrayEquals; + import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; @@ -43,6 +44,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.InvokableOperation; import org.eclipse.digitaltwin.basyx.core.exceptions.OperationDelegationException; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; @@ -58,15 +60,14 @@ import org.junit.BeforeClass; import org.junit.Test; import org.mockserver.model.HttpStatusCode; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.codec.json.Jackson2JsonEncoder; - /** * Tests the {@link OperationDelegationSubmodelRepository} feature * @@ -189,7 +190,7 @@ private void createExpectationsForPost(String path, String requestBody, String e } private static SubmodelRepository createOperationDelegationSubmodelRepository(OperationDelegation operationDelegation) { - SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory()); + SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())); return new OperationDelegationSubmodelRepositoryFactory(repoFactory, operationDelegation).create(); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/testconfig/DummySubmodelRepositoryConfig.java b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/testconfig/DummySubmodelRepositoryConfig.java index af57719fa..88934fe35 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/testconfig/DummySubmodelRepositoryConfig.java +++ b/basyx.submodelrepository/basyx.submodelrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/http/testconfig/DummySubmodelRepositoryConfig.java @@ -25,6 +25,7 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.http.testconfig; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; @@ -45,6 +46,6 @@ public class DummySubmodelRepositoryConfig { @Bean @ConditionalOnMissingBean public SubmodelRepository createSubmodelRepository() { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory()).create(); + return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); } } diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml index 62858bb06..8f3759f2f 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml @@ -17,6 +17,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-core + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-inmemory + org.eclipse.digitaltwin.basyx basyx.submodelservice-core @@ -32,5 +36,6 @@ org.springframework.boot spring-boot-starter + \ No newline at end of file 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 f3c1aa9e6..3f0101f93 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 @@ -25,10 +25,15 @@ package org.eclipse.digitaltwin.basyx.submodelservice; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; @@ -37,21 +42,27 @@ import org.eclipse.digitaltwin.basyx.InvokableOperation; 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.NotInvokableException; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; 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.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.submodelservice.pathparsing.HierarchicalSubmodelElementParser; import org.eclipse.digitaltwin.basyx.submodelservice.pathparsing.SubmodelElementIdShortHelper; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.factory.SubmodelElementValueMapperFactory; import org.eclipse.digitaltwin.basyx.submodelservice.value.mapper.ValueMapper; - /** * Implements the SubmodelService as in-memory variant * - * @author schnicke, danish + * @author schnicke, danish, mateusmolina * */ public class InMemorySubmodelService implements SubmodelService { @@ -60,13 +71,16 @@ public class InMemorySubmodelService implements SubmodelService { private HierarchicalSubmodelElementParser parser; private SubmodelElementIdShortHelper helper = new SubmodelElementIdShortHelper(); + private final FileRepository fileRepository; + /** * Creates the InMemory SubmodelService containing the passed Submodel * * @param submodel */ - public InMemorySubmodelService(Submodel submodel) { + public InMemorySubmodelService(Submodel submodel, FileRepository fileRepository) { this.submodel = submodel; + this.fileRepository = fileRepository; parser = new HierarchicalSubmodelElementParser(submodel); } @@ -79,11 +93,9 @@ public Submodel getSubmodel() { public CursorResult> getSubmodelElements(PaginationInfo pInfo) { List allSubmodels = submodel.getSubmodelElements(); - TreeMap submodelMap = allSubmodels.stream() - .collect(Collectors.toMap(SubmodelElement::getIdShort, aas -> aas, (a, b) -> a, TreeMap::new)); + TreeMap submodelMap = allSubmodels.stream().collect(Collectors.toMap(SubmodelElement::getIdShort, aas -> aas, (a, b) -> a, TreeMap::new)); - PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, - SubmodelElement::getIdShort); + PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, SubmodelElement::getIdShort); CursorResult> paginatedSubmodels = paginationSupport.getPaged(pInfo); return paginatedSubmodels; } @@ -96,7 +108,7 @@ public SubmodelElement getSubmodelElement(String idShortPath) throws ElementDoes @Override public SubmodelElementValue getSubmodelElementValue(String idShort) throws ElementDoesNotExistException { SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); - + return submodelElementValueFactory.create(getSubmodelElement(idShort)).getValue(); } @@ -104,26 +116,26 @@ public SubmodelElementValue getSubmodelElementValue(String idShort) throws Eleme @Override public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); - + ValueMapper valueMapper = submodelElementValueFactory.create(getSubmodelElement(idShort)); - - valueMapper.setValue(value); + + valueMapper.setValue(value); } @Override public void createSubmodelElement(SubmodelElement submodelElement) throws CollidingIdentifierException { throwIfSubmodelElementExists(submodelElement.getIdShort()); - + List smElements = submodel.getSubmodelElements(); smElements.add(submodelElement); submodel.setSubmodelElements(smElements); } - + private void throwIfSubmodelElementExists(String submodelElementId) { try { getSubmodelElement(submodelElementId); - throw new CollidingIdentifierException(submodelElementId); - }catch(ElementDoesNotExistException e) { + throw new CollidingIdentifierException(submodelElementId); + } catch (ElementDoesNotExistException e) { return; } } @@ -131,9 +143,9 @@ private void throwIfSubmodelElementExists(String submodelElementId) { @Override public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException, CollidingIdentifierException { throwIfSubmodelElementExists(getFullIdShortPath(idShortPath, submodelElement.getIdShort())); - + SubmodelElement parentSme = parser.getSubmodelElementFromIdShortPath(idShortPath); - if(parentSme instanceof SubmodelElementList) { + if (parentSme instanceof SubmodelElementList) { SubmodelElementList list = (SubmodelElementList) parentSme; List submodelElements = list.getValue(); submodelElements.add(submodelElement); @@ -148,18 +160,20 @@ public void createSubmodelElement(String idShortPath, SubmodelElement submodelEl return; } } - + @Override public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) { deleteSubmodelElement(idShortPath); - + String idShortPathParentSME = parser.getIdShortPathOfParentElement(idShortPath); - + createSubmodelElement(idShortPathParentSME, submodelElement); } @Override public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { + deleteAssociatedFileIfAny(idShortPath); + if (!helper.isNestedIdShortPath(idShortPath)) { deleteFlatSubmodelElement(idShortPath); return; @@ -169,7 +183,7 @@ public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExist private void deleteNestedSubmodelElement(String idShortPath) { SubmodelElement sm = parser.getSubmodelElementFromIdShortPath(idShortPath); - if(helper.isDirectParentASubmodelElementList(idShortPath)) { + if (helper.isDirectParentASubmodelElementList(idShortPath)) { deleteNestedSubmodelElementFromList(idShortPath, sm); } else { deleteNestedSubmodelElementFromCollection(idShortPath, sm); @@ -184,8 +198,7 @@ private void deleteNestedSubmodelElementFromList(String idShortPath, SubmodelEle private void deleteNestedSubmodelElementFromCollection(String idShortPath, SubmodelElement sm) { String collectionId = helper.extractDirectParentSubmodelElementCollectionIdShort(idShortPath); - SubmodelElementCollection collection = (SubmodelElementCollection) parser - .getSubmodelElementFromIdShortPath(collectionId); + SubmodelElementCollection collection = (SubmodelElementCollection) parser.getSubmodelElementFromIdShortPath(collectionId); collection.getValue().remove(sm); } @@ -210,14 +223,14 @@ private int findIndexOfElementTobeDeleted(String idShortPath) { @Override public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) { SubmodelElement sme = getSubmodelElement(idShortPath); - + if (!(sme instanceof InvokableOperation)) throw new NotInvokableException(idShortPath); - + InvokableOperation operation = (InvokableOperation) sme; return operation.invoke(input); } - + private String getFullIdShortPath(String idShortPath, String submodelElementId) { return idShortPath + "." + submodelElementId; } @@ -226,4 +239,120 @@ private String getFullIdShortPath(String idShortPath, String submodelElementId) public void patchSubmodelElements(List submodelElementList) { this.submodel.setSubmodelElements(submodelElementList); } + + @Override + public java.io.File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + SubmodelElement submodelElement = getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + String filePath = getFilePath(fileSmElement); + + InputStream fileContent = getFileInputStream(filePath); + + return createFile(filePath, fileContent); + } + + @Override + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + SubmodelElement submodelElement = getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + if (fileRepository.exists(fileSmElement.getValue())) + fileRepository.delete(fileSmElement.getValue()); + + String uniqueFileName = createUniqueFileName(idShortPath, fileName); + + FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); + + String filePath = fileRepository.save(fileMetadata); + + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + + setSubmodelElementValue(idShortPath, fileValue); + + } + + @Override + public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + SubmodelElement submodelElement = getSubmodelElement(idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSubmodelElement = (File) submodelElement; + String filePath = fileSubmodelElement.getValue(); + + fileRepository.delete(filePath); + + FileBlobValue fileValue = new FileBlobValue(" ", " "); + + setSubmodelElementValue(idShortPath, fileValue); + } + + private void deleteAssociatedFileIfAny(String idShortPath) { + try { + deleteFileValue(idShortPath); + } catch (Exception e) { + } + } + + private boolean isFileSubmodelElement(SubmodelElement submodelElement) { + return submodelElement instanceof File; + } + + private InputStream getFileInputStream(String filePath) { + InputStream fileContent; + + try { + fileContent = fileRepository.find(filePath); + } catch (FileDoesNotExistException e) { + throw new FileDoesNotExistException(String.format("File at path '%s' could not be found.", filePath)); + } + + return fileContent; + } + + private java.io.File createFile(String filePath, InputStream fileIs) { + + try { + byte[] content = fileIs.readAllBytes(); + fileIs.close(); + + createOutputStream(filePath, content); + + return new java.io.File(filePath); + } catch (IOException e) { + throw new FileHandlingException("Exception occurred while creating file from the InputStream." + e.getMessage()); + } + + } + + private String getFilePath(File fileSubmodelElement) { + return fileSubmodelElement.getValue(); + } + + private String createUniqueFileName(String idShortPath, String fileName) { + return Base64UrlEncodedIdentifier.encodeIdentifier(submodel.getId()) + "-" + idShortPath.replace("/", "-") + "-" + fileName; + } + + private void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + + if (!isFileSubmodelElement(submodelElement)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + + private void createOutputStream(String filePath, byte[] content) throws IOException { + + try (OutputStream outputStream = new FileOutputStream(filePath)) { + outputStream.write(content); + } catch (IOException e) { + throw new FileHandlingException("Exception occurred while creating OutputStream from byte[]." + e.getMessage()); + } + + } + } diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java index 08bf6fff3..2ae48ef10 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java @@ -27,22 +27,29 @@ package org.eclipse.digitaltwin.basyx.submodelservice; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** * SubmodelService factory returning an in-memory backend SubmodelService * - * @author schnicke + * @author schnicke, mateusmolina * */ @ConditionalOnExpression("'${basyx.submodelservice.backend}'.equals('InMemory') or '${basyx.backend}'.equals('InMemory')") @Component public class InMemorySubmodelServiceFactory implements SubmodelServiceFactory { + private final FileRepository fileRepository; + + public InMemorySubmodelServiceFactory(FileRepository fileRepository) { + this.fileRepository = fileRepository; + } + @Override public SubmodelService create(Submodel submodel) { - return new InMemorySubmodelService(submodel); + return new InMemorySubmodelService(submodel, fileRepository); } } diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestInMemorySubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestInMemorySubmodelService.java index 2bc6be649..60926d00d 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestInMemorySubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestInMemorySubmodelService.java @@ -27,6 +27,7 @@ package org.eclipse.digitaltwin.basyx.submodelservice; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; /** * @@ -37,7 +38,13 @@ public class TestInMemorySubmodelService extends SubmodelServiceSuite { @Override protected SubmodelService getSubmodelService(Submodel submodel) { - return new InMemorySubmodelServiceFactory().create(submodel); + return new InMemorySubmodelServiceFactory(new InMemoryFileRepository()).create(submodel); } + @Override + protected boolean fileExistsInStorage(String fileValue) { + java.io.File file = new java.io.File(fileValue); + + return file.exists(); + } } diff --git a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java index 02068b489..77d614432 100644 --- a/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-client/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/client/ConnectedSubmodelService.java @@ -26,6 +26,8 @@ package org.eclipse.digitaltwin.basyx.submodelservice.client; +import java.io.File; +import java.io.InputStream; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; @@ -33,7 +35,9 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.client.internal.ApiException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotImplementedException; +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.SubmodelService; @@ -148,4 +152,19 @@ public void patchSubmodelElements(List submodelElementList) { throw new FeatureNotImplementedException(); } + @Override + public File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + throw new FeatureNotImplementedException(); + } + + @Override + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + throw new FeatureNotImplementedException(); + } + + @Override + public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + throw new FeatureNotImplementedException(); + } + } diff --git a/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java index 2a2216333..b5051fa2a 100644 --- a/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-client/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/client/TestConnectedSubmodelService.java @@ -27,6 +27,8 @@ package org.eclipse.digitaltwin.basyx.submodelservice.client; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.NotInvokableException; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -75,6 +77,13 @@ protected SubmodelService getSubmodelService(Submodel submodel) { return new ConnectedSubmodelService("http://localhost:8080/submodels/" + base64UrlEncodedId); } + @Override + protected boolean fileExistsInStorage(String fileValue) { + java.io.File file = new java.io.File(fileValue); + + return file.exists(); + } + @Override public void getSubmodelElements() { // TODO Auto-generated method stub @@ -105,4 +114,57 @@ public void invokeNonOperation() { throw new NotInvokableException(); } + @Override + public void deleteFileSubmodelElementDeletesFile() { + // TODO Auto-generated method stub + + } + + @Override + public void updateFileSMEWithNonFileSME() { + // TODO Auto-generated method stub + + } + + @Override + public void updateFileSMEWithFileSME() { + // TODO Auto-generated method stub + + } + + @Override + public void getNonExistingFile() { + throw new FileDoesNotExistException(); + + } + + @Override + public void deleteNonExistingFile() { + throw new FileDoesNotExistException(); + + } + + @Override + public void getFileFromNonFileSME() { + throw new ElementNotAFileException(); + + } + + @Override + public void deleteFile() { + // TODO Auto-generated method stub + + } + + @Override + public void getFile() { + // TODO Auto-generated method stub + + } + + @Override + public void updateFile() { + // TODO Auto-generated method stub + + } } diff --git a/basyx.submodelservice/basyx.submodelservice-core/pom.xml b/basyx.submodelservice/basyx.submodelservice-core/pom.xml index 4848c6a3e..4183c527d 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/pom.xml +++ b/basyx.submodelservice/basyx.submodelservice-core/pom.xml @@ -30,5 +30,10 @@ org.eclipse.digitaltwin.basyx basyx.http + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelService.java b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelService.java index 8bf2a383c..776b2b909 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelService.java @@ -25,12 +25,15 @@ package org.eclipse.digitaltwin.basyx.submodelservice; +import java.io.InputStream; import java.util.List; 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.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; @@ -38,7 +41,7 @@ /** * Specifies the overall SubmodelService API * - * @author schnicke + * @author schnicke, mateusmolina * */ public interface SubmodelService { @@ -143,4 +146,43 @@ public interface SubmodelService { */ public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException; + /** + * Retrieves the file of a file submodelelement + * + * @param idShortPath + * the IdShort path of the file element + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException + */ + public java.io.File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; + + /** + * Uploads a file to a file submodelelement + * + * @param idShortPath + * the IdShort path of the file element + * @param fileName + * the file name + * @param inputStream + * the inputStream of the file to be uploaded + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + */ + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException; + + /** + * Deletes the file of a file submodelelement + * + * @param idShortPath + * the IdShort path of the file element + * + * @throws ElementDoesNotExistException + * @throws ElementNotAFileException + * @throws FileDoesNotExistException + */ + public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException; + } diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceHelper.java b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceHelper.java index 5b7df0c55..ab3bcae87 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceHelper.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceHelper.java @@ -127,6 +127,7 @@ public class SubmodelServiceHelper { public static final String SUBMODEL_TECHNICAL_DATA_FILE_CATEGORY = "PARAMETER"; public static final String SUBMODEL_TECHNICAL_DATA_FILE_VALUE = "testFile.json"; public static final String SUBMODEL_TECHNICAL_DATA_FILE_CONTENT_TYPE = "application/json"; + public static final String SUBMODEL_TECHNICAL_NON_FILE = "application/json"; // SUBMODEL_ELEMENT_B_DATA public static final String SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT = "BlobData"; @@ -329,7 +330,8 @@ public static SubmodelElementList createSubmodelElementList() { public static List getAllSubmodelElements() { List list = new ArrayList<>(); list.addAll( - Lists.newArrayList(createPropertySubmodelElement(), createRangeSubmodelElement(), createMultiLanguagePropertySubmodelElement(), createFileSubmodelElement(), createEntitySubmodelElement(), createReferenceElementSubmodelElement(), + Lists.newArrayList(createPropertySubmodelElement(), createRangeSubmodelElement(), createMultiLanguagePropertySubmodelElement(), createFileSubmodelElement(), createEntitySubmodelElement(), + createReferenceElementSubmodelElement(), createRelationshipElementSubmodelElement(), createAnnotatedRelationshipElementSubmodelElement(), createBlobSubmodelElement(), createSubmodelElementCollection(), createSubmodelElementList(), createInvokableOperation())); return list; diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java index 4b2c7f4c7..a599d6b90 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceSuite.java @@ -26,13 +26,20 @@ package org.eclipse.digitaltwin.basyx.submodelservice; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.eclipse.digitaltwin.aas4j.v3.model.DataTypeDefXsd; import org.eclipse.digitaltwin.aas4j.v3.model.Entity; import org.eclipse.digitaltwin.aas4j.v3.model.File; @@ -50,6 +57,8 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; 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.NotInvokableException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; @@ -62,13 +71,25 @@ /** * Testsuite for implementations of the SubmodelService interface * - * @author schnicke, danish + * @author schnicke, danish, mateusmolina * */ public abstract class SubmodelServiceSuite { protected static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + protected abstract SubmodelService getSubmodelService(Submodel submodel); + /** + * SubmodelService independent way to check if a file exists in storage + * + * @param fileValue + * @return + */ + protected abstract boolean fileExistsInStorage(String fileValue); + + private static final String DUMMY_JSON_1 = "{\"name\":\"SampleJsonFile\",\"description\":\"A JSON file for verification\",\"version\":1}"; + private static final String DUMMY_JSON_2 = "{\"name\":\"SampleJsonFile\",\"description\":\"A JSON file for verification\",\"version\":2}"; + @Test public void getSubmodel() { Submodel technicalData = DummySubmodelFactory.createTechnicalDataSubmodel(); @@ -82,8 +103,7 @@ public void getSubmodelElements() { Submodel technicalData = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService smService = getSubmodelService(technicalData); - assertTrue(technicalData.getSubmodelElements() - .containsAll(smService.getSubmodelElements(NO_LIMIT_PAGINATION_INFO).getResult())); + assertTrue(technicalData.getSubmodelElements().containsAll(smService.getSubmodelElements(NO_LIMIT_PAGINATION_INFO).getResult())); } @Test @@ -127,11 +147,7 @@ public void getHierarchicalSubmodelElementWhenFirstElementIsList() { SubmodelElementList submodelElementList = new DefaultSubmodelElementList(); submodelElementList.setIdShort("testList"); List listElements = new ArrayList<>(); - Property testProperty = new DefaultProperty.Builder().idShort("propIdShort") - .category("cat1") - .value("123") - .valueType(DataTypeDefXsd.INTEGER) - .build(); + Property testProperty = new DefaultProperty.Builder().idShort("propIdShort").category("cat1").value("123").valueType(DataTypeDefXsd.INTEGER).build(); listElements.add(testProperty); submodelElementList.setValue(listElements); submodelElementsList.add(submodelElementList); @@ -261,12 +277,7 @@ public void getRangeValue() { public void getMultiLanguagePropertyValue() { Submodel technicalData = DummySubmodelFactory.createTechnicalDataSubmodel(); - List expectedValue = Arrays.asList(new DefaultLangStringTextType.Builder().text("Hello") - .language("en") - .build(), - new DefaultLangStringTextType.Builder().text("Hallo") - .language("de") - .build()); + List expectedValue = Arrays.asList(new DefaultLangStringTextType.Builder().text("Hello").language("en").build(), new DefaultLangStringTextType.Builder().text("Hallo").language("de").build()); MultiLanguagePropertyValue submodelElementValue = (MultiLanguagePropertyValue) getSubmodelService(technicalData).getSubmodelElementValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); @@ -316,17 +327,9 @@ public void createNestedSubmodelElement() { Submodel operationDataSubmodel = DummySubmodelFactory.createOperationalDataSubmodelWithHierarchicalSubmodelElements(); SubmodelService submodelService = getSubmodelService(operationDataSubmodel); - Property propertyInSmeCol = new DefaultProperty.Builder().idShort("test123") - .category("cat1") - .value("305") - .valueType(DataTypeDefXsd.INTEGER) - .build(); + Property propertyInSmeCol = new DefaultProperty.Builder().idShort("test123").category("cat1").value("305").valueType(DataTypeDefXsd.INTEGER).build(); - Property propertyInSmeList = new DefaultProperty.Builder().idShort("test456") - .category("cat1") - .value("305") - .valueType(DataTypeDefXsd.INTEGER) - .build(); + Property propertyInSmeList = new DefaultProperty.Builder().idShort("test456").category("cat1").value("305").valueType(DataTypeDefXsd.INTEGER).build(); String idShortPathPropertyInSmeCol = DummySubmodelFactory.SUBMODEL_OPERATIONAL_DATA_ELEMENT_COLLECTION_ID_SHORT; submodelService.createSubmodelElement(idShortPathPropertyInSmeCol, propertyInSmeCol); @@ -342,51 +345,93 @@ public void createNestedSubmodelElement() { SubmodelElement propertyInSmeListCreated = submodelService.getSubmodelElement(idShortPathPropertyInSmeList); assertEquals("test456", propertyInSmeListCreated.getIdShort()); } - + @Test public void updateNonFileSME() { Submodel technicalSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService submodelService = getSubmodelService(technicalSubmodel); - + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT; - + Property newProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "arbitraryValue", DataTypeDefXsd.STRING); - + submodelService.updateSubmodelElement(idShortPathPropertyInSmeCol, newProperty); - + Property updatedProperty = (Property) submodelService.getSubmodelElement(idShortPathPropertyInSmeCol); - + assertEquals(newProperty, updatedProperty); } - + @Test public void updateNonFileSMEWithFileSME() { Submodel technicalSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService submodelService = getSubmodelService(technicalSubmodel); - + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT; - + org.eclipse.digitaltwin.aas4j.v3.model.File newFileSME = SubmodelServiceHelper.createDummyFile(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "text/plain", "arbitraryFileValue"); - + submodelService.updateSubmodelElement(idShortPathPropertyInSmeCol, newFileSME); - + org.eclipse.digitaltwin.aas4j.v3.model.File updatedFile = (org.eclipse.digitaltwin.aas4j.v3.model.File) submodelService.getSubmodelElement(idShortPathPropertyInSmeCol); - + assertEquals(newFileSME, updatedFile); } - + @Test(expected = ElementDoesNotExistException.class) public void updateNonExistingSME() { Submodel technicalSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService submodelService = getSubmodelService(technicalSubmodel); - + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + "NonExistingSMEIdShort"; - + Property newNonExistingProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT, "arbitraryPropertyValue", DataTypeDefXsd.STRING); - + submodelService.updateSubmodelElement(idShortPathPropertyInSmeCol, newNonExistingProperty); } + @Test + public void updateFileSMEWithNonFileSME() throws FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; + + submodelService.setFileValue(idShortPathPropertyInSmeCol, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + String fileValue = ((File) submodelService.getSubmodelElement(idShortPathPropertyInSmeCol)).getValue(); + + assertTrue(fileExistsInStorage(fileValue)); + + Property newProperty = SubmodelServiceHelper.createDummyProperty(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "4005", DataTypeDefXsd.INT); + + submodelService.updateSubmodelElement(idShortPathPropertyInSmeCol, newProperty); + + Property updatedProperty = (Property) submodelService.getSubmodelElement(idShortPathPropertyInSmeCol); + + assertEquals(newProperty, updatedProperty); + assertFalse(fileExistsInStorage(fileValue)); + } + + @Test + public void updateFileSMEWithFileSME() throws FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; + + submodelService.setFileValue(idShortPathPropertyInSmeCol, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + assertStoredFileContentEquals(submodelService, idShortPathPropertyInSmeCol, DUMMY_JSON_1); + + File newFileSME = SubmodelServiceHelper.createDummyFile(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "text/plain", "someArbitraryPlainText"); + submodelService.updateSubmodelElement(idShortPathPropertyInSmeCol, newFileSME); + + submodelService.setFileValue(idShortPathPropertyInSmeCol, "jsonFile2.json", getInputStreamOfDummyFile(DUMMY_JSON_2)); + + assertStoredFileContentEquals(submodelService, idShortPathPropertyInSmeCol, DUMMY_JSON_2); + } + @Test public void deleteNestedSubmodelElementInSubmodelElementCollection() { Submodel operationDataSubmodel = DummySubmodelFactory.createOperationalDataSubmodelWithHierarchicalSubmodelElements(); @@ -421,8 +466,7 @@ public void deleteNestedSubmodelElementInSubmodelElementList() { public void getPaginatedSubmodelElement() { Submodel technicalData = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService submodelService = getSubmodelService(technicalData); - CursorResult> cursorResult = submodelService - .getSubmodelElements(new PaginationInfo(1, "")); + CursorResult> cursorResult = submodelService.getSubmodelElements(new PaginationInfo(1, "")); assertEquals(1, cursorResult.getResult().size()); } @@ -430,8 +474,7 @@ public void getPaginatedSubmodelElement() { public void paginationCursor() { Submodel technicalData = DummySubmodelFactory.createTechnicalDataSubmodel(); SubmodelService submodelService = getSubmodelService(technicalData); - CursorResult> cursorResult = submodelService.getSubmodelElements(new PaginationInfo(1, - SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT)); + CursorResult> cursorResult = submodelService.getSubmodelElements(new PaginationInfo(1, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT)); assertEquals(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_BLOB_ID_SHORT, cursorResult.getCursor()); } @@ -440,13 +483,13 @@ public void paginationCursor() { public void invokeOperation() { Submodel invokableSubmodel = DummySubmodelFactory.createSubmodelWithAllSubmodelElements(); SubmodelService submodelService = getSubmodelService(invokableSubmodel); - + Property val = new DefaultProperty.Builder().idShort("in").value("2").build(); - + OperationVariable[] result = submodelService.invokeOperation(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_OPERATION_ID, new OperationVariable[] { SubmodelServiceHelper.createOperationVariable(val) }); Property ret = (Property) result[0].getValue(); - + assertEquals("4", ret.getValue()); } @@ -459,6 +502,101 @@ public void invokeNonOperation() { submodelService.invokeOperation(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_ANNOTATED_RELATIONSHIP_ELEMENT_ID_SHORT, new OperationVariable[0]); } + @Test + public void deleteFileSubmodelElementDeletesFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + submodelService.setFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + SubmodelElement submodelElement = submodelService.getSubmodelElement(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + String fileValue = ((File) submodelElement).getValue(); + + assertTrue(fileExistsInStorage(fileValue)); + + submodelService.deleteSubmodelElement(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + assertFalse(fileExistsInStorage(fileValue)); + } + + @Test + public void getFile() throws FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + String expectedFileExtension = "json"; + + submodelService.setFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + java.io.File retrievedValue = submodelService.getFileByPath(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + assertEquals(expectedFileExtension, getExtension(retrievedValue.getName())); + assertStoredFileContentEquals(submodelService, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, DUMMY_JSON_1); + } + + @Test(expected = FileDoesNotExistException.class) + public void getNonExistingFile() { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + deleteFileIfExisted(submodelService, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + submodelService.getFileByPath(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } + + @Test + public void deleteFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + submodelService.setFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + submodelService.deleteFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + try { + submodelService.getFileByPath(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + fail(); + } catch (FileDoesNotExistException expected) { + } + } + + @Test + public void updateFile() throws ElementDoesNotExistException, ElementNotAFileException, FileNotFoundException, IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + submodelService.setFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "jsonFile1.json", getInputStreamOfDummyFile(DUMMY_JSON_1)); + + assertStoredFileContentEquals(submodelService, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, DUMMY_JSON_1); + + submodelService.setFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, "jsonFile2.json", getInputStreamOfDummyFile(DUMMY_JSON_2)); + + assertStoredFileContentEquals(submodelService, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT, DUMMY_JSON_2); + } + + @Test(expected = ElementNotAFileException.class) + public void getFileFromNonFileSME() { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + + submodelService.getFileByPath(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_MULTI_LANG_PROP_ID_SHORT); + } + + @Test(expected = FileDoesNotExistException.class) + public void deleteNonExistingFile() throws IOException { + Submodel technicalDataSubmodel = DummySubmodelFactory.createTechnicalDataSubmodel(); + SubmodelService submodelService = getSubmodelService(technicalDataSubmodel); + deleteFileIfExisted(submodelService, SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + + submodelService.deleteFileValue(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT); + } + + private void assertStoredFileContentEquals(SubmodelService submodelService, String fileIdShort, String content) throws IOException { + java.io.File retrievedValue = submodelService.getFileByPath(fileIdShort); + + String actual = new String(FileUtils.openInputStream(retrievedValue).readAllBytes()); + + assertEquals(content, actual); + } + private List createHierarchicalSubmodelElement() { List submodelElementsCollection = new ArrayList<>(); @@ -493,28 +631,37 @@ private String generateNestedIdShortPath() { } private DefaultSubmodelElementList createDummySubmodelElementList(String idShort) { - return new DefaultSubmodelElementList.Builder().idShort(idShort) - .build(); + return new DefaultSubmodelElementList.Builder().idShort(idShort).build(); } private SubmodelElementCollection createDummySubmodelElementCollection(String idShort) { - return new DefaultSubmodelElementCollection.Builder().idShort(idShort) - .build(); + return new DefaultSubmodelElementCollection.Builder().idShort(idShort).build(); } private DefaultEntity createDummyEntityWithStatement(SubmodelElement submodelElement, String idShort) { - return new DefaultEntity.Builder().idShort(idShort) - .category("cat1") - .statements(submodelElement) - .build(); + return new DefaultEntity.Builder().idShort(idShort).category("cat1").statements(submodelElement).build(); } private DefaultProperty createDummyProperty(String idShort) { - return new DefaultProperty.Builder().idShort(idShort) - .category("cat1") - .value("123") - .valueType(DataTypeDefXsd.INTEGER) - .build(); + return new DefaultProperty.Builder().idShort(idShort).category("cat1").value("123").valueType(DataTypeDefXsd.INTEGER).build(); + } + + private InputStream getInputStreamOfDummyFile(String fileContent) throws FileNotFoundException, IOException { + return new ByteArrayInputStream(fileContent.getBytes()); + } + + private String getExtension(String filename) { + return FilenameUtils.getExtension(filename); } + + private void deleteFileIfExisted(SubmodelService service, String idShort) { + try { + service.getFileByPath(idShort); + service.deleteFileValue(idShort); + } catch (FileDoesNotExistException e) { + return; + } + + } } diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApi.java b/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApi.java index 8e12e20eb..93c6b1dd2 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApi.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApi.java @@ -37,6 +37,7 @@ import org.eclipse.digitaltwin.basyx.pagination.GetSubmodelElementsResult; 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; @@ -44,6 +45,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; @@ -322,4 +325,55 @@ ResponseEntity putSubmodelElementByPath(@Parameter(in = ParameterIn.PATH, ResponseEntity patchSubmodelValueOnly(@Parameter(in = ParameterIn.DEFAULT, description = "Submodel object in its ValueOnly representation", required = false, schema = @Schema()) @Valid @RequestBody List body, @Parameter(in = ParameterIn.QUERY, description = "Determines the structural depth of the respective resource content", schema = @Schema(allowableValues = { "deep" }, defaultValue = "deep")) @Valid @RequestParam(value = "level", required = false, defaultValue = "deep") String level); + + @Operation(summary = "Deletes file content of an existing submodel element at a specified path within submodel elements hierarchy", description = "", tags = { "Submodel 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 = "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 = "/submodel/submodel-elements/{idShortPath}/attachment", produces = { "application/json" }, method = RequestMethod.DELETE) + ResponseEntity deleteFileByPath(@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 = { "Submodel 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 = "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 = "405", description = "Method not allowed - Download only valid for File submodel element", 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 = "/submodel/submodel-elements/{idShortPath}/attachment", produces = { "application/octet-stream", "application/json" }, method = RequestMethod.GET) + ResponseEntity getFileByPath(@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 = { "Submodel 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 = "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 = "405", description = "Method not allowed - Upload only valid for File submodel element", 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 = "/submodel/submodel-elements/{idShortPath}/attachment", produces = { "application/json" }, consumes = { "multipart/form-data" }, method = RequestMethod.PUT) + ResponseEntity putFileByPath(@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); } diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApiController.java b/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApiController.java index 67b340fc9..2d309dfe9 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApiController.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/http/SubmodelServiceHTTPApiController.java @@ -25,6 +25,8 @@ package org.eclipse.digitaltwin.basyx.submodelservice.http; +import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; import java.util.List; @@ -34,6 +36,9 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationResult; +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.http.pagination.Base64UrlEncodedCursor; @@ -44,12 +49,15 @@ 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.FileSystemResource; +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.RequestParam; 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; @@ -248,4 +256,55 @@ private String getEncodedCursorFromCursorResult(CursorResult cursorResult) { return Base64UrlEncodedCursor.encodeCursor(cursorResult.getCursor()); } + @Override + public ResponseEntity getFileByPath(String idShortPath) { + Resource resource = new FileSystemResource(service.getFileByPath(idShortPath)); + + return new ResponseEntity<>(resource, HttpStatus.OK); + } + + @Override + public ResponseEntity putFileByPath(String idShortPath, String fileName, @Valid MultipartFile file) { + InputStream fileInputstream = null; + try { + fileInputstream = file.getInputStream(); + service.setFileValue(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(String idShortPath) { + try { + service.deleteFileValue(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 void closeInputStream(InputStream fileInputstream) { + if (fileInputstream == null) { + return; + } + + try { + fileInputstream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/DummySubmodelServiceComponent.java b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/DummySubmodelServiceComponent.java index 5d31f9c80..5bccb1449 100644 --- a/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/DummySubmodelServiceComponent.java +++ b/basyx.submodelservice/basyx.submodelservice-http/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/http/DummySubmodelServiceComponent.java @@ -25,6 +25,7 @@ package org.eclipse.digitaltwin.basyx.submodelservice.http; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; @@ -36,7 +37,7 @@ public class DummySubmodelServiceComponent { @Bean public SubmodelService getSubmodelService() { - return new InMemorySubmodelServiceFactory() + return new InMemorySubmodelServiceFactory(new InMemoryFileRepository()) .create(DummySubmodelFactory.createSubmodelWithAllSubmodelElements()); } } \ No newline at end of file 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 16750e70d..23bcd0ec9 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 @@ -25,20 +25,34 @@ package org.eclipse.digitaltwin.basyx.submodelservice.http; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.io.FileNotFoundException; import java.io.IOException; - +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +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.pagination.Base64UrlEncodedCursor; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpStatus; +import org.springframework.util.ResourceUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -503,7 +517,125 @@ public void invokeOperation() throws FileNotFoundException, IOException, ParseEx BaSyxHttpTestUtils.assertSameJSONContent(expectedValue, BaSyxHttpTestUtils.getResponseAsString(response)); } + + @Test + public void updateFileSMEWithNonFileSME() throws FileNotFoundException, IOException, ParseException { + String element = getJSONValueAsString("PropertySubmodelElementUpdateWithNewIdShort.json"); + + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; + + uploadFileToSubmodelElement(idShortPathPropertyInSmeCol); + + CloseableHttpResponse fileResponse = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(idShortPathPropertyInSmeCol)); + assertEquals(HttpStatus.OK.value(), fileResponse.getCode()); + + CloseableHttpResponse updatedResponse = updateElement(createSpecificSubmodelElementURL(idShortPathPropertyInSmeCol), element); + assertEquals(HttpStatus.NO_CONTENT.value(), updatedResponse.getCode()); + + CloseableHttpResponse fetchedResponse = BaSyxHttpTestUtils.executeGetOnURL(createSpecificSubmodelElementURL(idShortPathPropertyInSmeCol)); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedResponse)); + } + + @Test + public void updateFileSMEWithFileSME() throws FileNotFoundException, IOException, ParseException { + String element = getJSONValueAsString("FileSubmodelElementUpdateWithNewIdShort.json"); + + String idShortPathPropertyInSmeCol = SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_SUBMODEL_ELEMENT_COLLECTION_ID_SHORT + "." + SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_FILE_ID_SHORT; + + uploadFileToSubmodelElement(idShortPathPropertyInSmeCol); + + CloseableHttpResponse fileResponse = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(idShortPathPropertyInSmeCol)); + assertEquals(HttpStatus.OK.value(), fileResponse.getCode()); + + CloseableHttpResponse updatedResponse = updateElement(createSpecificSubmodelElementURL(idShortPathPropertyInSmeCol), element); + assertEquals(HttpStatus.NO_CONTENT.value(), updatedResponse.getCode()); + + CloseableHttpResponse fetchedResponse = BaSyxHttpTestUtils.executeGetOnURL(createSpecificSubmodelElementURL(idShortPathPropertyInSmeCol)); + BaSyxHttpTestUtils.assertSameJSONContent(element, BaSyxHttpTestUtils.getResponseAsString(fetchedResponse)); + } + + + @Test + public void uploadFileToFileSubmodelElement() throws IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + assertEquals(HttpStatus.OK.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void uploadFileToNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse submodelElementFileUploadResponse = uploadFileToSubmodelElement("ElementNotExist"); + + assertEquals(HttpStatus.NOT_FOUND.value(), submodelElementFileUploadResponse.getCode()); + } + + @Test + public void deleteFile() throws FileNotFoundException, IOException { + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileDeleteURL(DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(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_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT)); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void deleteFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeDeleteOnURL(createSMEFileGetURL("ElementNotExist")); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + + @Test + public void getFile() throws FileNotFoundException, IOException, ParseException { + String fileName = DummySubmodelFactory.FILE_NAME; + + byte[] expectedFile = readBytesFromClasspath(fileName); + + uploadFileToSubmodelElement(DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT); + + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(DummySubmodelFactory.SUBMODEL_ELEMENT_FILE_ID_SHORT)); + assertEquals(HttpStatus.OK.value(), response.getCode()); + + byte[] actualFile = EntityUtils.toByteArray(response.getEntity()); + + response.close(); + + assertArrayEquals(expectedFile, actualFile); + } + + @Test + public void getFileFromNonFileSubmodelElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL(SubmodelServiceHelper.SUBMODEL_TECHNICAL_DATA_PROPERTY_ID_SHORT)); + + assertEquals(HttpStatus.PRECONDITION_FAILED.value(), response.getCode()); + } + + @Test + public void getFileFromNotExistElement() throws FileNotFoundException, UnsupportedEncodingException, ClientProtocolException, IOException { + CloseableHttpResponse response = BaSyxHttpTestUtils.executeGetOnURL(createSMEFileGetURL("ElementNotExist")); + + assertEquals(HttpStatus.NOT_FOUND.value(), response.getCode()); + } + private CloseableHttpResponse updateElement(String url, String element) throws IOException { return BaSyxHttpTestUtils.executePutOnURL(url, element); } @@ -591,4 +723,57 @@ private String getJSONValueAsString(String fileName) throws FileNotFoundExceptio return BaSyxHttpTestUtils.readJSONStringFromClasspath(fileName); } + private CloseableHttpResponse uploadFileToSubmodelElement(String submodelElementIdShort) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + String fileName = DummySubmodelFactory.FILE_NAME; + + java.io.File file = ResourceUtils.getFile("classpath:" + fileName); + + HttpPut putRequest = createPutRequestWithFile(submodelElementIdShort, fileName, file); + + return executePutRequest(client, putRequest); + } + + private HttpPut createPutRequestWithFile(String submodelElementIdShort, String fileName, java.io.File file) { + HttpPut putRequest = new HttpPut(createSMEFileUploadURL(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 submodelElementIdShort, String fileName) { + return getURL() + "/submodel-elements/" + submodelElementIdShort + "/attachment?fileName=" + fileName; + } + + private String createSMEFileDeleteURL(String submodelElementIdShort) { + return getURL() + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + private String createSMEFileGetURL( String submodelElementIdShort) { + return getURL() + "/submodel-elements/" + submodelElementIdShort + "/attachment"; + } + + + private CloseableHttpResponse executePutRequest(CloseableHttpClient client, HttpPut putRequest) throws IOException { + CloseableHttpResponse response = client.execute(putRequest); + + HttpEntity responseEntity = response.getEntity(); + + EntityUtils.consume(responseEntity); + return response; + } + + private byte[] readBytesFromClasspath(String fileName) throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource(fileName); + InputStream in = classPathResource.getInputStream(); + + return in.readAllBytes(); + } } diff --git a/basyx.submodelservice/basyx.submodelservice-http/src/test/resources/BaSyx-Logo.png b/basyx.submodelservice/basyx.submodelservice-http/src/test/resources/BaSyx-Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..da613e94c07c4fef1b8a454ef927fd2fbf987f66 GIT binary patch literal 11433 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.authorization ${revision} + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-inmemory + ${revision} + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-mongodb + ${revision} + org.eclipse.digitaltwin.basyx @@ -742,6 +757,18 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + ${revision} + tests + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-inmemory + ${revision} + tests + org.eclipse.digitaltwin.basyx @@ -842,6 +869,12 @@ ${revision} tests + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend-mongodb + ${revision} + tests + org.eclipse.digitaltwin.basyx basyx.aasservice-feature-mqtt