diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/AasDiscoveryInMemoryBackendProvider.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/InMemoryAasDiscoveryDocumentBackend.java similarity index 75% rename from basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/AasDiscoveryInMemoryBackendProvider.java rename to basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/InMemoryAasDiscoveryDocumentBackend.java index 21720e822..1ee2e1da1 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/AasDiscoveryInMemoryBackendProvider.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/InMemoryAasDiscoveryDocumentBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,28 +25,23 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.inmemory; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryBackendProvider; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryDocument; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryDocumentBackend; import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; /** + * InMemory backend for the AasDiscoveryDocument based on InMemoryCrudRepository * - * InMemory backend provider for the AAS Discovery - * - * @author zielstor, fried + * @author mateusmolina */ @ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") @Component -public class AasDiscoveryInMemoryBackendProvider implements AasDiscoveryBackendProvider { - - private CrudRepository repository = new InMemoryCrudRepository(AasDiscoveryDocument::getShellIdentifier); +public class InMemoryAasDiscoveryDocumentBackend extends InMemoryCrudRepository implements AasDiscoveryDocumentBackend { - @Override - public CrudRepository getCrudRepository() { - return repository; + public InMemoryAasDiscoveryDocumentBackend() { + super(AasDiscoveryDocument::getShellIdentifier); } -} +} \ No newline at end of file diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/TestInMemoryAasDiscoveryService.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/TestInMemoryAasDiscoveryService.java index ca50d5301..072b14cc3 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/TestInMemoryAasDiscoveryService.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/inmemory/TestInMemoryAasDiscoveryService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,7 +25,7 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.inmemory; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.SimpleAasDiscoveryFactory; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.CrudAasDiscoveryFactory; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceSuite; @@ -39,7 +39,7 @@ public class TestInMemoryAasDiscoveryService extends AasDiscoveryServiceSuite { @Override protected AasDiscoveryService getAasDiscoveryService() { - return new SimpleAasDiscoveryFactory(new AasDiscoveryInMemoryBackendProvider()).create(); + return new CrudAasDiscoveryFactory(new InMemoryAasDiscoveryDocumentBackend()).create(); } } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/pom.xml b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/pom.xml index 4914d78a3..6fce83a2a 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/pom.xml +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/pom.xml @@ -44,5 +44,10 @@ org.eclipse.digitaltwin.basyx basyx.aasdiscoveryservice-backend + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/MongoDBAasDiscoveryDocumentBackendConfiguration.java similarity index 61% rename from basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java rename to basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/MongoDBAasDiscoveryDocumentBackendConfiguration.java index 680df9beb..5f46aa554 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/MongoDBSubmodelRepositoryConfiguration.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/MongoDBAasDiscoveryDocumentBackendConfiguration.java @@ -1,6 +1,6 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * + * Copyright (C) 2025 the Eclipse BaSyx Authors + * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -19,33 +19,37 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * + * * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelrepository; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend; + +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; /** - * Provides a InMemorySubmodelServiceFactory for usage in the MongoDB Submodel - * Repository backend.
- *
- * This is needed to ensure that the SubmodelServiceFeatures are processed - * correctly when utilizing MongoDb * - * @author jungjan + * Provides the MongoDB configuration for the {@link AasDiscoveryDocumentBackend} + * + * @author mateusmolina * */ @Configuration @ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -public class MongoDBSubmodelRepositoryConfiguration { +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend") +public class MongoDBAasDiscoveryDocumentBackendConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.aasdiscoveryservice.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "aasdiscovery-service"; + @Bean - public SubmodelServiceFactory getInMemorySubmodelServiceFactory(FileRepository fileRepository) { - return new InMemorySubmodelServiceFactory(fileRepository); + MappingEntry aasDiscoveryDocumentMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, AasDiscoveryDocument.class); } + } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/AasDiscoveryMongoDBBackendProvider.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/AasDiscoveryMongoDBBackendProvider.java deleted file mode 100644 index dbe06c2a5..000000000 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/AasDiscoveryMongoDBBackendProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb; - -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryBackendProvider; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.AasDiscoveryDocument; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Bean; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; -import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; -import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; - -/** - * MongoDB backend provider for the AAS Discovery - * - * @author zielstor, fried - */ - -@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -@Component -public class AasDiscoveryMongoDBBackendProvider implements AasDiscoveryBackendProvider { - - private BasyxMongoMappingContext mappingContext; - - private MongoTemplate template; - - @Autowired - public AasDiscoveryMongoDBBackendProvider(BasyxMongoMappingContext mappingContext, - @Value("${basyx.aasdiscoveryservice.mongodb.collectionName:aasdiscovery-service}") String collectionName, - MongoTemplate template) { - super(); - this.mappingContext = mappingContext; - this.template = template; - - mappingContext.addEntityMapping(AasDiscoveryDocument.class, collectionName); - } - - @Override - public CrudRepository getCrudRepository() { - @SuppressWarnings("unchecked") - MongoPersistentEntity entity = (MongoPersistentEntity) mappingContext - .getPersistentEntity(AasDiscoveryDocument.class); - return new SimpleMongoRepository<>(new MappingMongoEntityInformation<>(entity), template); - } - - -} diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/DummyDiscoveryServiceConfig.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/DummyDiscoveryServiceConfig.java index 29f24a1c5..7706aba3b 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/DummyDiscoveryServiceConfig.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/DummyDiscoveryServiceConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,15 +25,10 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.SimpleAasDiscoveryFactory; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.MongoTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * Configuration for tests @@ -44,22 +39,11 @@ @Configuration public class DummyDiscoveryServiceConfig { - public final static String COLLECTION = "discoveryServicePersistencyTestCollection"; - public final static String DB = "BaSyxTestDb"; - - @Bean - public AasDiscoveryService createAasDiscoveryService(MongoTemplate template) { - return new SimpleAasDiscoveryFactory( - new AasDiscoveryMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template)).create(); - } + static final String TEST_COLLECTION = "discoveryServiceTestCollection"; @Bean - public MongoTemplate createMongoTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, DB); + AasDiscoveryService aasDiscoveryService(AasDiscoveryServiceFactory factory) { + return factory.create(); } } \ No newline at end of file diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryPersistency.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryPersistency.java index d1ef2d67e..35c13da36 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryPersistency.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryPersistency.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -51,7 +51,7 @@ public static void initComponent() { @Before public void clearTemplate() { MongoTemplate mongoTemplate = applicationContext.getBean(MongoTemplate.class); - MongoDBUtilities.clearCollection(mongoTemplate, DummyDiscoveryServiceConfig.COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, DummyDiscoveryServiceConfig.TEST_COLLECTION); } @Override diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryService.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryService.java index c9737c8c2..9a4008fa0 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryService.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/mongodb/TestMongoDBAasDiscoveryService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,102 +25,40 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb; -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.List; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.SimpleAasDiscoveryFactory; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceFactory; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceSuite; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; -import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; +import org.springframework.test.context.junit4.SpringRunner; /** - * Tests the {@link MongoDBAasDiscoveryService} + * Tests the AasDiscoveryService with MongoDb as backend * - * @author danish + * @author danish, mateusmolina * */ +@SpringBootTest +@RunWith(SpringRunner.class) public class TestMongoDBAasDiscoveryService extends AasDiscoveryServiceSuite { - private static final String CONFIGURED_AAS_DISC_SERV_NAME = "configured-aas-discovery-service-name"; - private final String COLLECTION = "aasDiscoveryServiceTestCollection"; - - @Override - protected AasDiscoveryService getAasDiscoveryService() { - MongoTemplate mongoTemplate = createTemplate(); - MongoDBUtilities.clearCollection(mongoTemplate, COLLECTION); - AasDiscoveryMongoDBBackendProvider aasDiscoveryBackendProvider = new AasDiscoveryMongoDBBackendProvider( - new BasyxMongoMappingContext(), - COLLECTION, mongoTemplate); - AasDiscoveryServiceFactory aasDiscoveryFactory = new SimpleAasDiscoveryFactory(aasDiscoveryBackendProvider); - - return aasDiscoveryFactory.create(); - } - - @Test - public void configuredMongoDBAasDiscoveryServiceName() { - MongoTemplate template = createTemplate(); - - clearDatabase(template); - - AasDiscoveryMongoDBBackendProvider aasDiscoveryBackendProvider = new AasDiscoveryMongoDBBackendProvider( - new BasyxMongoMappingContext(), COLLECTION, template); - AasDiscoveryServiceFactory aasDiscoveryFactory = new SimpleAasDiscoveryFactory(aasDiscoveryBackendProvider, - CONFIGURED_AAS_DISC_SERV_NAME); - AasDiscoveryService service = aasDiscoveryFactory.create(); - - assertEquals(CONFIGURED_AAS_DISC_SERV_NAME, service.getName()); - } - - @Test - public void assetLinkIsPersisted() { - AasDiscoveryService aasDiscoveryService = getAasDiscoveryService(); - String dummyShellIdentifier = "DummyShellID"; + @Autowired + MongoTemplate mongoTemplate; - List expectedAssetIDs = createDummyAssetLinkOnDiscoveryService(dummyShellIdentifier, aasDiscoveryService); + @Autowired + AasDiscoveryService aasDiscoveryService; - List actualAssetIDs = aasDiscoveryService.getAllAssetLinksById(dummyShellIdentifier); - - assertEquals(expectedAssetIDs, actualAssetIDs); - - removeCreatedAssetLink(dummyShellIdentifier, aasDiscoveryService); - } - - private List createDummyAssetLinkOnDiscoveryService(String testShellIdentifier, AasDiscoveryService aasDiscoveryService) { - AssetAdministrationShell aas = getSingleDummyShell(testShellIdentifier); - createAssetLink(aas, aasDiscoveryService); - - SpecificAssetId specificAssetId_1 = createDummySpecificAssetId("TestAsset1", "TestAssetValue1"); - SpecificAssetId specificAssetId_2 = createDummySpecificAssetId("TestAsset2", "TestAssetValue2"); - - return Arrays.asList(specificAssetId_1, specificAssetId_2); - } - - private void removeCreatedAssetLink(String dummyShellIdentifier, AasDiscoveryService aasDiscoveryService) { - aasDiscoveryService.deleteAllAssetLinksById(dummyShellIdentifier); + @Before + public void cleanup() { + MongoDBUtilities.clearCollection(mongoTemplate, DummyDiscoveryServiceConfig.TEST_COLLECTION); } - private MongoTemplate createTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, "BaSyxTestDb"); - } - - private void clearDatabase(MongoTemplate template) { - template.remove(new Query(), COLLECTION); + @Override + protected AasDiscoveryService getAasDiscoveryService() { + return aasDiscoveryService; } } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/resources/application.properties b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..4d5e2ae85 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,9 @@ +basyx.backend = MongoDB +basyx.aasdiscoveryservice.mongodb.collectionName= discoveryServiceTestCollection + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryBackendProvider.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryDocumentBackend.java similarity index 80% rename from basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryBackendProvider.java rename to basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryDocumentBackend.java index f6ae8e3b7..5e892fa2a 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryBackendProvider.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/AasDiscoveryDocumentBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,21 +22,18 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; /** - * Backend provider for the AAS Discovery + * CrudRepository for the AasDiscoveryDocument * - * @author zielstor, fried + * @author mateusmolina */ -public interface AasDiscoveryBackendProvider { - - /** - * Get the CRUD repository for the AAS Discovery - * - * @return The CRUD repository - */ - public CrudRepository getCrudRepository(); +@Repository +public interface AasDiscoveryDocumentBackend extends CrudRepository { + } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscovery.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscovery.java index 60e1fd1e8..5f997d704 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscovery.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscovery.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,7 +24,7 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend; -import static org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryUtils.deriveAssetLinksFromSpecificAssetIds; +import static org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryUtils.*; import java.util.ArrayList; import java.util.HashSet; @@ -44,43 +44,23 @@ 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.springframework.beans.factory.annotation.Value; import org.springframework.data.repository.CrudRepository; /** * Default Implementation for the {@link AasDiscoveryService} based on Spring * {@link CrudRepository} * - * @author zielstor, fried + * @author zielstor, fried, mateusmolina * */ public class CrudAasDiscovery implements AasDiscoveryService { - private AasDiscoveryBackendProvider provider; - private String aasDiscoveryServiceName; + private final AasDiscoveryDocumentBackend backend; + private final String aasDiscoveryServiceName; - /** - * Constructor - * - * @param provider - * The backend provider - */ - public CrudAasDiscovery(AasDiscoveryBackendProvider provider) { - this.provider = provider; - } - - /** - * Constructor - * - * @param provider - * The backend provider - * @param aasDiscoveryName - * The name of the AAS discovery service - */ - public CrudAasDiscovery(AasDiscoveryBackendProvider provider, - @Value("${basyx.aasdiscovery.name:aas-discovery}") String aasDiscoveryName) { - this(provider); - this.aasDiscoveryServiceName = aasDiscoveryName; + public CrudAasDiscovery(AasDiscoveryDocumentBackend backend, String aasDiscoveryServiceName) { + this.backend = backend; + this.aasDiscoveryServiceName = aasDiscoveryServiceName; } /** @@ -140,7 +120,7 @@ public List createAllAssetLinksById(String shellIdentifier, List shellAssetLinks = deriveAssetLinksFromSpecificAssetIds(specificAssetIds); AasDiscoveryDocument aasDiscoveryDocument = new AasDiscoveryDocument(shellIdentifier, new HashSet<>(shellAssetLinks), specificAssetIds); - provider.getCrudRepository().save(aasDiscoveryDocument); + backend.save(aasDiscoveryDocument); } return specificAssetIds; @@ -159,7 +139,7 @@ public void deleteAllAssetLinksById(String shellIdentifier) { synchronized (assetLinks) { throwIfAssetLinkDoesNotExist(assetLinks, shellIdentifier); - provider.getCrudRepository().deleteById(shellIdentifier); + backend.deleteById(shellIdentifier); } } @@ -185,7 +165,7 @@ private void throwIfSpecificAssetIdLinkDoesNotExist(Map> getAssetIds() { - Iterable aasDiscoveryDocuments = provider.getCrudRepository().findAll(); + Iterable aasDiscoveryDocuments = backend.findAll(); List aasDiscoveryDocumentList = StreamSupport .stream(aasDiscoveryDocuments.spliterator(), false).collect(Collectors.toList()); Map> assetIds = aasDiscoveryDocumentList.stream().collect( @@ -194,7 +174,7 @@ private Map> getAssetIds() { } private Map> getAssetLinks() { - Iterable aasDiscoveryDocuments = provider.getCrudRepository().findAll(); + Iterable aasDiscoveryDocuments = backend.findAll(); List aasDiscoveryDocumentList = StreamSupport .stream(aasDiscoveryDocuments.spliterator(), false).collect(Collectors.toList()); Map> assetLinks = aasDiscoveryDocumentList.stream() diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/SimpleAasDiscoveryFactory.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryFactory.java similarity index 68% rename from basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/SimpleAasDiscoveryFactory.java rename to basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryFactory.java index 6f552bbba..f903497c4 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/SimpleAasDiscoveryFactory.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -35,46 +35,29 @@ * Simple AAS Discovery factory that creates a {@link CrudAasDiscovery} with a * backend provider and a service factory * - * @author zielstor, fried + * @author zielstor, fried, mateusmolina * */ @Component @ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") -public class SimpleAasDiscoveryFactory implements AasDiscoveryServiceFactory { +public class CrudAasDiscoveryFactory implements AasDiscoveryServiceFactory { - private AasDiscoveryBackendProvider aasBackendProvider; + private final AasDiscoveryDocumentBackend aasDiscoveryDocumentBackend; + private final String aasDiscoveryName; - private String aasDiscoveryName = null; - - /** - * Constructor - * - * @param aasBackendProvider - * The backend provider - */ - @Autowired(required = false) - public SimpleAasDiscoveryFactory(AasDiscoveryBackendProvider aasBackendProvider) { - this.aasBackendProvider = aasBackendProvider; + @Autowired + public CrudAasDiscoveryFactory(AasDiscoveryDocumentBackend aasDiscoveryDocumentBackend, @Value("${basyx.aasdiscoveryservice.name:aas-discovery-service}") String aasDiscoveryName) { + this.aasDiscoveryDocumentBackend = aasDiscoveryDocumentBackend; + this.aasDiscoveryName = aasDiscoveryName; } - /** - * Constructor - * - * @param aasBackendProvider - * The backend provider - * @param aasDiscoveryName - * The name of the AAS discovery service - */ - @Autowired(required = false) - public SimpleAasDiscoveryFactory(AasDiscoveryBackendProvider aasBackendProvider, - @Value("${basyx.aasdiscoveryservice.name:aas-discovery-service}") String aasDiscoveryName) { - this(aasBackendProvider); - this.aasDiscoveryName = aasDiscoveryName; + public CrudAasDiscoveryFactory(AasDiscoveryDocumentBackend aasBackendProvider) { + this(aasBackendProvider, "aas-discovery-service"); } @Override public AasDiscoveryService create() { - return new CrudAasDiscovery(aasBackendProvider, aasDiscoveryName); + return new CrudAasDiscovery(aasDiscoveryDocumentBackend, aasDiscoveryName); } } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryTest.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryTest.java index c799181a8..a2ade914c 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryTest.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/CrudAasDiscoveryTest.java @@ -1,12 +1,6 @@ -package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend; -import static org.junit.Assert.assertEquals; - -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; -import org.junit.Test; - /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * + * Copyright (C) 2025 the Eclipse BaSyx Authors + * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -14,10 +8,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -25,16 +19,22 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * + * * SPDX-License-Identifier: MIT ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend; +import static org.junit.Assert.assertEquals; + +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.junit.Test; + public class CrudAasDiscoveryTest { private static final String CONFIGURED_AAS_DISCOVERY_NAME = "test-aas-discovery"; @Test public void getConfiguredAasDiscoveryName(){ - AasDiscoveryService service = new SimpleAasDiscoveryFactory(null, CONFIGURED_AAS_DISCOVERY_NAME).create(); + AasDiscoveryService service = new CrudAasDiscoveryFactory(null, CONFIGURED_AAS_DISCOVERY_NAME).create(); assertEquals(CONFIGURED_AAS_DISCOVERY_NAME,service.getName()); } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/feature/authorization/MockAasDiscoveryServiceFactory.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/feature/authorization/MockAasDiscoveryServiceFactory.java index 26920499a..541ab5a2a 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/feature/authorization/MockAasDiscoveryServiceFactory.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/feature/authorization/MockAasDiscoveryServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,8 +25,8 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.feature.authorization; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.SimpleAasDiscoveryFactory; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.inmemory.AasDiscoveryInMemoryBackendProvider; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.CrudAasDiscoveryFactory; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.inmemory.InMemoryAasDiscoveryDocumentBackend; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryServiceFactory; @@ -41,7 +41,7 @@ public class MockAasDiscoveryServiceFactory implements AasDiscoveryServiceFactor private final AasDiscoveryService aasDiscoveryService; public MockAasDiscoveryServiceFactory() { - this.aasDiscoveryService = new SimpleAasDiscoveryFactory(new AasDiscoveryInMemoryBackendProvider()).create(); + this.aasDiscoveryService = new CrudAasDiscoveryFactory(new InMemoryAasDiscoveryDocumentBackend()).create(); } @Override diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/pom.xml b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/pom.xml index 75fc025e3..9d5c1b83e 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/pom.xml +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/pom.xml @@ -35,7 +35,7 @@ org.eclipse.digitaltwin.basyx - basyx.aasdiscoveryservice-backend-mongodb + basyx.aasdiscoveryservice-backend-inmemory test diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/TestAasDiscoveryServiceHTTP.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/TestAasDiscoveryServiceHTTP.java index cd392381b..228da51d3 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/TestAasDiscoveryServiceHTTP.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/TestAasDiscoveryServiceHTTP.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -33,7 +33,7 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingAssetLinkException; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.springframework.boot.SpringApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; /** @@ -47,8 +47,8 @@ public class TestAasDiscoveryServiceHTTP extends AasDiscoveryServiceHTTPSuite { private static ConfigurableApplicationContext appContext; @BeforeClass - public static void startConceptDescriptionRepo() throws Exception { - appContext = new SpringApplication(DummyAasDiscoveryServiceComponent.class).run(new String[] {}); + public static void startConceptDescriptionRepo() { + appContext = new SpringApplicationBuilder(DummyAasDiscoveryServiceComponent.class).profiles("httptests").run(); } @Override diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/testconfig/DummyDiscoveryServiceConfig.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/testconfig/DummyDiscoveryServiceConfig.java index a769670a3..6ca481c4a 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/testconfig/DummyDiscoveryServiceConfig.java +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-http/src/test/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/http/testconfig/DummyDiscoveryServiceConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,17 +25,12 @@ package org.eclipse.digitaltwin.basyx.aasdiscoveryservice.http.testconfig; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.SimpleAasDiscoveryFactory; -import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.mongodb.AasDiscoveryMongoDBBackendProvider; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.CrudAasDiscoveryFactory; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.backend.inmemory.InMemoryAasDiscoveryDocumentBackend; import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.MongoTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; +import org.springframework.context.annotation.Profile; /** * Configuration for tests @@ -44,22 +39,12 @@ * */ @Configuration +@Profile("httptests") public class DummyDiscoveryServiceConfig { - private final String COLLECTION = "discoveryServiceHTTPTestCollection"; - @Bean - @ConditionalOnMissingBean - public AasDiscoveryService createAasDiscoveryService() { - return new SimpleAasDiscoveryFactory(new AasDiscoveryMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, createTemplate())).create(); - } - - private MongoTemplate createTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, "BaSyxTestDb"); + AasDiscoveryService aasDiscoveryService() { + return new CrudAasDiscoveryFactory(new InMemoryAasDiscoveryDocumentBackend()).create(); } } \ No newline at end of file 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 18fde13dd..2e8211329 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 @@ -25,33 +25,32 @@ package org.eclipse.digitaltwin.basyx.aasenvironment; -import java.io.File; -import java.io.IOException; -import java.util.List; - import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepository; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.InMemoryAasRepositoryBackend; +import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.ConceptDescriptionInMemoryBackendProvider; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.CrudConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.CrudConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.InMemoryConceptDescriptionBackend; 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; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.core.io.DefaultResourceLoader; +import java.io.File; +import java.io.IOException; +import java.util.List; + /** * Tests the behavior of {@link AasEnvironment} loader functionality @@ -75,9 +74,9 @@ public class AasEnvironmentLoaderTest { @Before public void setUp() { - submodelRepository = Mockito.spy(new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()))); - aasRepository = Mockito.spy(new CrudAasRepository(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo")); - conceptDescriptionRepository = Mockito.spy(new CrudConceptDescriptionRepository(new ConceptDescriptionInMemoryBackendProvider())); + submodelRepository = Mockito.spy(CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).create()); + aasRepository = Mockito.spy(CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository(new InMemoryFileRepository()).create()); + conceptDescriptionRepository = Mockito.spy(CrudConceptDescriptionRepositoryFactory.builder().backend(new InMemoryConceptDescriptionBackend()).create()); } protected void loadRepositories(List pathsToLoad) throws IOException, DeserializationException, InvalidFormatException { 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 6ada57a42..d8dd9e30f 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 @@ -25,16 +25,6 @@ package org.eclipse.digitaltwin.basyx.aasenvironment; -import static org.junit.Assert.*; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXDeserializer; @@ -43,11 +33,7 @@ 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.xml.XmlDeserializer; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetKind; -import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultConceptDescription; @@ -55,22 +41,31 @@ import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.InMemoryAasRepositoryBackend; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.ConceptDescriptionInMemoryBackendProvider; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.SimpleConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.CrudConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.InMemoryConceptDescriptionBackend; 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; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceHelper; import org.junit.Before; import org.junit.Test; import org.xml.sax.SAXException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + public class TestAASEnvironmentSerialization { public static final String AAS_TECHNICAL_DATA_ID = "shell001"; @@ -86,9 +81,9 @@ public class TestAASEnvironmentSerialization { @Before public void setup() { - submodelRepository = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); - aasRepository = new CrudAasRepositoryFactory(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo").create(); - conceptDescriptionRepository = new SimpleConceptDescriptionRepositoryFactory(new ConceptDescriptionInMemoryBackendProvider(), createDummyConceptDescriptions(), "cdRepo").create(); + submodelRepository = CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).create(); + aasRepository = CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository(new InMemoryFileRepository()).create(); + conceptDescriptionRepository = CrudConceptDescriptionRepositoryFactory.builder().backend(new InMemoryConceptDescriptionBackend()).remoteCollection(createDummyConceptDescriptions()).create(); for (Submodel submodel : createDummySubmodels()) { submodelRepository.createSubmodel(submodel); diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackend.java b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackend.java deleted file mode 100644 index b7003eb6f..000000000 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackend.java +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2025 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory; - -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.function.Consumer; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.AasRepositoryBackend; -import org.eclipse.digitaltwin.basyx.aasservice.AasService; -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; -import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; -import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; -import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; -import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; - -/** - * InMemory implementation of the {@link AasRepositoryBackend} based on the - * {@link AasService} - * - * @author mateusmolina - */ -public class InMemoryAasRepositoryBackend extends InMemoryCrudRepository implements AasRepositoryBackend { - - private final AasServiceFactory aasServiceFactory; - - public InMemoryAasRepositoryBackend(AasServiceFactory aasServiceFactory) { - super(aas -> aas.getId()); - this.aasServiceFactory = aasServiceFactory; - } - - public static InMemoryAasRepositoryBackend buildDefault() { - return new InMemoryAasRepositoryBackend(new InMemoryAasServiceFactory(new InMemoryFileRepository())); - } - - @Override - public CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) { - return getAasService(aasId).getSubmodelReferences(pInfo); - } - - @Override - public void addSubmodelReference(String aasId, Reference submodelReference) { - doWithService(aasId, s -> s.addSubmodelReference(submodelReference)); - } - - @Override - public void removeSubmodelReference(String aasId, String submodelId) { - doWithService(aasId, s -> s.removeSubmodelReference(submodelId)); - } - - @Override - public void setAssetInformation(String aasId, AssetInformation aasInfo) { - doWithService(aasId, s -> s.setAssetInformation(aasInfo)); - } - - @Override - public AssetInformation getAssetInformation(String aasId) { - return getAasService(aasId).getAssetInformation(); - } - - @Override - public File getThumbnail(String aasId) { - return getAasService(aasId).getThumbnail(); - } - - @Override - public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { - doWithService(aasId, s -> s.setThumbnail(fileName, contentType, inputStream)); - } - - @Override - public void deleteThumbnail(String aasId) { - doWithService(aasId, s -> s.deleteThumbnail()); - } - - private AasService getAasService(String aasId) { - return findById(aasId).map(aasServiceFactory::create).orElseThrow(() -> new ElementDoesNotExistException(aasId)); - } - - private void doWithService(String aasId, Consumer consumer) { - AasService aasService = getAasService(aasId); - consumer.accept(aasService); - save(aasService.getAAS()); - } -} diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java index 31a00e478..edb73fcc8 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/TestInMemoryAasRepository.java @@ -40,14 +40,14 @@ import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.DummyAssetAdministrationShellFactory; -import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.junit.Test; /** - * Tests the {@link InMemoryAasRepository} name + * Tests the {@link CrudAasRepository} with {@link InMemoryAasBackend} name * * @author schnicke, kammognie, mateusmolina * @@ -60,7 +60,7 @@ public class TestInMemoryAasRepository extends AasRepositorySuite { @Override protected AasRepository getAasRepository() { fileRepository = new InMemoryFileRepository(); - return new CrudAasRepositoryFactory(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo").create(); + return CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository(fileRepository).create(); } @Override @@ -84,10 +84,8 @@ protected AasService getAasServiceWithThumbnail() throws IOException { @Test public void getConfiguredInMemoryAasRepositoryName() { - fileRepository = new InMemoryFileRepository(); - InMemoryAasRepositoryBackend backend = new InMemoryAasRepositoryBackend(new InMemoryAasServiceFactory(fileRepository)); - AasRepository repo = new CrudAasRepository(backend, CONFIGURED_AAS_REPO_NAME); - + AasRepository repo = CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository(new InMemoryFileRepository()).repositoryName(CONFIGURED_AAS_REPO_NAME).create(); + assertEquals(CONFIGURED_AAS_REPO_NAME, repo.getName()); } } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasRepositoryBackendConfiguration.java b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasBackendConfiguration.java similarity index 60% rename from basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasRepositoryBackendConfiguration.java rename to basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasBackendConfiguration.java index 2f6791971..00fc702d7 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasRepositoryBackendConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/MongoDBAasBackendConfiguration.java @@ -27,19 +27,12 @@ import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceOperations; -import org.eclipse.digitaltwin.basyx.aasservice.backend.MongoDBAasServiceOperations; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; -import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation; /** * @@ -51,19 +44,14 @@ @Configuration @ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") @EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.aasrepository.backend") -public class MongoDBAasRepositoryBackendConfiguration { - @Bean - MappingMongoEntityInformation mappingMongoEntityInformation(BasyxMongoMappingContext mappingContext, @Value("${basyx.aasrepository.mongodb.collectionName:aas-repo}") String collectionName) { - mappingContext.addEntityMapping(AssetAdministrationShell.class, collectionName); +public class MongoDBAasBackendConfiguration { - @SuppressWarnings("unchecked") - MongoPersistentEntity entity = (MongoPersistentEntity) mappingContext.getPersistentEntity(AssetAdministrationShell.class); - return new MappingMongoEntityInformation<>(entity); - } + static final String COLLECTION_NAME_FIELD = "basyx.aasrepository.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "aas-repo"; @Bean - @DependsOn("mappingMongoEntityInformation") - AasServiceOperations aasServiceOperations(MongoOperations template, FileRepository fileRepository) { - return new MongoDBAasServiceOperations(template, fileRepository); + MappingEntry aasMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, AssetAdministrationShell.class); } + } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/DummyAasRepositoryConfig.java b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/DummyAasRepositoryConfig.java index c32402964..d54fbaeb2 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/DummyAasRepositoryConfig.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/DummyAasRepositoryConfig.java @@ -27,13 +27,8 @@ import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.MongoTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * Configuration for tests @@ -43,14 +38,11 @@ */ @Configuration public class DummyAasRepositoryConfig { - public static final String COLLECTION = "aasRepositoryPersistencyTestCollection"; - public static final String DB = "BaSyxTestDb"; - @Autowired - private AasRepositoryFactory aasRepositoryFactory; + static final String TEST_COLLECTION_NAME = "testAasCollection"; @Bean - AasRepository createAasRepository() { + AasRepository createAasRepository(AasRepositoryFactory aasRepositoryFactory) { return aasRepositoryFactory.create(); } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepository.java index 9ad1f79d8..f54593cd2 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepository.java @@ -25,32 +25,25 @@ package org.eclipse.digitaltwin.basyx.aasrepository.backend.mongodb; -import static org.junit.Assert.*; - import java.io.IOException; -import java.util.Arrays; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.Resource; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositorySuite; -import org.eclipse.digitaltwin.basyx.aasrepository.DummyAasFactory; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.DummyAssetAdministrationShellFactory; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.junit.After; -import org.junit.Test; +import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; /** @@ -61,12 +54,9 @@ * @author schnicke, danish, kammognie, mateusmolina, despen * */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @SpringBootTest @RunWith(SpringRunner.class) public class TestMongoDBAasRepository extends AasRepositorySuite { - private static final String COLLECTION = "testAasCollection"; - private static final String CONFIGURED_AAS_REPO_NAME = "configured-aas-repo-name"; @Autowired private FileRepository fileRepository; @@ -82,9 +72,9 @@ protected AasRepository getAasRepository() { return aasRepositoryFactory.create(); } - @After + @Before public void cleanup() { - MongoDBUtilities.clearCollection(mongoTemplate, COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, DummyAasRepositoryConfig.TEST_COLLECTION_NAME); } @Override @@ -108,43 +98,4 @@ protected AasService getAasServiceWithThumbnail() throws IOException { return aasServiceWithThumbnail; } - @Test - public void aasIsPersisted() { - AasRepository aasRepository = getAasRepository(); - - AssetAdministrationShell expectedShell = createDummyShellOnRepo(aasRepository); - AssetAdministrationShell retrievedShell = aasRepository.getAas(expectedShell.getId()); - - assertEquals(expectedShell, retrievedShell); - } - - @Test - public void updatedAasIsPersisted() { - AasRepository aasRepository = getAasRepository(); - - AssetAdministrationShell expectedShell = createDummyShellOnRepo(aasRepository); - addSubmodelReferenceToAas(expectedShell); - - aasRepository.updateAas(expectedShell.getId(), expectedShell); - - AssetAdministrationShell retrievedShell = aasRepository.getAas(expectedShell.getId()); - - assertEquals(expectedShell, retrievedShell); - } - - @Test - public void getConfiguredMongoDBAasRepositoryName() { - // todo - } - - private void addSubmodelReferenceToAas(AssetAdministrationShell expectedShell) { - expectedShell.setSubmodels(Arrays.asList(DummyAasFactory.createDummyReference("dummySubmodel"))); - } - - private AssetAdministrationShell createDummyShellOnRepo(AasRepository aasRepository) { - AssetAdministrationShell expectedShell = new DefaultAssetAdministrationShell.Builder().id("dummy").build(); - - aasRepository.createAas(expectedShell); - return expectedShell; - } } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepositoryPersistency.java b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepositoryPersistency.java index 9e98dd4a0..5b87eb5b4 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepositoryPersistency.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/mongodb/TestMongoDBAasRepositoryPersistency.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -50,7 +50,7 @@ public static void initComponent() { @Before public void clearTemplate() { MongoTemplate mongoTemplate = applicationContext.getBean(MongoTemplate.class); - MongoDBUtilities.clearCollection(mongoTemplate, DummyAasRepositoryConfig.COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, DummyAasRepositoryConfig.TEST_COLLECTION_NAME); } @Override diff --git a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml index 5936dd14a..d2c2f4bdd 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml @@ -27,5 +27,9 @@ commons-io commons-io + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryFragmentConfiguration.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryFragmentConfiguration.java deleted file mode 100644 index 67277cd2a..000000000 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryFragmentConfiguration.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2025 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasrepository.backend; - -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceOperations; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; -import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; -import org.springframework.data.repository.core.support.RepositoryFragment; -import org.springframework.lang.NonNull; - -/** - * Configures the Spring Data repository fragments for the - * {@link AasRepositoryBackend} - * - * Requires a bean with the name "aasServiceOperations". It is composed with the - * {@link AasRepositoryBackend} as a {@link RepositoryFragment} - * - * @author mateusmolina - */ -@Configuration -@ConditionalOnBean(name="aasServiceOperations") -public class AasRepositoryFragmentConfiguration implements BeanPostProcessor { - - private final AasServiceOperations aasServiceOperations; - - public AasRepositoryFragmentConfiguration(@Qualifier("aasServiceOperations") AasServiceOperations aasServiceOperations) { - this.aasServiceOperations = aasServiceOperations; - } - - @Override - public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException { - if (!(bean instanceof RepositoryFactoryBeanSupport beanfactory)) - return bean; - - if (!beanfactory.getObjectType().equals(AasRepositoryBackend.class)) - return bean; - - RepositoryFragment fragment = RepositoryFragment.implemented(AasServiceOperations.class, aasServiceOperations); - RepositoryFragments fragments = RepositoryFragments.of(fragment); - beanfactory.setRepositoryFragments(fragments); - - return bean; - } - -} \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java index 8e03ee767..11ed994ac 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepository.java @@ -24,17 +24,13 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasrepository.backend; -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.aasservice.AasService; +import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.AasBackend; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; @@ -42,9 +38,15 @@ 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.springframework.beans.factory.annotation.Value; import org.springframework.data.repository.CrudRepository; +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + /** * Default Implementation for the {@link AasRepository} based on Spring * {@link CrudRepository} @@ -54,12 +56,14 @@ */ public class CrudAasRepository implements AasRepository { - private final AasRepositoryBackend aasBackend; + private final AasBackend aasBackend; + private final AasServiceFactory aasServiceFactory; private final String aasRepositoryName; - public CrudAasRepository(AasRepositoryBackend aasBackend, @Value("${basyx.aasrepo.name:aas-repo}") String aasRepositoryName) { + public CrudAasRepository(AasBackend aasBackend, AasServiceFactory aasServiceFactory, String aasRepositoryName) { this.aasBackend = aasBackend; + this.aasServiceFactory = aasServiceFactory; this.aasRepositoryName = aasRepositoryName; } @@ -108,27 +112,27 @@ public void updateAas(String aasId, AssetAdministrationShell aas) { @Override public CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) { - return aasBackend.getSubmodelReferences(aasId, pInfo); + return getService(aasId).getSubmodelReferences(pInfo); } @Override public void addSubmodelReference(String aasId, Reference submodelReference) { - aasBackend.addSubmodelReference(aasId, submodelReference); + getService(aasId).addSubmodelReference(submodelReference); } @Override public void removeSubmodelReference(String aasId, String submodelId) { - aasBackend.removeSubmodelReference(aasId, submodelId); + getService(aasId).removeSubmodelReference(submodelId); } @Override public void setAssetInformation(String aasId, AssetInformation aasInfo) throws ElementDoesNotExistException { - aasBackend.setAssetInformation(aasId, aasInfo); + getService(aasId).setAssetInformation(aasInfo); } @Override public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException { - return aasBackend.getAssetInformation(aasId); + return getService(aasId).getAssetInformation(); } @Override @@ -138,17 +142,17 @@ public String getName() { @Override public File getThumbnail(String aasId) { - return aasBackend.getThumbnail(aasId); + return getService(aasId).getThumbnail(); } @Override public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { - aasBackend.setThumbnail(aasId, fileName, contentType, inputStream); + getService(aasId).setThumbnail(fileName, contentType, inputStream); } @Override public void deleteThumbnail(String aasId) { - aasBackend.deleteThumbnail(aasId); + getService(aasId).deleteThumbnail(); } private void throwIfMismatchingIds(String aasId, AssetAdministrationShell newAas) { @@ -173,4 +177,9 @@ private void throwIfAasDoesNotExist(String aasId) { if (!aasBackend.existsById(aasId)) throw new ElementDoesNotExistException(aasId); } + + private AasService getService(String aasId) { + return aasServiceFactory.create(aasId); + } + } diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryFactory.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryFactory.java index cb92e9850..227f5a107 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryFactory.java +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryFactory.java @@ -27,13 +27,18 @@ import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; +import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.AasBackend; +import org.eclipse.digitaltwin.basyx.aasservice.backend.CrudAasServiceFactory; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import java.util.Optional; + /** - * Simple AAS repository factory that provides the {@link CrudAasRepository} - * component + * Factory component for the {@link CrudAasRepository} * * @author mateusmolina * @@ -42,15 +47,62 @@ @ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") public class CrudAasRepositoryFactory implements AasRepositoryFactory { - private final CrudAasRepository crudAasRepository; + static final String DEFAULT_AAS_REPO_NAME = "aas-repo"; + + private final AasBackend aasRepositoryBackend; + private final AasServiceFactory aasServiceFactory; + private final String aasRepositoryName; - public CrudAasRepositoryFactory(AasRepositoryBackend aasRepositoryBackend, @Value("${basyx.aasrepo.name:aas-repo}") String aasRepositoryName) { - this.crudAasRepository = new CrudAasRepository(aasRepositoryBackend, aasRepositoryName); + public CrudAasRepositoryFactory(AasBackend aasRepositoryBackend, AasServiceFactory aasServiceFactory, @Value("${basyx.aasrepo.name:"+DEFAULT_AAS_REPO_NAME+"}") String aasRepositoryName) { + this.aasRepositoryBackend = aasRepositoryBackend; + this.aasServiceFactory = aasServiceFactory; + this.aasRepositoryName = aasRepositoryName; } @Override public AasRepository create() { - return crudAasRepository; + return new CrudAasRepository(aasRepositoryBackend, aasServiceFactory, aasRepositoryName); + } + + /** + * Creates a new {@link Builder} for internal use + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private AasBackend aasRepositoryBackend; + private FileRepository fileRepository; + private Optional aasRepositoryName = Optional.empty(); + + public Builder backend(AasBackend aasRepositoryBackend) { + this.aasRepositoryBackend = aasRepositoryBackend; + return this; + } + + public Builder fileRepository(FileRepository fileRepository) { + this.fileRepository = fileRepository; + return this; + } + + public Builder repositoryName(String aasRepositoryName) { + this.aasRepositoryName = Optional.of(aasRepositoryName); + return this; + } + + public CrudAasRepositoryFactory buildFactory() { + assert aasRepositoryBackend != null; + assert fileRepository != null; + + AasServiceFactory aasServiceFactory = new CrudAasServiceFactory(aasRepositoryBackend, fileRepository); + + return new CrudAasRepositoryFactory(aasRepositoryBackend, aasServiceFactory, aasRepositoryName.orElse(DEFAULT_AAS_REPO_NAME)); + } + + public AasRepository create() { + return buildFactory().create(); + } } } diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryTest.java b/basyx.aasrepository/basyx.aasrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryTest.java index 207b62b42..b1d6a7ec6 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryTest.java +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/CrudAasRepositoryTest.java @@ -40,7 +40,7 @@ public class CrudAasRepositoryTest { @Test public void getConfiguredAasRepositoryName() { - AasRepository repo = new CrudAasRepository(null, CONFIGURED_AAS_REPO_NAME); + AasRepository repo = new CrudAasRepositoryFactory(null, null, CONFIGURED_AAS_REPO_NAME).create(); assertEquals(CONFIGURED_AAS_REPO_NAME, repo.getName()); } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/TestMqttV2AASAggregatorObserver.java b/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/TestMqttV2AASAggregatorObserver.java index 3ae079de3..cba715c69 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/TestMqttV2AASAggregatorObserver.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/mqtt/TestMqttV2AASAggregatorObserver.java @@ -40,10 +40,11 @@ import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasrepository.DummyAasFactory; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.InMemoryAasRepositoryBackend; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; import org.eclipse.digitaltwin.basyx.common.mqttcore.encoding.Base64URLEncoder; import org.eclipse.digitaltwin.basyx.common.mqttcore.encoding.URLEncoder; import org.eclipse.digitaltwin.basyx.common.mqttcore.listener.MqttTestListener; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.paho.client.mqttv3.IMqttClient; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; @@ -170,7 +171,7 @@ private AssetAdministrationShell createAasWithSubmodelReference(String aasId) { } private static AasRepository createMqttAasRepository(MqttClient client) { - AasRepositoryFactory repoFactory = new CrudAasRepositoryFactory(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo"); + AasRepositoryFactory repoFactory = CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository(new InMemoryFileRepository()).buildFactory(); return new MqttAasRepositoryFactory(repoFactory, client, new MqttAasRepositoryTopicFactory(new URLEncoder())).create(); } diff --git a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/AasRepositoryRegistryLinkDescriptorGenerationTest.java b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/AasRepositoryRegistryLinkDescriptorGenerationTest.java index 367a22402..9951a4dad 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/AasRepositoryRegistryLinkDescriptorGenerationTest.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/AasRepositoryRegistryLinkDescriptorGenerationTest.java @@ -25,12 +25,6 @@ package org.eclipse.digitaltwin.basyx.aasrepository.feature.registry.integration; -import static org.junit.Assert.*; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; @@ -39,7 +33,8 @@ import org.eclipse.digitaltwin.basyx.aasregistry.main.client.mapper.AttributeMapper; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory.InMemoryAasRepositoryBackend; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; import org.junit.Before; import org.junit.Test; @@ -47,6 +42,12 @@ import org.junit.runners.Parameterized; import org.mockito.Mockito; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + /** * Test suite for {@link RegistryIntegrationAasRepository} feature */ @@ -113,7 +114,7 @@ private AssetAdministrationShell createDummyAas() { } protected AasRepository getAasRepository() { - return new CrudAasRepositoryFactory(InMemoryAasRepositoryBackend.buildDefault(), "aas-repo").create(); + return CrudAasRepositoryFactory.builder().backend(new InMemoryAasBackend()).fileRepository( new InMemoryFileRepository()).create(); } } diff --git a/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/testconfig/DummyAasRepositoryConfig.java b/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/testconfig/DummyAasRepositoryConfig.java index 2f95a7f62..3620d46d7 100644 --- a/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/testconfig/DummyAasRepositoryConfig.java +++ b/basyx.aasrepository/basyx.aasrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/http/testconfig/DummyAasRepositoryConfig.java @@ -26,7 +26,6 @@ package org.eclipse.digitaltwin.basyx.aasrepository.http.testconfig; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.AasRepositoryBackend; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepositoryFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -43,7 +42,7 @@ @Profile("httptests") public class DummyAasRepositoryConfig { @Bean - AasRepository createAasRepository(AasRepositoryBackend backend) { - return new CrudAasRepositoryFactory(backend, "aas-repo").create(); + AasRepository createAasRepository(CrudAasRepositoryFactory factory) { + return factory.create(); } } diff --git a/basyx.aasrepository/basyx.aasrepository.component/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/component/AasRepositoryConfiguration.java b/basyx.aasrepository/basyx.aasrepository.component/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/component/AasRepositoryConfiguration.java index 8f0489d4c..ccaeb1c2e 100644 --- a/basyx.aasrepository/basyx.aasrepository.component/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/component/AasRepositoryConfiguration.java +++ b/basyx.aasrepository/basyx.aasrepository.component/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/component/AasRepositoryConfiguration.java @@ -25,16 +25,20 @@ package org.eclipse.digitaltwin.basyx.aasrepository.component; -import java.util.List; - import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasrepository.feature.AasRepositoryFeature; import org.eclipse.digitaltwin.basyx.aasrepository.feature.DecoratedAasRepositoryFactory; import org.eclipse.digitaltwin.basyx.aasservice.AasService; +import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.feature.AasServiceFeature; +import org.eclipse.digitaltwin.basyx.aasservice.feature.DecoratedAasServiceFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.util.List; /** * Provides the spring bean configuration for the {@link AasRepository} and @@ -50,4 +54,10 @@ public class AasRepositoryConfiguration { public static AasRepository getAasRepository(AasRepositoryFactory aasRepositoryFactory, List features) { return new DecoratedAasRepositoryFactory(aasRepositoryFactory, features).create(); } + + @Primary + @Bean + public AasServiceFactory getAasServiceFactory(AasServiceFactory aasServiceFactory, List features) { + return new DecoratedAasServiceFactory(aasServiceFactory, features); + } } diff --git a/basyx.aasservice/basyx.aasservice-backend-inmemory/pom.xml b/basyx.aasservice/basyx.aasservice-backend-inmemory/pom.xml index 51430de95..466adefc1 100644 --- a/basyx.aasservice/basyx.aasservice-backend-inmemory/pom.xml +++ b/basyx.aasservice/basyx.aasservice-backend-inmemory/pom.xml @@ -33,5 +33,13 @@ org.eclipse.digitaltwin.basyx basyx.filerepository-backend-inmemory + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend + + + org.eclipse.digitaltwin.basyx + basyx.backend.inmemory.core + \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasService.java b/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasBackend.java similarity index 50% rename from basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasService.java rename to basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasBackend.java index 85bf16d68..b065f3133 100644 --- a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasService.java +++ b/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasBackend.java @@ -24,31 +24,22 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasservice.backend; -import java.io.File; -import java.io.InputStream; -import java.util.List; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; -import org.eclipse.digitaltwin.aas4j.v3.model.Key; -import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Resource; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; -import org.eclipse.digitaltwin.basyx.aasservice.AasService; +import org.eclipse.digitaltwin.aas4j.v3.model.*; +import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.MissingKeyTypeException; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepositoryHelper; 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.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Implements the AasService as in-memory variant @@ -56,84 +47,71 @@ * @author schnicke, mateusmolina * */ -public class InMemoryAasService implements AasService { - private AssetAdministrationShell aas; - - private final FileRepository fileRepository; - - /** - * Creates the InMemory AasService containing the passed AAS - * - * @param aas - */ - public InMemoryAasService(AssetAdministrationShell aas, FileRepository fileRepository) { - this.aas = aas; - this.fileRepository = fileRepository; - } +@ConditionalOnExpression("'${basyx.aasservice.backend}'.equals('InMemory') or '${basyx.backend}'.equals('InMemory')") +@Component +public class InMemoryAasBackend extends InMemoryCrudRepository implements AasBackend { - @Override - public AssetAdministrationShell getAAS() { - return aas; + public InMemoryAasBackend() { + super(AssetAdministrationShell::getId); } @Override - public CursorResult> getSubmodelReferences(PaginationInfo pInfo) { - List submodelReferences = aas.getSubmodels(); + public CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) { + List submodelReferences = getAas(aasId).getSubmodels(); Function idResolver = extractSubmodelID(); TreeMap submodelRefMap = convertToTreeMap(submodelReferences, idResolver); PaginationSupport paginationSupport = new PaginationSupport<>(submodelRefMap, idResolver); - CursorResult> paginatedSubmodelReference = paginationSupport.getPaged(pInfo); - return paginatedSubmodelReference; + return paginationSupport.getPaged(pInfo); } @Override - public void addSubmodelReference(Reference submodelReference) { + public void addSubmodelReference(String aasId, Reference submodelReference) { + AssetAdministrationShell aas = getAas(aasId); List submodelsRefs = aas.getSubmodels(); synchronized (submodelsRefs) { - throwExceptionIfReferenceIsAlreadyPresent(submodelReference); + throwExceptionIfReferenceIsAlreadyPresent(aas, submodelReference); submodelsRefs.add(submodelReference); } } @Override - public void removeSubmodelReference(String submodelId) { + public void removeSubmodelReference(String aasId, String submodelId) { + AssetAdministrationShell aas = getAas(aasId); List submodelsRefs = aas.getSubmodels(); synchronized (submodelsRefs) { - submodelsRefs.remove(getSubmodelReferenceById(submodelId)); + submodelsRefs.remove(getSubmodelReferenceById(aas, submodelId)); } } @Override - public void setAssetInformation(AssetInformation aasInfo) { - aas.setAssetInformation(aasInfo); + public void setAssetInformation(String aasId, AssetInformation aasInfo) { + getAas(aasId).setAssetInformation(aasInfo); } @Override - public AssetInformation getAssetInformation() { - return aas.getAssetInformation(); + public AssetInformation getAssetInformation(String aasId) { + return getAas(aasId).getAssetInformation(); } - private Reference getSubmodelReferenceById(String submodelId) { + private static Reference getSubmodelReferenceById(AssetAdministrationShell aas, String submodelId) { List submodelReferences = aas.getSubmodels(); - Reference specificSubmodelReference = submodelReferences.stream().filter(reference -> { + return submodelReferences.stream().filter(reference -> { List keys = reference.getKeys(); Key foundKey = keys.stream().filter(key -> key.getType().equals(KeyTypes.SUBMODEL)).findFirst().get(); return foundKey.getValue().equals(submodelId); }).findFirst().orElseThrow(() -> new ElementDoesNotExistException(submodelId)); - - return specificSubmodelReference; } - private TreeMap convertToTreeMap(List submodelReferences, + private static TreeMap convertToTreeMap(List submodelReferences, Function idResolver) { return submodelReferences.stream().collect(Collectors - .toMap(reference -> idResolver.apply(reference), ref -> ref, (ref1, ref2) -> ref1, TreeMap::new)); + .toMap(idResolver, ref -> ref, (ref1, ref2) -> ref1, TreeMap::new)); } private Function extractSubmodelID() { @@ -148,58 +126,31 @@ private Function extractSubmodelID() { }; } - @Override - public File getThumbnail() { - return FileRepositoryHelper.fetchAndStoreFileLocally(fileRepository, getThumbnailResourcePathOrThrow(getAssetInformation())); - } - - @Override - public void setThumbnail(String fileName, String contentType, InputStream inputStream) { - String filePath = FileRepositoryHelper.saveOrOverwriteFile(fileRepository, fileName, contentType, inputStream); - setAssetInformation(configureAssetInformationThumbnail(getAssetInformation(), contentType, filePath)); - } - - @Override - public void deleteThumbnail() { - AssetInformation assetInformation = getAssetInformation(); - FileRepositoryHelper.removeFileIfExists(fileRepository, getThumbnailResourcePathOrThrow(assetInformation)); - setAssetInformation(configureAssetInformationThumbnail(assetInformation, "", "")); - } - - private String getThumbnailResourcePathOrThrow(AssetInformation assetInformation) { - return Optional.ofNullable(assetInformation).map(AssetInformation::getDefaultThumbnail).map(Resource::getPath).orElseThrow(FileDoesNotExistException::new); - } - - private static AssetInformation configureAssetInformationThumbnail(AssetInformation assetInformation, String contentType, String filePath) { - Resource resource = new DefaultResource(); - resource.setContentType(contentType); - resource.setPath(filePath); - assetInformation.setDefaultThumbnail(resource); - return assetInformation; - } - - private void throwExceptionIfReferenceIsAlreadyPresent(Reference submodelReference) { + private static void throwExceptionIfReferenceIsAlreadyPresent(AssetAdministrationShell aas, Reference submodelReference) { Optional submodelIdKey = getSubmodelTypeKey(submodelReference); if(submodelIdKey.isEmpty()) return; String submodelId = submodelIdKey.get().getValue(); - if (isSubmodelIdAlreadyReferenced(submodelId)) { + if (isSubmodelIdAlreadyReferenced(aas, submodelId)) { throw new CollidingSubmodelReferenceException(submodelId); } } - private boolean isSubmodelIdAlreadyReferenced(String submodelId) { + private static boolean isSubmodelIdAlreadyReferenced(AssetAdministrationShell aas, String submodelId) { return aas.getSubmodels().stream().anyMatch(ref -> ref.getKeys().stream().anyMatch(key -> key.getValue().equals(submodelId))); } private static Optional getSubmodelTypeKey(Reference submodelReference) { - Optional submodelIdKey = submodelReference.getKeys().stream().filter(key -> { + return submodelReference.getKeys().stream().filter(key -> { KeyTypes type = key.getType(); if(type == null) throw new MissingKeyTypeException(); return type.equals(KeyTypes.SUBMODEL); }).findFirst(); - return submodelIdKey; + } + + private AssetAdministrationShell getAas(String aasId) { + return findById(aasId).orElseThrow(() -> new ElementDoesNotExistException(aasId)); } } diff --git a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/TestInMemoryAasService.java b/basyx.aasservice/basyx.aasservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/TestInMemoryAasService.java index e4a5a7bbb..0220e2097 100644 --- a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/TestInMemoryAasService.java +++ b/basyx.aasservice/basyx.aasservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/TestInMemoryAasService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2022 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,17 +26,18 @@ package org.eclipse.digitaltwin.basyx.aasservice; -import java.io.IOException; - import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.Resource; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; -import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.CrudAasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasBackend; import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; +import java.io.IOException; + public class TestInMemoryAasService extends AasServiceSuite { private static FileRepository fileRepository; @@ -44,7 +45,7 @@ public class TestInMemoryAasService extends AasServiceSuite { @Override protected AasService getAasService(AssetAdministrationShell shell) { fileRepository = new InMemoryFileRepository(); - return new InMemoryAasServiceFactory(fileRepository).create(shell); + return new CrudAasServiceFactory(new InMemoryAasBackend(), fileRepository).create(shell); } @Override diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/pom.xml b/basyx.aasservice/basyx.aasservice-backend-mongodb/pom.xml index 3190d1b23..bb6363299 100644 --- a/basyx.aasservice/basyx.aasservice-backend-mongodb/pom.xml +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/pom.xml @@ -18,6 +18,10 @@ org.eclipse.digitaltwin.basyx basyx.aasservice-core + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend + org.eclipse.digitaltwin.basyx basyx.aasservice-core @@ -33,5 +37,10 @@ org.eclipse.digitaltwin.basyx basyx.filerepository-backend-mongodb + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceOperations.java b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasOperations.java similarity index 58% rename from basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceOperations.java rename to basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasOperations.java index 305ce1c0a..0fb81f3af 100644 --- a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceOperations.java +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasOperations.java @@ -25,89 +25,79 @@ package org.eclipse.digitaltwin.basyx.aasservice.backend; -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; -import org.eclipse.digitaltwin.aas4j.v3.model.Key; -import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.Resource; +import com.mongodb.client.result.UpdateResult; +import org.bson.Document; +import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetInformation; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceOperations; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepositoryHelper; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.aggregation.Aggregation; import org.springframework.data.mongodb.core.aggregation.AggregationOperation; import org.springframework.data.mongodb.core.aggregation.AggregationResults; -import org.springframework.data.mongodb.core.aggregation.LimitOperation; -import org.springframework.data.mongodb.core.aggregation.MatchOperation; -import org.springframework.data.mongodb.core.aggregation.ProjectionOperation; -import org.springframework.data.mongodb.core.aggregation.UnwindOperation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.lang.NonNull; -import com.mongodb.client.result.UpdateResult; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** - * MongoDB implementation of the {@link AasServiceOperations} + * MongoDB implementation of the {@link AasOperations} * * @author mateusmolina */ -public class MongoDBAasServiceOperations implements AasServiceOperations { +public class MongoDBAasOperations implements AasOperations { - private static final String SMREF_KEY = "submodels"; - private static final String ASSETINFORMATION_KEY = "assetInformation"; + private static final String KEY_SMREF = "submodels"; + private static final String KEY_ASSETINFORMATION = "assetInformation"; + private static final String KEY_SMREF_KEY_VALUE = KEY_SMREF + ".keys.value"; private final MongoOperations mongoOperations; private final String collectionName; - private final FileRepository fileRepository; - public MongoDBAasServiceOperations(MongoOperations mongoOperations, FileRepository fileRepository) { + public MongoDBAasOperations(MongoOperations mongoOperations) { this.mongoOperations = mongoOperations; - this.fileRepository = fileRepository; collectionName = mongoOperations.getCollectionName(AssetAdministrationShell.class); } @Override public CursorResult> getSubmodelReferences(@NonNull String aasId, @NonNull PaginationInfo pInfo) throws ElementDoesNotExistException { - MatchOperation matchAasId = Aggregation.match(Criteria.where("_id").is(aasId)); - UnwindOperation unwindSubmodels = Aggregation.unwind(SMREF_KEY); - ProjectionOperation projectReference = Aggregation.project().and("submodels.keys").as("keys").and("submodels.type").as("type"); - List ops = new ArrayList<>(); - ops.add(matchAasId); - ops.add(unwindSubmodels); - ops.add(projectReference); - String cursor = pInfo.getCursor(); - if (cursor != null && !cursor.isEmpty()) { - ops.add(Aggregation.match(Criteria.where("keys.value").gt(cursor))); - } - if (pInfo.getLimit() != null && pInfo.getLimit() > 0) { - ops.add(new LimitOperation(pInfo.getLimit())); + ops.add(Aggregation.match(Criteria.where("_id").is(aasId))); + + if (pInfo.getCursor() != null && !pInfo.getCursor().isEmpty()) { + Document addCursorIndex = new Document("$addFields", + new Document("cursorIndex", new Document("$cond", Arrays.asList(new Document("$eq", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + KEY_SMREF_KEY_VALUE, pInfo.getCursor())), -1)), 0, + new Document("$add", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + KEY_SMREF_KEY_VALUE, pInfo.getCursor())), 1)))))); + ops.add(context -> addCursorIndex); + + int limit = (pInfo.getLimit() != null && pInfo.getLimit() > 0) ? pInfo.getLimit() : Integer.MAX_VALUE; + + Document projectSlice = new Document("$project", new Document(KEY_SMREF, new Document("$slice", Arrays.asList("$" + KEY_SMREF, "$cursorIndex", limit)))); + ops.add(context -> projectSlice); + } else { + if (pInfo.getLimit() != null && pInfo.getLimit() > 0) { + Document projectSlice = new Document("$project", new Document(KEY_SMREF, new Document("$slice", Arrays.asList("$" + KEY_SMREF, 0, pInfo.getLimit())))); + ops.add(context -> projectSlice); + } } + ops.add(Aggregation.unwind(KEY_SMREF)); + ops.add(Aggregation.replaceRoot("$" + KEY_SMREF)); + Aggregation aggregation = Aggregation.newAggregation(ops); AggregationResults results = mongoOperations.aggregate(aggregation, collectionName, DefaultReference.class); List refs = results.getMappedResults(); - if (refs.isEmpty() && !existsAas(aasId)) + if (refs.isEmpty() && existsAas(aasId)) throw new ElementDoesNotExistException(aasId); String nextCursor = null; @@ -122,14 +112,14 @@ public CursorResult> getSubmodelReferences(@NonNull String aasId @Override public void addSubmodelReference(@NonNull String aasId, @NonNull Reference submodelReference) throws ElementDoesNotExistException, CollidingSubmodelReferenceException { String newKeyValue = submodelReference.getKeys().get(0).getValue(); - Query query = new Query(new Criteria().andOperator(Criteria.where("_id").is(aasId), Criteria.where(SMREF_KEY).not().elemMatch(Criteria.where("keys.0.value").is(newKeyValue)))); - Update update = new Update().push(SMREF_KEY, submodelReference); + Query query = new Query(new Criteria().andOperator(Criteria.where("_id").is(aasId), Criteria.where(KEY_SMREF).not().elemMatch(Criteria.where("keys.0.value").is(newKeyValue)))); + Update update = new Update().push(KEY_SMREF, submodelReference); UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); if (result.getMatchedCount() != 0) return; - if (!existsAas(aasId)) + if (existsAas(aasId)) throw new ElementDoesNotExistException(aasId); throw new CollidingSubmodelReferenceException(newKeyValue); @@ -138,13 +128,13 @@ public void addSubmodelReference(@NonNull String aasId, @NonNull Reference submo @Override public void removeSubmodelReference(@NonNull String aasId, @NonNull String submodelId) throws ElementDoesNotExistException { Query query = new Query(Criteria.where("_id").is(aasId)); - Update update = new Update().pull(SMREF_KEY, Query.query(Criteria.where("keys.value").is(submodelId)).getQueryObject()); + Update update = new Update().pull(KEY_SMREF, Query.query(Criteria.where("keys.value").is(submodelId)).getQueryObject()); UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); if (result.getModifiedCount() != 0) return; - if (!existsAas(aasId)) + if (existsAas(aasId)) throw new ElementDoesNotExistException(aasId); throw new ElementDoesNotExistException(submodelId); @@ -154,20 +144,20 @@ public void removeSubmodelReference(@NonNull String aasId, @NonNull String submo public void setAssetInformation(@NonNull String aasId, @NonNull AssetInformation aasInfo) { Query query = new Query(Criteria.where("_id").is(aasId)); - Update update = new Update().set(ASSETINFORMATION_KEY, aasInfo); + Update update = new Update().set(KEY_ASSETINFORMATION, aasInfo); UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); // Second check for the case where the update was not performed because the // aasInfo is the // same as the existing one - if (result.getModifiedCount() == 0 && !existsAas(aasId)) + if (result.getModifiedCount() == 0 && existsAas(aasId)) throw new ElementDoesNotExistException(aasId); } @Override public AssetInformation getAssetInformation(@NonNull String aasId) { - Aggregation aggregation = Aggregation.newAggregation(Aggregation.match(Criteria.where("_id").is(aasId)), Aggregation.replaceRoot().withValueOf("$" + ASSETINFORMATION_KEY)); + Aggregation aggregation = Aggregation.newAggregation(Aggregation.match(Criteria.where("_id").is(aasId)), Aggregation.replaceRoot().withValueOf("$" + KEY_ASSETINFORMATION)); AggregationResults results = mongoOperations.aggregate(aggregation, collectionName, DefaultAssetInformation.class // Use concrete type ); @@ -180,37 +170,8 @@ public AssetInformation getAssetInformation(@NonNull String aasId) { return aasInfo; } - @Override - public File getThumbnail(@NonNull String aasId) { - return FileRepositoryHelper.fetchAndStoreFileLocally(fileRepository, getThumbnailResourcePathOrThrow(getAssetInformation(aasId))); - } - - @Override - public void setThumbnail(@NonNull String aasId, @NonNull String fileName, @NonNull String contentType, @NonNull InputStream inputStream) { - String filePath = FileRepositoryHelper.saveOrOverwriteFile(fileRepository, fileName, contentType, inputStream); - setAssetInformation(aasId, configureAssetInformationThumbnail(getAssetInformation(aasId), contentType, filePath)); - } - - @Override - public void deleteThumbnail(@NonNull String aasId) { - AssetInformation assetInformation = getAssetInformation(aasId); - FileRepositoryHelper.removeFileIfExists(fileRepository, getThumbnailResourcePathOrThrow(assetInformation)); - setAssetInformation(aasId, configureAssetInformationThumbnail(assetInformation, "", "")); - } - private boolean existsAas(String aasId) { - return mongoOperations.exists(new Query(Criteria.where("_id").is(aasId)), AssetAdministrationShell.class, collectionName); - } - - private String getThumbnailResourcePathOrThrow(AssetInformation assetInformation) { - return Optional.ofNullable(assetInformation).map(AssetInformation::getDefaultThumbnail).map(Resource::getPath).orElseThrow(FileDoesNotExistException::new); - } - private static AssetInformation configureAssetInformationThumbnail(AssetInformation assetInformation, String contentType, String filePath) { - Resource resource = new DefaultResource(); - resource.setContentType(contentType); - resource.setPath(filePath); - assetInformation.setDefaultThumbnail(resource); - return assetInformation; + return !mongoOperations.exists(new Query(Criteria.where("_id").is(aasId)), AssetAdministrationShell.class, collectionName); } private static String extractSubmodelId(Reference reference) { diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceConfiguration.java b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceConfiguration.java new file mode 100644 index 000000000..416214e35 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/MongoDBAasServiceConfiguration.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * + * Provides the MongoDB configuration for the AasService + * + * @author mateusmolina + * + */ +@Configuration +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.aasservice.backend") +public class MongoDBAasServiceConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.aasservice.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "aas-service"; + + @Bean + @ConditionalOnMissingBean + MappingEntry aasMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, AssetAdministrationShell.class); + } + +} diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/resources/META-INF/spring.factories b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..084784cba --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.eclipse.digitaltwin.basyx.aasservice.backend.AasOperations=org.eclipse.digitaltwin.basyx.aasservice.backend.MongoDBAasOperations diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/DummyTestMongoDbAasServiceComponent.java b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/DummyTestMongoDbAasServiceComponent.java new file mode 100644 index 000000000..9df43ada3 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/DummyTestMongoDbAasServiceComponent.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummyTestMongoDbAasServiceComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyTestMongoDbAasServiceComponent.class, args); + } + +} diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/TestMongoDbAasService.java b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/TestMongoDbAasService.java new file mode 100644 index 000000000..198f29346 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasservice/backend/TestMongoDbAasService.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Resource; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; +import org.eclipse.digitaltwin.basyx.aasservice.AasService; +import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; +import org.eclipse.digitaltwin.basyx.aasservice.AasServiceSuite; +import org.eclipse.digitaltwin.basyx.aasservice.DummyAssetAdministrationShellFactory; +import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +@SpringBootTest +@RunWith(SpringRunner.class) +public class TestMongoDbAasService extends AasServiceSuite { + + static final String TEST_COLLECTION = "aasServiceTestCollection"; + + @Autowired + private FileRepository fileRepository; + + @Autowired + private AasServiceFactory aasServiceFactory; + + @Autowired + private MongoTemplate mongoTemplate; + + @Before + public void clear() { + MongoDBUtilities.clearCollection(mongoTemplate, TEST_COLLECTION); + } + + @Override + protected AasService getAasService(AssetAdministrationShell shell) { + return aasServiceFactory.create(shell); + } + + @Override + protected AasService getAasServiceWithThumbnail() throws IOException { + AssetAdministrationShell expected = DummyAssetAdministrationShellFactory.createForThumbnail(); + AasService aasServiceWithThumbnail = getAasService(expected); + + FileMetadata defaultThumbnail = new FileMetadata("dummyImgA.jpeg", "", createDummyImageIS_A()); + + if (fileRepository.exists(defaultThumbnail.getFileName())) + fileRepository.delete(defaultThumbnail.getFileName()); + + String thumbnailFilePath = fileRepository.save(defaultThumbnail); + + Resource defaultResource = new DefaultResource.Builder().path(thumbnailFilePath).contentType("").build(); + AssetInformation defaultAasAssetInformation = aasServiceWithThumbnail.getAssetInformation(); + defaultAasAssetInformation.setDefaultThumbnail(defaultResource); + + aasServiceWithThumbnail.setAssetInformation(defaultAasAssetInformation); + + return aasServiceWithThumbnail; + } +} diff --git a/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/resources/application.properties b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..c6b2d8c44 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,10 @@ +basyx.backend = MongoDB + +basyx.aasservice.mongodb.collectionName= aasServiceTestCollection + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-backend/pom.xml b/basyx.aasservice/basyx.aasservice-backend/pom.xml new file mode 100644 index 000000000..0ee62985c --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.aasservice + ${revision} + + + basyx.aasservice-backend + BaSyx AAS Service backend + BaSyx AAS Service Backend + + + + org.eclipse.digitaltwin.basyx + basyx.aasservice-core + + + org.eclipse.digitaltwin.basyx + basyx.aasservice-core + tests + test + + + org.springframework.boot + spring-boot-starter + + + org.springframework.data + spring-data-commons + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + + + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryBackend.java b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasBackend.java similarity index 86% rename from basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryBackend.java rename to basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasBackend.java index eaff7b179..39517cb80 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/AasRepositoryBackend.java +++ b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasBackend.java @@ -23,10 +23,9 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.aasrepository.backend; +package org.eclipse.digitaltwin.basyx.aasservice.backend; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceOperations; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @@ -36,6 +35,6 @@ * @author mateusmolina */ @Repository -public interface AasRepositoryBackend extends CrudRepository, AasServiceOperations { +public interface AasBackend extends CrudRepository, AasOperations { } \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasOperations.java b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasOperations.java new file mode 100644 index 000000000..332f53619 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasOperations.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +import java.util.List; + +/** + * This interface provides backend-level operations for managing + * {@link AssetAdministrationShell}s + * + * @author mateusmolina + */ +public interface AasOperations { + + /** + * Retrieves all Submodel References for the given AAS. + * + * @param aasId the identifier of the Asset Administration Shell + * @param pInfo the pagination information + * @return a {@code CursorResult} containing a list of Submodel References + * @throws ElementDoesNotExistException if the Asset Administration Shell with the specified + * {@code aasId} does not exist + */ + CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) throws ElementDoesNotExistException; + + /** + * Adds a Submodel Reference to the specified AAS. + * + * @param aasId the identifier of the Asset Administration Shell + * @param submodelReference the reference to be added + * @throws ElementDoesNotExistException if the Asset Administration Shell with the specified + * {@code aasId} does not exist + * @throws CollidingSubmodelReferenceException if the provided submodel reference conflicts with an existing + * reference + */ + void addSubmodelReference(String aasId, Reference submodelReference) throws ElementDoesNotExistException, CollidingSubmodelReferenceException; + + /** + * Removes a Submodel Reference from the specified AAS. + * + * @param aasId the identifier of the Asset Administration Shell + * @param submodelId the identifier of the submodel to remove + * @throws ElementDoesNotExistException if the Asset Administration Shell with the specified + * {@code aasId} does not exist + */ + void removeSubmodelReference(String aasId, String submodelId) throws ElementDoesNotExistException; + + /** + * Sets the asset information of the specified AAS. + * + * @param aasId the identifier of the Asset Administration Shell + * @param assetInformation the asset information to be set + * @throws ElementDoesNotExistException if the Asset Administration Shell with the specified + * {@code aasId} does not exist + */ + void setAssetInformation(String aasId, AssetInformation assetInformation) throws ElementDoesNotExistException; + + /** + * Retrieves the asset information of the specified AAS. + * + * @param aasId the identifier of the Asset Administration Shell + * @return the asset information of the AAS, or if not found + * @throws ElementDoesNotExistException if the Asset Administration Shell with the specified + * {@code aasId} does not exist + */ + AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException; + +} \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasThumbnailOperations.java b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasThumbnailOperations.java new file mode 100644 index 000000000..b737beda1 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/AasThumbnailOperations.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Resource; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultResource; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepositoryHelper; + +import java.io.File; +import java.io.InputStream; +import java.util.Optional; + +/** + * Collection of methods for handling AAS Thumbnails + * + * @author mateusmolina + */ +public class AasThumbnailOperations { + + private final AasOperations aasOperations; + private final FileRepository fileRepository; + + public AasThumbnailOperations(AasOperations aasOperations, FileRepository fileRepository) { + this.aasOperations = aasOperations; + this.fileRepository = fileRepository; + } + + public File getThumbnail(String aasId) { + return FileRepositoryHelper.fetchAndStoreFileLocally(fileRepository, getThumbnailResourcePathOrThrow(aasOperations.getAssetInformation(aasId))); + } + + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + String filePath = FileRepositoryHelper.saveOrOverwriteFile(fileRepository, fileName, contentType, inputStream); + aasOperations.setAssetInformation(aasId, configureAssetInformationThumbnail(aasOperations.getAssetInformation(aasId), contentType, filePath)); + } + + public void deleteThumbnail(String aasId) { + AssetInformation assetInformation = aasOperations.getAssetInformation(aasId); + FileRepositoryHelper.removeFileIfExists(fileRepository, getThumbnailResourcePathOrThrow(assetInformation)); + aasOperations.setAssetInformation(aasId, configureAssetInformationThumbnail(assetInformation, "", "")); + } + + private static String getThumbnailResourcePathOrThrow(AssetInformation assetInformation) { + return Optional.ofNullable(assetInformation).map(AssetInformation::getDefaultThumbnail).map(Resource::getPath).orElseThrow(FileDoesNotExistException::new); + } + + private static AssetInformation configureAssetInformationThumbnail(AssetInformation assetInformation, String contentType, String filePath) { + Resource resource = new DefaultResource(); + resource.setContentType(contentType); + resource.setPath(filePath); + assetInformation.setDefaultThumbnail(resource); + return assetInformation; + } +} diff --git a/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasService.java b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasService.java new file mode 100644 index 000000000..156f4b351 --- /dev/null +++ b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasService.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.basyx.aasservice.AasService; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +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 java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * Default implementation for the {@link AasService} + * + * @author mateusmolina + */ +public class CrudAasService implements AasService { + + private final AasBackend aasBackend; + private final AasThumbnailOperations thumbnailOperations; + private final String aasId; + + public CrudAasService(AasBackend aasBackend, FileRepository fileRepository, AssetAdministrationShell aas) { + this(aasBackend, fileRepository, aas.getId()); + hostAas(aas); + } + + public CrudAasService(AasBackend aasBackend, FileRepository fileRepository, String aasId) { + this.aasBackend = aasBackend; + this.thumbnailOperations = new AasThumbnailOperations(aasBackend, fileRepository); + this.aasId = aasId; + } + + @Override + public AssetAdministrationShell getAAS() { + return aasBackend.findById(aasId).orElseThrow(() -> new ElementDoesNotExistException(aasId)); + } + + @Override + public CursorResult> getSubmodelReferences(PaginationInfo pInfo) { + return aasBackend.getSubmodelReferences(aasId, pInfo); + } + + @Override + public void addSubmodelReference(Reference submodelReference) { + aasBackend.addSubmodelReference(aasId, submodelReference); + } + + @Override + public void removeSubmodelReference(String submodelId) { + aasBackend.removeSubmodelReference(aasId, submodelId); + } + + @Override + public void setAssetInformation(AssetInformation aasInfo) { + aasBackend.setAssetInformation(aasId, aasInfo); + } + + @Override + public AssetInformation getAssetInformation() { + return aasBackend.getAssetInformation(aasId); + } + + @Override + public File getThumbnail() { + return thumbnailOperations.getThumbnail(aasId); + } + + @Override + public void setThumbnail(String fileName, String contentType, InputStream inputStream) { + thumbnailOperations.setThumbnail(aasId, fileName, contentType, inputStream); + } + + @Override + public void deleteThumbnail() { + thumbnailOperations.deleteThumbnail(aasId); + } + + private void hostAas(AssetAdministrationShell aas){ + aasBackend.save(aas); + } +} diff --git a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasServiceFactory.java b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasServiceFactory.java similarity index 69% rename from basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasServiceFactory.java rename to basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasServiceFactory.java index 8ec5e822a..aeef2681b 100644 --- a/basyx.aasservice/basyx.aasservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/InMemoryAasServiceFactory.java +++ b/basyx.aasservice/basyx.aasservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/backend/CrudAasServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,32 +22,38 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasservice.backend; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** - * AasService factory returning an in-memory backend AasService + * Factory component for the {@link CrudAasService} * - * @author schnicke + * @author mateusmolina */ -@ConditionalOnExpression("'${basyx.aasservice.backend}'.equals('InMemory') or '${basyx.backend}'.equals('InMemory')") @Component -public class InMemoryAasServiceFactory implements AasServiceFactory { +public class CrudAasServiceFactory implements AasServiceFactory { + + private final AasBackend aasBackend; + private final FileRepository fileRepository; - private final FileRepository fileRepository; + public CrudAasServiceFactory(AasBackend aasBackend, FileRepository fileRepository) { + this.aasBackend = aasBackend; + this.fileRepository = fileRepository; + } - public InMemoryAasServiceFactory(FileRepository fileRepository) { - this.fileRepository = fileRepository; - } + @Override + public AasService create(AssetAdministrationShell aas) { + return new CrudAasService(aasBackend, fileRepository, aas); + } - @Override - public AasService create(AssetAdministrationShell aas) { - return new InMemoryAasService(aas, fileRepository); - } + @Override + public AasService create(String aasId) { + return new CrudAasService(aasBackend, fileRepository, aasId); + } } diff --git a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceFactory.java b/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceFactory.java index fc98c4b7f..4b175ba31 100644 --- a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceFactory.java +++ b/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -41,4 +41,11 @@ public interface AasServiceFactory { * @return */ public AasService create(AssetAdministrationShell aas); + + /** + * Creates a new AasService assuming an AAS with ID aasId is already hosted in the storage backend + * + * @param aasId + */ + public AasService create(String aasId); } diff --git a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceOperations.java b/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceOperations.java deleted file mode 100644 index 99026d86e..000000000 --- a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/AasServiceOperations.java +++ /dev/null @@ -1,166 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2025 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.aasservice; - -import java.io.File; -import java.io.InputStream; -import java.util.List; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingSubmodelReferenceException; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; -import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; -import org.springframework.lang.NonNull; - -/** - * This interface provides backend-level operations for managing - * {@link AssetAdministrationShell}s - * - * @author mateusmolina - * - */ -public interface AasServiceOperations { - - /** - * Retrieves all Submodel References for the given AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @param pInfo - * the pagination information; must not be {@code null} - * @return a {@code CursorResult} containing a list of Submodel References - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - CursorResult> getSubmodelReferences(@NonNull String aasId, @NonNull PaginationInfo pInfo) throws ElementDoesNotExistException; - - /** - * Adds a Submodel Reference to the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @param submodelReference - * the reference to be added; must not be {@code null} - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - * @throws CollidingSubmodelReferenceException - * if the provided submodel reference conflicts with an existing - * reference - */ - void addSubmodelReference(@NonNull String aasId, @NonNull Reference submodelReference) throws ElementDoesNotExistException, CollidingSubmodelReferenceException; - - /** - * Removes a Submodel Reference from the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @param submodelId - * the identifier of the submodel to remove; must not be {@code null} - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - void removeSubmodelReference(@NonNull String aasId, @NonNull String submodelId) throws ElementDoesNotExistException; - - /** - * Sets the asset information of the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @param assetInformation - * the asset information to be set; must not be {@code null} - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - void setAssetInformation(@NonNull String aasId, @NonNull AssetInformation assetInformation) throws ElementDoesNotExistException; - - /** - * Retrieves the asset information of the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @return the asset information of the AAS, or {@code null} if not found - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - AssetInformation getAssetInformation(@NonNull String aasId) throws ElementDoesNotExistException; - - /** - * Retrieves the thumbnail for the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @return the file of the thumbnail, or {@code null} if no thumbnail exists - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - File getThumbnail(@NonNull String aasId) throws ElementDoesNotExistException; - - /** - * Sets the thumbnail for the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @param fileName - * the name of the thumbnail file (including extension); must not be - * {@code null} - * @param contentType - * the MIME type of the file; must not be {@code null} - * @param inputStream - * the input stream containing the thumbnail data; must not be - * {@code null} - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - void setThumbnail(@NonNull String aasId, @NonNull String fileName, @NonNull String contentType, @NonNull InputStream inputStream) throws ElementDoesNotExistException; - - /** - * Deletes the thumbnail of the specified AAS. - * - * @param aasId - * the identifier of the Asset Administration Shell; must not be - * {@code null} - * @throws ElementDoesNotExistException - * if the Asset Administration Shell with the specified - * {@code aasId} does not exist - */ - void deleteThumbnail(@NonNull String aasId) throws ElementDoesNotExistException; -} \ No newline at end of file diff --git a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/feature/DecoratedAasServiceFactory.java b/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/feature/DecoratedAasServiceFactory.java index 273d24e5d..bfa4cbc63 100644 --- a/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/feature/DecoratedAasServiceFactory.java +++ b/basyx.aasservice/basyx.aasservice-core/src/main/java/org/eclipse/digitaltwin/basyx/aasservice/feature/DecoratedAasServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,13 +26,13 @@ package org.eclipse.digitaltwin.basyx.aasservice.feature; -import java.util.List; - import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasservice.AasService; import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; import org.eclipse.digitaltwin.basyx.core.DecoratedFactory; +import java.util.List; + /** * Factory utilized for decorating {@link AasServiceFactory} * @@ -56,4 +56,9 @@ public DecoratedAasServiceFactory(AasServiceFactory toDecorate, List basyx.aasservice-core + basyx.aasservice-backend basyx.aasservice-backend-inmemory basyx.aasservice-backend-mongodb basyx.aasservice-feature-mqtt diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/pom.xml b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/pom.xml index f601be551..035d45b68 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/pom.xml +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/pom.xml @@ -21,6 +21,10 @@ org.eclipse.digitaltwin.basyx basyx.backend.inmemory.core + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-inmemory + org.eclipse.digitaltwin.basyx basyx.aasxfileserver-core diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerInMemoryBackendProvider.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerInMemoryBackendProvider.java deleted file mode 100644 index b80b64198..000000000 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerInMemoryBackendProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -/******************************************************************************* -* Copyright (C) 2024 the Eclipse BaSyx Authors -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -* SPDX-License-Identifier: MIT -******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasxfileserver; - -import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.AASXFileServerBackendProvider; -import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; -import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; - - -@ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") -@Component -public class AASXFileServerInMemoryBackendProvider implements AASXFileServerBackendProvider { - - private CrudRepository repository = new InMemoryCrudRepository(Package::getPackageId); - - @Override - public CrudRepository getCrudRepository() { - return repository; - } - -} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/InMemoryPackageBackend.java similarity index 72% rename from basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java rename to basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/InMemoryPackageBackend.java index bf65b85dc..4008fbe9f 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelInMemoryBackendProvider.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/InMemoryPackageBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,28 +23,25 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelrepository; +package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SubmodelBackendProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; /** + * InMemory backend for the {@link Package} based on + * {@link InMemoryCrudRepository} * - * InMemory backend provider for the {@link Submodel} - * - * @author mateusmolina, danish + * @author mateusmolina */ @ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") @Component -public class SubmodelInMemoryBackendProvider implements SubmodelBackendProvider { +public class InMemoryPackageBackend extends InMemoryCrudRepository implements PackageBackend { - @Override - public CrudRepository getCrudRepository() { - return new InMemoryCrudRepository(Submodel::getId); + public InMemoryPackageBackend() { + super(Package::getPackageId); } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestInMemoryAASXFileServer.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestInMemoryAASXFileServer.java index be575c40b..fdc6a28fd 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestInMemoryAASXFileServer.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestInMemoryAASXFileServer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,10 +25,12 @@ package org.eclipse.digitaltwin.basyx.aasxfileserver; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; -import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.SimpleAASXFileServerFactory; +import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.CrudAASXFileServerFactory; +import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.InMemoryPackageBackend; import org.eclipse.digitaltwin.basyx.aasxfileserver.core.AASXFileServerSuite; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.junit.Test; /** @@ -43,12 +45,12 @@ public class TestInMemoryAASXFileServer extends AASXFileServerSuite { @Override protected AASXFileServer getAASXFileServer() { - return new SimpleAASXFileServerFactory(new AASXFileServerInMemoryBackendProvider()).create(); + return new CrudAASXFileServerFactory(new InMemoryPackageBackend(), new InMemoryFileRepository()).create(); } @Test public void getConfiguredInMemoryAASXFileServer() { - AASXFileServer server = new SimpleAASXFileServerFactory(new AASXFileServerInMemoryBackendProvider(),CONFIGURED_AASX_SERVER_NAME).create(); + AASXFileServer server = new CrudAASXFileServerFactory(new InMemoryPackageBackend(), new InMemoryFileRepository(), CONFIGURED_AASX_SERVER_NAME).create(); assertEquals(CONFIGURED_AASX_SERVER_NAME, server.getName()); } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/pom.xml b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/pom.xml index 608c0a9a3..6b3fb5e78 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/pom.xml +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/pom.xml @@ -34,6 +34,10 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend-mongodb + commons-io commons-io @@ -47,5 +51,10 @@ org.eclipse.digitaltwin.basyx basyx.mongodbcore + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBBackendProvider.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBBackendProvider.java deleted file mode 100644 index 407c3e9a3..000000000 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBBackendProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* -* Copyright (C) 2024 the Eclipse BaSyx Authors -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -* SPDX-License-Identifier: MIT -******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasxfileserver; - -import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.AASXFileServerBackendProvider; -import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; -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.repository.CrudRepository; -import org.springframework.stereotype.Component; - -/** - * Implementation of {@link AASXFileServerBackendProvider} for MongoDB Backend - * - * @author zielstor, fried - * - */ -@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -@Component -public class AASXFileServerMongoDBBackendProvider implements AASXFileServerBackendProvider { - - private AASXFileServerMongoDBCrudRepository repository; - - @Autowired - public AASXFileServerMongoDBBackendProvider(MongoTemplate template, @Value("${basyx.aasx-fileserver.mongodb.collectionName:aasx-fileserver}") String collectionName, @Value("${basyx.aasx-fileserver.mongodb.collectionName:aasx-fileserver}") String gridFsBucketName) { - this.repository = new AASXFileServerMongoDBCrudRepository(template,collectionName,gridFsBucketName); - } - - @Override - public CrudRepository getCrudRepository() { - return repository; - } - -} diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBCrudRepository.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBCrudRepository.java deleted file mode 100644 index b47bb4e77..000000000 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/AASXFileServerMongoDBCrudRepository.java +++ /dev/null @@ -1,152 +0,0 @@ -/******************************************************************************* -* Copyright (C) 2024 the Eclipse BaSyx Authors -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -* SPDX-License-Identifier: MIT -******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasxfileserver; -import com.mongodb.client.*; -import com.mongodb.client.gridfs.*; -import com.mongodb.client.gridfs.model.*; -import com.mongodb.client.model.Filters; -import org.bson.Document; -import org.bson.types.ObjectId; -import org.eclipse.digitaltwin.aas4j.v3.model.PackageDescription; -import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; -import org.eclipse.digitaltwin.basyx.aasxfileserver.model.PackagesBody; -import org.springframework.beans.factory.annotation.Autowired; -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.repository.CrudRepository; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -/** - * Implementation of {@link CrudRepository} for MongoDB Backend - * - * @author zielstor, fried - */ -public class AASXFileServerMongoDBCrudRepository implements CrudRepository { - - private final MongoTemplate mongoTemplate; - private final String collectionName; - private final GridFSBucket gridFSBucket; - - public AASXFileServerMongoDBCrudRepository(MongoTemplate mongoTemplate, String collectionName, String gridfsBucketName) { - this.mongoTemplate = mongoTemplate; - this.collectionName = collectionName; - this.gridFSBucket = GridFSBuckets.create(mongoTemplate.getDb(), gridfsBucketName); - } - - @Override - public S save(S entity) { - if (entity.getPackagesBody().getFile() != null) { - GridFSUploadOptions options = new GridFSUploadOptions(); - ObjectId fileId = gridFSBucket.uploadFromStream(entity.getPackagesBody().getFileName(), entity.getPackagesBody().getFile(), options); - entity.getPackagesBody().setFile(null); - entity.getPackagesBody().setPackageId(fileId.toString()); - } - mongoTemplate.save(entity, collectionName); - return entity; - } - - @Override - public Iterable saveAll(Iterable entities) { - List result = new ArrayList<>(); - for (S entity : entities) { - result.add(save(entity)); - } - return result; - } - - @Override - public Optional findById(String id) { - Package pkg = mongoTemplate.findById(id, Package.class, collectionName); - if (pkg != null && pkg.getPackagesBody().getPackageId() != null) { - InputStream file = gridFSBucket.openDownloadStream(new ObjectId(pkg.getPackagesBody().getPackageId())); - pkg.getPackagesBody().setFile(file); - } - return Optional.ofNullable(pkg); - } - - @Override - public boolean existsById(String id) { - Query query = new Query(Criteria.where("packageId").is(id)); - return mongoTemplate.exists(query, Package.class, collectionName); - } - - @Override - public Iterable findAll() { - return mongoTemplate.findAll(Package.class, collectionName); - } - - @Override - public Iterable findAllById(Iterable ids) { - Query query = new Query(Criteria.where("packageId").in(ids)); - return mongoTemplate.find(query, Package.class, collectionName); - } - - @Override - public long count() { - return mongoTemplate.count(new Query(), Package.class, collectionName); - } - - @Override - public void deleteById(String id) { - Package pkg = mongoTemplate.findAndRemove(new Query(Criteria.where("packageId").is(id)), Package.class, collectionName); - if (pkg != null && pkg.getPackagesBody().getPackageId() != null) { - gridFSBucket.delete(new ObjectId(pkg.getPackagesBody().getPackageId())); - } - } - - @Override - public void delete(Package entity) { - deleteById(entity.getPackageId()); - } - - @Override - public void deleteAllById(Iterable ids) { - for (String id : ids) { - deleteById(id); - } - } - - @Override - public void deleteAll(Iterable entities) { - for (Package entity : entities) { - deleteById(entity.getPackageId()); - } - } - - @Override - public void deleteAll() { - mongoTemplate.dropCollection(collectionName); - gridFSBucket.drop(); - } -} diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/MongoDbPackageBackendConfiguration.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/MongoDbPackageBackendConfiguration.java new file mode 100644 index 000000000..2c695fc81 --- /dev/null +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/MongoDbPackageBackendConfiguration.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; + +import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Configuration for the MongoDB {@link PackageBackend} + * + * @author mateusmolina + */ +@Configuration +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.aasxfileserver.backend") +public class MongoDbPackageBackendConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.aasxfileserver.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "aasxfileserver"; + + @Bean + MappingEntry packageMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, Package.class); + } +} diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerComponent.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerComponent.java new file mode 100644 index 000000000..910b1c982 --- /dev/null +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerComponent.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummyAASXFileServerComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyAASXFileServerComponent.class, args); + } +} diff --git a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackendConfiguration.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerConfiguration.java similarity index 68% rename from basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackendConfiguration.java rename to basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerConfiguration.java index 77ab718e7..6b1fb787f 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/inmemory/InMemoryAasRepositoryBackendConfiguration.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/DummyAASXFileServerConfiguration.java @@ -23,26 +23,21 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.aasrepository.backend.inmemory; +package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; -import org.eclipse.digitaltwin.basyx.aasrepository.backend.AasRepositoryBackend; -import org.eclipse.digitaltwin.basyx.aasservice.AasServiceFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; +import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -/** - * - * InMemory backend provider for the AAS - * - * @author mateusmolina, danish - */ -@ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") @Configuration -public class InMemoryAasRepositoryBackendConfiguration { +public class DummyAASXFileServerConfiguration { + + static final String TEST_COLLECTION = "testAasxFileServerCollection"; + + @Bean + AASXFileServer aasxFileServer(AASXFileServerFactory factory) { + return factory.create(); + } - @Bean - AasRepositoryBackend getAasBackend(AasServiceFactory aasServiceFactory) { - return new InMemoryAasRepositoryBackend(aasServiceFactory); - } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestMongoDBAASXFileServer.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/TestMongoDBAASXFileServer.java similarity index 52% rename from basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestMongoDBAASXFileServer.java rename to basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/TestMongoDBAASXFileServer.java index 7c4c5d496..91e1180cc 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/TestMongoDBAASXFileServer.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/TestMongoDBAASXFileServer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,44 +23,50 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.aasxfileserver; +package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.SimpleAASXFileServerFactory; +import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; import org.eclipse.digitaltwin.basyx.aasxfileserver.core.AASXFileServerSuite; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; -import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; - -import static org.junit.Assert.assertEquals; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.gridfs.GridFsCriteria; +import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.test.context.junit4.SpringRunner; /** - * Tests the {@link AASXFileServerMongoDBBackendProvider} + * Tests the {@link AASXFileServer} with MongoDB backend * - * @author zielstor,fried + * @author zielstor,fried, mateusmolina * */ +@SpringBootTest +@RunWith(SpringRunner.class) public class TestMongoDBAASXFileServer extends AASXFileServerSuite { - private static final String CONFIGURED_AASX_SERVER_NAME = "configured-aasx-server-name"; - private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; - private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); - private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); + @Autowired + AASXFileServer aasxFileServer; + + @Autowired + MongoTemplate mongoTemplate; + + @Autowired + GridFsTemplate gridFsTemplate; @Override protected AASXFileServer getAASXFileServer() { - MongoDBUtilities.clearCollection(TEMPLATE, "BaSyxAASXFileServerTest"); - MongoDBUtilities.clearCollection(TEMPLATE, "BaSyxAASXFileServerTestFileBucket.chunks"); - MongoDBUtilities.clearCollection(TEMPLATE, "BaSyxAASXFileServerTestFileBucket.files"); - return new SimpleAASXFileServerFactory(new AASXFileServerMongoDBBackendProvider(TEMPLATE,"BaSyxAASXFileServerTest","BaSyxAASXFileServerTestFileBucket")).create(); + return aasxFileServer; } - @Test - public void getConfiguredInMemoryAASXFileServer() { - AASXFileServer server = new SimpleAASXFileServerFactory(new AASXFileServerMongoDBBackendProvider(TEMPLATE,"BaSyxAASXFileServerTest","BaSyxAASXFileServerTestFileBucket"),CONFIGURED_AASX_SERVER_NAME).create(); - - assertEquals(CONFIGURED_AASX_SERVER_NAME, server.getName()); + @Before + public void cleanUp() { + MongoDBUtilities.clearCollection(mongoTemplate, DummyAASXFileServerConfiguration.TEST_COLLECTION); + Query query = new Query(GridFsCriteria.whereContentType().is(CrudAASXFileServer.AASX_CONTENT_TYPE)); + gridFsTemplate.delete(query); } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/resources/application.properties b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..63ec67273 --- /dev/null +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,10 @@ +basyx.backend = MongoDB + +basyx.aasxfileserver.mongodb.collectionName = testAasxFileServerCollection + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml b/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml index f142caf38..553ee9efc 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml @@ -13,6 +13,10 @@ org.eclipse.digitaltwin.basyx basyx.aasxfileserver-core + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + org.springframework.data spring-data-commons diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServer.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServer.java index ce5211c52..9ff680b70 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServer.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServer.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,13 +22,16 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; import java.io.InputStream; +import java.util.Base64; import java.util.List; import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.UUID; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.eclipse.digitaltwin.aas4j.v3.model.PackageDescription; @@ -37,6 +40,8 @@ import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; import org.eclipse.digitaltwin.basyx.aasxfileserver.model.PackagesBody; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +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; @@ -46,34 +51,39 @@ * Default Implementation for the {@link AASXFileServer} based on Spring * {@link CrudRepository} * - * @author zielstor, fried + * @author zielstor, fried, mateusmolina * */ public class CrudAASXFileServer implements AASXFileServer { - private AASXFileServerBackendProvider aasxFileServerBackendProvider; - private String aasxFileServerName; - private AtomicInteger packageId = new AtomicInteger(0); + static final String AASX_CONTENT_TYPE = "application/asset-administration-shell-package"; + + private final PackageBackend packageBackend; + private final FileRepository fileRepository; + private final String aasxFileServerName; /** * Constructor * * @param aasxFileServerBackendProvider * The backend provider + * @param fileRepository + * The file repository * @param aasxFileServerName * The AASX file server name */ - public CrudAASXFileServer(AASXFileServerBackendProvider aasxFileServerBackendProvider, String aasxFileServerName) { - this.aasxFileServerBackendProvider = aasxFileServerBackendProvider; + public CrudAASXFileServer(PackageBackend packageBackend, FileRepository fileRepository, String aasxFileServerName) { + this.packageBackend = packageBackend; + this.fileRepository = fileRepository; this.aasxFileServerName = aasxFileServerName; } @Override - public CursorResult> getAllAASXPackageIds(String shellId,PaginationInfo pInfo) { - List packageDescriptions = getPackages().stream().map(Package::getPackageDescription).collect(Collectors.toList()); + public CursorResult> getAllAASXPackageIds(String shellId, PaginationInfo pInfo) { + List packageDescriptions = getPackages().map(Package::getPackageDescription).toList(); if (!(shellId == null || shellId.isBlank())) - packageDescriptions = packageDescriptions.stream().filter(packageDesc -> containsShellId(packageDesc, shellId)).collect(Collectors.toList()); + packageDescriptions = packageDescriptions.stream().filter(packageDesc -> containsShellId(packageDesc, shellId)).toList(); TreeMap packageDescriptionMap = packageDescriptions.stream().collect(Collectors.toMap(PackageDescription::getPackageId, submodel -> submodel, (a, b) -> a, TreeMap::new)); @@ -84,36 +94,45 @@ public CursorResult> getAllAASXPackageIds(String shellI @Override public InputStream getAASXByPackageId(String packageId) throws ElementDoesNotExistException { - throwIfAASXPackageIdDoesNotExist(packageId); - - return aasxFileServerBackendProvider.getCrudRepository().findById(packageId).get().getPackagesBody().getFile(); + return packageBackend.findById(packageId).map(this::getISFromPackage).orElseThrow(ElementDoesNotExistException::new); } @Override public void updateAASXByPackageId(String packageId, List shellIds, InputStream file, String filename) throws ElementDoesNotExistException { + deleteAASXByPackageId(packageId); + + PackageDescription packageDescription = createPackageDescription(shellIds, packageId); + + String filepath = fileRepository.save(new FileMetadata(filename, AASX_CONTENT_TYPE, file)); + + PackagesBody packagesBody = createPackagesBody(shellIds, filename, filepath); - throwIfAASXPackageIdDoesNotExist(packageId); + Package pkg = createPackage(packageDescription, packagesBody); - updateAASXPackage(packageId, shellIds, file, filename); + packageBackend.save(pkg); } @Override public PackageDescription createAASXPackage(List shellIds, InputStream file, String fileName) { - String newpackageId = String.valueOf(packageId.incrementAndGet()); + String newpackageId = generateShortUUID(); PackageDescription packageDescription = createPackageDescription(shellIds, newpackageId); - createPackage(shellIds, file, fileName, newpackageId, packageDescription); + String filepath = fileRepository.save(new FileMetadata(fileName, AASX_CONTENT_TYPE, file)); + + PackagesBody packagesBody = createPackagesBody(shellIds, fileName, filepath); + + Package pkg = createPackage(packageDescription, packagesBody); + + packageBackend.save(pkg); return packageDescription; } @Override public void deleteAASXByPackageId(String packageId) throws ElementDoesNotExistException { - throwIfAASXPackageIdDoesNotExist(packageId); - - aasxFileServerBackendProvider.getCrudRepository().deleteById(packageId); + packageBackend.findById(packageId).map(this::fullyDeletePackage).orElseThrow(ElementDoesNotExistException::new); } @Override @@ -121,61 +140,57 @@ public String getName() { return aasxFileServerName == null ? AASXFileServer.super.getName() : aasxFileServerName; } - private PackageDescription createPackageDescription(List shellIds, String newPackageId) { - PackageDescription packageDescription = new DefaultPackageDescription(); - packageDescription.setPackageId(newPackageId); - packageDescription.setItems(shellIds); - - return packageDescription; + private InputStream getISFromPackage(Package pkg) { + return fileRepository.find(pkg.getPackagesBody().getFilePath()); } - private PackagesBody createPackagesBody(List shellIds, InputStream file, String fileName) { - PackagesBody packagesBody = new PackagesBody(); - packagesBody.aasIds(shellIds); - packagesBody.file(file); - packagesBody.fileName(fileName); - - return packagesBody; - } - - private void createPackage(List shellIds, InputStream file, String fileName, String newPackageId, PackageDescription packageDescription) { - PackagesBody packagesBody = createPackagesBody(shellIds, file, fileName); + /** + * + * @param pkg + * @return id of the deleted package + */ + private String fullyDeletePackage(Package pkg) { + final String pkgId = pkg.getPackageId(); - Package aasxPackage = new Package(newPackageId, packageDescription, packagesBody); + fileRepository.delete(pkg.getPackagesBody().getFilePath()); + packageBackend.deleteById(pkgId); - aasxFileServerBackendProvider.getCrudRepository().save(aasxPackage); + return pkgId; } - private void updateAASXPackage(String packageId, List shellIds, InputStream file, String filename) { - Package aasxPackage = aasxFileServerBackendProvider.getCrudRepository().findById(packageId).get(); - - updatePackagesBody(shellIds, file, filename, aasxPackage.getPackagesBody()); - - aasxPackage.getPackageDescription().setItems(shellIds); + private boolean containsShellId(PackageDescription packageDesc, String shellId) { + return packageDesc.getItems().stream().anyMatch(aasId -> aasId.equals(shellId)); + } - aasxFileServerBackendProvider.getCrudRepository().delete(aasxPackage); - aasxFileServerBackendProvider.getCrudRepository().save(aasxPackage); + private Stream getPackages() { + Iterable aasxFileServerPackages = packageBackend.findAll(); + return StreamSupport.stream(aasxFileServerPackages.spliterator(), false); } - private void updatePackagesBody(List shellIds, InputStream file, String filename, PackagesBody packagesBody) { - packagesBody.setAasIds(shellIds); - packagesBody.setFileName(filename); - packagesBody.setFile(file); + private static PackageDescription createPackageDescription(List shellIds, String newPackageId) { + PackageDescription packageDescription = new DefaultPackageDescription(); + packageDescription.setPackageId(newPackageId); + packageDescription.setItems(shellIds); + + return packageDescription; } - private void throwIfAASXPackageIdDoesNotExist(String id) { + private static PackagesBody createPackagesBody(List shellIds, String fileName, String filePath) { + PackagesBody packagesBody = new PackagesBody(); + packagesBody.aasIds(shellIds); + packagesBody.fileName(fileName); + packagesBody.setFilePath(filePath); - if (!aasxFileServerBackendProvider.getCrudRepository().existsById(id)) - throw new ElementDoesNotExistException(id); + return packagesBody; } - private boolean containsShellId(PackageDescription packageDesc, String shellId) { - return packageDesc.getItems().stream().anyMatch(aasId -> aasId.equals(shellId)); + private static Package createPackage(PackageDescription packageDescription, PackagesBody packagesBody) { + return new Package(packageDescription.getPackageId(), packageDescription, packagesBody); } - private List getPackages() { - Iterable aasxFileServerPackages = aasxFileServerBackendProvider.getCrudRepository().findAll(); - return StreamSupport.stream(aasxFileServerPackages.spliterator(), false).collect(Collectors.toList()); + private static String generateShortUUID() { + UUID uuid = UUID.randomUUID(); + return Base64.getUrlEncoder().withoutPadding().encodeToString(uuid.toString().getBytes()); } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/SimpleAASXFileServerFactory.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerFactory.java similarity index 65% rename from basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/SimpleAASXFileServerFactory.java rename to basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerFactory.java index 58d4dd093..0523b4102 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/SimpleAASXFileServerFactory.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,10 +22,12 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServerFactory; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -35,46 +37,33 @@ * Simple AAS Discovery factory that creates a {@link CrudAASXFileServer} with a * backend provider and a service factory * - * @author zielstor, fried + * @author zielstor, fried, mateusmolina * */ @Component @ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") -public class SimpleAASXFileServerFactory implements AASXFileServerFactory { +public class CrudAASXFileServerFactory implements AASXFileServerFactory { - private AASXFileServerBackendProvider aasxFileServerBackendProvider; + static final String DEFAULT_AASX_FILE_SERVER_NAME = "aasx-fileserver"; - private String aasxFileServerName = null; + private final PackageBackend packageBackend; + private final FileRepository fileRepository; + private final String aasxFileServerName; - /** - * Constructor - * - * @param aasxFileServerBackendProvider - * The backend provider - */ - @Autowired(required = false) - public SimpleAASXFileServerFactory(AASXFileServerBackendProvider aasxFileServerBackendProvider) { - this.aasxFileServerBackendProvider = aasxFileServerBackendProvider; + @Autowired + public CrudAASXFileServerFactory(PackageBackend aasxFileServerBackend, FileRepository fileRepository, @Value("${basyx.aasxfileserver.name:" + DEFAULT_AASX_FILE_SERVER_NAME + "}") String aasxFileServerName) { + this.packageBackend = aasxFileServerBackend; + this.fileRepository = fileRepository; + this.aasxFileServerName = aasxFileServerName; } - /** - * Constructor - * - * @param aasxFileServerBackendProvider - * The backend provider - * @param aasRepositoryName - * The AASX file server name - */ - @Autowired(required = false) - public SimpleAASXFileServerFactory(AASXFileServerBackendProvider aasxFileServerBackendProvider, - @Value("${basyx.aasxfileserver.name:aasx-fileserver}") String aasRepositoryName) { - this(aasxFileServerBackendProvider); - this.aasxFileServerName = aasRepositoryName; + public CrudAASXFileServerFactory(PackageBackend aasxFileServerBackend, FileRepository fileRepository) { + this(aasxFileServerBackend, fileRepository, DEFAULT_AASX_FILE_SERVER_NAME); } @Override public AASXFileServer create() { - return new CrudAASXFileServer(aasxFileServerBackendProvider, aasxFileServerName); + return new CrudAASXFileServer(packageBackend, fileRepository, aasxFileServerName); } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/AASXFileServerBackendProvider.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/PackageBackend.java similarity index 82% rename from basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/AASXFileServerBackendProvider.java rename to basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/PackageBackend.java index e75365c3e..9720f263e 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/AASXFileServerBackendProvider.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/PackageBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,21 +22,19 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.aasxfileserver.backend; import org.eclipse.digitaltwin.basyx.aasxfileserver.model.Package; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; /** - * Backend provider for the AAS Discovery + * PackageBackend based on {@link CrudRepository} * - * @author zielstor, fried + * @author mateusmolina */ -public interface AASXFileServerBackendProvider { - /** - * Get the CRUD repository for the AAS Discovery - * - * @return The CRUD repository - */ - public CrudRepository getCrudRepository(); +@Repository +public interface PackageBackend extends CrudRepository { + } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerTest.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerTest.java index b412e9dc5..60f7b727e 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerTest.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/CrudAASXFileServerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (C) 2024 the Eclipse BaSyx Authors +* Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -35,7 +35,7 @@ public class CrudAASXFileServerTest { @Test public void getConfiguredAASXFileServerName() { - AASXFileServer service = new SimpleAASXFileServerFactory(null, CONFIGURED_AASX_FILE_SERVER_NAME).create(); + AASXFileServer service = new CrudAASXFileServerFactory(null, null, CONFIGURED_AASX_FILE_SERVER_NAME).create(); assertEquals(CONFIGURED_AASX_FILE_SERVER_NAME, service.getName()); } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/model/PackagesBody.java b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/model/PackagesBody.java index 8fa8cf463..4ad5edcfe 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/model/PackagesBody.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/model/PackagesBody.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,7 +25,6 @@ package org.eclipse.digitaltwin.basyx.aasxfileserver.model; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -41,8 +40,17 @@ public class PackagesBody { private List aasIds = null; - private InputStream file = null; private String fileName = null; + private String filePath = null; + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + private String packageId = null; public PackagesBody aasIds(List aasIds) { @@ -66,19 +74,6 @@ public void setAasIds(List aasIds) { this.aasIds = aasIds; } - public PackagesBody file(InputStream file) { - this.file = file; - return this; - } - - public InputStream getFile() { - return file; - } - - public void setFile(InputStream file) { - this.file = file; - } - public PackagesBody fileName(String fileName) { this.fileName = fileName; return this; @@ -114,12 +109,12 @@ public boolean equals(java.lang.Object o) { return false; } PackagesBody packagesBody = (PackagesBody) o; - return Objects.equals(this.aasIds, packagesBody.aasIds) && Objects.equals(this.file, packagesBody.file) && Objects.equals(this.fileName, packagesBody.fileName); + return Objects.equals(this.aasIds, packagesBody.aasIds) && Objects.equals(this.fileName, packagesBody.fileName); } @Override public int hashCode() { - return Objects.hash(aasIds, file, fileName); + return Objects.hash(aasIds, fileName); } @Override @@ -128,7 +123,6 @@ public String toString() { sb.append("class PackagesBody {\n"); sb.append(" aasIds: ").append(toIndentedString(aasIds)).append("\n"); - sb.append(" file: ").append(toIndentedString(file)).append("\n"); sb.append(" fileName: ").append(toIndentedString(fileName)).append("\n"); sb.append("}"); return sb.toString(); diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/AASXFileServerSuite.java b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/AASXFileServerSuite.java index 2d0fc77b0..6830a380f 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/AASXFileServerSuite.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/AASXFileServerSuite.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,17 +25,15 @@ package org.eclipse.digitaltwin.basyx.aasxfileserver.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import java.util.List; -import org.apache.commons.io.IOUtils; +import org.apache.commons.io.IOUtils; import org.eclipse.digitaltwin.aas4j.v3.model.PackageDescription; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultPackageDescription; import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; @@ -57,10 +55,10 @@ public abstract class AASXFileServerSuite { public void getAllAASXPackageIds() { AASXFileServer server = getAASXFileServer(); - DummyAASXFileServerFactory.createMultipleDummyAASXPackagesOnServer(server); + List pkgDescs = DummyAASXFileServerFactory.createMultipleDummyAASXPackagesOnServer(server); - PackageDescription expectedDescription1 = DummyAASXFileServerFactory.createDummyPackageDescription("1", DummyAASXFileServerFactory.FIRST_SHELL_IDS); - PackageDescription expectedDescription2 = DummyAASXFileServerFactory.createDummyPackageDescription("2", DummyAASXFileServerFactory.SECOND_SHELL_IDS); + PackageDescription expectedDescription1 = DummyAASXFileServerFactory.createDummyPackageDescription(pkgDescs.get(0).getPackageId(), DummyAASXFileServerFactory.FIRST_SHELL_IDS); + PackageDescription expectedDescription2 = DummyAASXFileServerFactory.createDummyPackageDescription(pkgDescs.get(1).getPackageId(), DummyAASXFileServerFactory.SECOND_SHELL_IDS); List expectedPackageDescriptions = Arrays.asList(expectedDescription1, expectedDescription2); @@ -75,9 +73,9 @@ public void getAllAASXPackageIds() { public void getAllAASXPackageIdsByShellId() { AASXFileServer server = getAASXFileServer(); - DummyAASXFileServerFactory.createMultipleDummyAASXPackagesOnServer(server); + List pkgDescs = DummyAASXFileServerFactory.createMultipleDummyAASXPackagesOnServer(server); - PackageDescription expectedDescription = DummyAASXFileServerFactory.createDummyPackageDescription("2", DummyAASXFileServerFactory.SECOND_SHELL_IDS); + PackageDescription expectedDescription = DummyAASXFileServerFactory.createDummyPackageDescription(pkgDescs.get(1).getPackageId(), DummyAASXFileServerFactory.SECOND_SHELL_IDS); List expectedPackageDescriptions = Arrays.asList(expectedDescription); @@ -92,7 +90,7 @@ public void createAASXPackage() { AASXFileServer server = getAASXFileServer(); PackageDescription actualPackageDescription = DummyAASXFileServerFactory.createFirstDummyAASXPackageOnServer(server); - PackageDescription expectedPackageDescription = DummyAASXFileServerFactory.createDummyPackageDescription("1", DummyAASXFileServerFactory.FIRST_SHELL_IDS); + PackageDescription expectedPackageDescription = DummyAASXFileServerFactory.createDummyPackageDescription(actualPackageDescription.getPackageId(), DummyAASXFileServerFactory.FIRST_SHELL_IDS); assertEquals(expectedPackageDescription, actualPackageDescription); } @@ -124,7 +122,7 @@ public void updateExistingAASXByPackageId() throws IOException { updateAASXPackage(server, initialPackageDescription.getPackageId(), DummyAASXFileServerFactory.SECOND_SHELL_IDS, DummyAASXFileServerFactory.class.getClassLoader().getResourceAsStream("TestAAS2.aasx"), DummyAASXFileServerFactory.SECOND_FILENAME); PackageDescription expectedPackageDescription = new DefaultPackageDescription(); - expectedPackageDescription.setPackageId("1"); + expectedPackageDescription.setPackageId(initialPackageDescription.getPackageId()); expectedPackageDescription.setItems(DummyAASXFileServerFactory.SECOND_SHELL_IDS); CursorResult> pagedPackageDescriptions = server.getAllAASXPackageIds("", PaginationInfo.NO_LIMIT); @@ -193,7 +191,7 @@ private void assertUpdatedAASXPackageId(PackageDescription expectedPackageDescri assertEquals(1, actualPackageDescriptions.size()); assertTrue(actualPackageDescriptions.contains(expectedPackageDescription)); - InputStream actualAASXFile = server.getAASXByPackageId("1"); + InputStream actualAASXFile = server.getAASXByPackageId(expectedPackageDescription.getPackageId()); InputStream expectedAASXFile = DummyAASXFileServerFactory.class.getClassLoader().getResourceAsStream("TestAAS2.aasx"); assertTrue(IOUtils.contentEquals(expectedAASXFile, actualAASXFile)); diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/DummyAASXFileServerFactory.java b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/DummyAASXFileServerFactory.java index 8aa8c98a9..91dfb55e5 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/DummyAASXFileServerFactory.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-core/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/core/DummyAASXFileServerFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -64,7 +64,7 @@ public static PackageDescription createSecondDummyAASXPackageOnServer(AASXFileSe return server.createAASXPackage(SECOND_SHELL_IDS, resourceStream, SECOND_FILENAME); } - public static Collection createMultipleDummyAASXPackagesOnServer(AASXFileServer server) { + public static List createMultipleDummyAASXPackagesOnServer(AASXFileServer server) { PackageDescription firstPackage = createFirstDummyAASXPackageOnServer(server); PackageDescription secondPackage = createSecondDummyAASXPackageOnServer(server); diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-http/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/http/DummyAASXFileServerConfiguration.java b/basyx.aasxfileserver/basyx.aasxfileserver-http/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/http/DummyAASXFileServerConfiguration.java index 6ab4f9034..478b9fa73 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-http/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/http/DummyAASXFileServerConfiguration.java +++ b/basyx.aasxfileserver/basyx.aasxfileserver-http/src/test/java/org/eclipse/digitaltwin/basyx/aasxfileserver/http/DummyAASXFileServerConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -27,21 +27,15 @@ import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServerFactory; -import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServerInMemoryBackendProvider; -import org.eclipse.digitaltwin.basyx.aasxfileserver.backend.CrudAASXFileServer; -import org.eclipse.digitaltwin.basyx.aasxfileserver.feature.AASXFileServerFeature; -import org.eclipse.digitaltwin.basyx.aasxfileserver.feature.DecoratedAASXFileServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import java.util.List; - @Configuration public class DummyAASXFileServerConfiguration { @Bean - public static AASXFileServer getAASXFileServer() { - return new CrudAASXFileServer(new AASXFileServerInMemoryBackendProvider(),""); + AASXFileServer getAASXFileServer(AASXFileServerFactory factory) { + return factory.create(); } } diff --git a/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java b/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java index be4ce3b90..a2882aa95 100644 --- a/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java +++ b/basyx.common/basyx.filerepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/InMemoryFileRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -61,7 +61,7 @@ public String save(FileMetadata fileMetadata) throws FileHandlingException { try (FileOutputStream outStream = new FileOutputStream(targetFile)) { IOUtils.copy(fileMetadata.getFileContent(), outStream); } catch (IOException e) { - throw new FileHandlingException(fileMetadata.getFileName()); + throw new FileHandlingException(fileMetadata.getFileName(), e); } fileMetadata.setFileName(filePath); diff --git a/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java b/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java index d38725bd8..6a8083454 100644 --- a/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java +++ b/basyx.common/basyx.filerepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/core/filerepository/MongoDBFileRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -42,7 +42,7 @@ /** * A MongoDB implementation of the {@link FileRepository} - * + * * @author danish */ @Component @@ -68,13 +68,13 @@ public static GridFsTemplate buildDefaultGridFsTemplate(MongoTemplate mongoTempl @Override public String save(FileMetadata fileMetadata) throws FileHandlingException { - + if (exists(fileMetadata.getFileName())) - throw new FileHandlingException(); - + throw new FileHandlingException("File '%s' already exists.".formatted(fileMetadata.getFileName())); + gridFsTemplate.store(fileMetadata.getFileContent(), fileMetadata.getFileName(), fileMetadata.getContentType()); - - return fileMetadata.getFileName(); + + return fileMetadata.getFileName(); } @Override diff --git a/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/BasyxMongoMappingContext.java b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/BasyxMongoMappingContext.java index 1df188773..f907dd92a 100644 --- a/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/BasyxMongoMappingContext.java +++ b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/BasyxMongoMappingContext.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,38 +24,37 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.common.mongocore; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.util.TypeInformation; import org.springframework.stereotype.Component; +import org.springframework.lang.NonNull; /** * Custom BaSyx Mongo Mapping Context Necessary for configuring MongoDB * collection names for AAS, SM and CD * + * This component retrieves all {@link MappingEntry} defined in the Spring Context, + * * @author mateusmolina, despen */ @Component public class BasyxMongoMappingContext extends MongoMappingContext { - private final Map, String> customCollectionNames = new HashMap<>(); - public BasyxMongoMappingContext() { - super(); - } + private final List entries; - public void addEntityMapping(Class clazz, String collectionName) { - customCollectionNames.put(clazz, collectionName); + public BasyxMongoMappingContext(List entries) { + this.entries = entries; } @Override - protected BasicMongoPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + protected @NonNull BasicMongoPersistentEntity createPersistentEntity(@NonNull TypeInformation typeInformation) { return new BasicMongoPersistentEntity(typeInformation) { @Override - public String getCollection() { - return customCollectionNames.getOrDefault(typeInformation.getType(), super.getCollection()); + public @NonNull String getCollection() { + return entries.stream().filter(e -> e.getEntityClass().equals(typeInformation.getType())).findFirst().map(MappingEntry::getCollectionName).orElseGet(super::getCollection); } }; } diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/exception/SubmodelElementValueDeserializationException.java b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/MappingEntry.java similarity index 65% rename from basyx.submodelservice/basyx.submodelservice-core/deserialization/exception/SubmodelElementValueDeserializationException.java rename to basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/MappingEntry.java index 640a089b9..72a8f58d3 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/exception/SubmodelElementValueDeserializationException.java +++ b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/MappingEntry.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,21 +23,33 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.exception; +package org.eclipse.digitaltwin.basyx.common.mongocore; /** - * Indicates that the provided content for SubmodelELementValue is invalid + * Defines a mapping between a given entity and a collection name + * * - * @author danish - * + * @author mateusmolina */ -public class SubmodelElementValueDeserializationException extends RuntimeException { - - private static final long serialVersionUID = 1L; +public interface MappingEntry { + + String getCollectionName(); + + Class getEntityClass(); + - public SubmodelElementValueDeserializationException() { - super("The provided SubmodelElementValue JSON is not as defined in the Dot AAS Part 2"); - } + public static MappingEntry of(String collectionName, Class entityClass) { + return new MappingEntry() { + @Override + public String getCollectionName() { + return collectionName; + } + @Override + public Class getEntityClass() { + return entityClass; + } + }; + } + } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionInMemoryBackendProvider.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/InMemoryConceptDescriptionBackend.java similarity index 79% rename from basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionInMemoryBackendProvider.java rename to basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/InMemoryConceptDescriptionBackend.java index 3f146a4c2..c551ecbcb 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionInMemoryBackendProvider.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/InMemoryConceptDescriptionBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -28,21 +28,20 @@ import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; /** - * InMemory backend provider for the {@link ConceptDescription} + * InMemory backend for the {@link ConceptDescription} based on + * {@link InMemoryCrudRepository} * - * @author danish + * @author danish, mateusmolina */ @ConditionalOnExpression("'${basyx.backend}'.equals('InMemory')") @Component -public class ConceptDescriptionInMemoryBackendProvider implements ConceptDescriptionBackendProvider { +public class InMemoryConceptDescriptionBackend extends InMemoryCrudRepository implements ConceptDescriptionBackend { - @Override - public CrudRepository getCrudRepository() { - return new InMemoryCrudRepository(ConceptDescription::getId); + public InMemoryConceptDescriptionBackend() { + super(ConceptDescription::getId); } } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestInMemoryConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestInMemoryConceptDescriptionRepository.java index f98d4704e..04123a685 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestInMemoryConceptDescriptionRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestInMemoryConceptDescriptionRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -40,28 +40,28 @@ /** * Tests the {@link InMemoryConceptDescriptionRepository} * - * @author danish, kammognie + * @author danish, kammognie, mateusmolina * */ public class TestInMemoryConceptDescriptionRepository extends ConceptDescriptionRepositorySuite { private static final String CONFIGURED_CD_REPO_NAME = "configured-cd-repo-name"; - private ConceptDescriptionBackendProvider backendProvider = new ConceptDescriptionInMemoryBackendProvider(); + private final ConceptDescriptionBackend backend = new InMemoryConceptDescriptionBackend(); @Override protected ConceptDescriptionRepository getConceptDescriptionRepository() { - return new SimpleConceptDescriptionRepositoryFactory(backendProvider).create(); + return CrudConceptDescriptionRepositoryFactory.builder().backend(backend).create(); } @Override protected ConceptDescriptionRepository getConceptDescriptionRepository(Collection conceptDescriptions) { - return new SimpleConceptDescriptionRepositoryFactory(backendProvider, conceptDescriptions).create(); + return CrudConceptDescriptionRepositoryFactory.builder().backend(backend).remoteCollection(conceptDescriptions).create(); } @Test public void getConfiguredInMemoryConceptDescriptionRepositoryName() { - ConceptDescriptionRepository repo = new CrudConceptDescriptionRepository(backendProvider, CONFIGURED_CD_REPO_NAME); + ConceptDescriptionRepository repo = new CrudConceptDescriptionRepository(backend, CONFIGURED_CD_REPO_NAME); assertEquals(CONFIGURED_CD_REPO_NAME, repo.getName()); } @@ -70,7 +70,7 @@ public void getConfiguredInMemoryConceptDescriptionRepositoryName() { public void idCollisionDuringConstruction() { Collection conceptDescriptionsWithCollidingIds = createConceptDescriptionCollectionWithCollidingIds(); - new CrudConceptDescriptionRepository(backendProvider, conceptDescriptionsWithCollidingIds); + CrudConceptDescriptionRepositoryFactory.builder().backend(backend).remoteCollection(conceptDescriptionsWithCollidingIds).create(); } private Collection createConceptDescriptionCollectionWithCollidingIds() { diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml index c76caf8c7..5e3e3957b 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml @@ -44,5 +44,10 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionMongoDBBackendProvider.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionMongoDBBackendProvider.java deleted file mode 100644 index 3809cc0f2..000000000 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionMongoDBBackendProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; - -import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -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.repository.support.MappingMongoEntityInformation; -import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; - -/** - * - * MongoDB Backend Provider for the {@link ConceptDescription} - * - * @author mateusmolina, despen, danish - */ -@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -@Component -public class ConceptDescriptionMongoDBBackendProvider implements ConceptDescriptionBackendProvider { - - private BasyxMongoMappingContext mappingContext; - private MongoTemplate template; - - @Autowired - public ConceptDescriptionMongoDBBackendProvider(BasyxMongoMappingContext mappingContext, @Value("${basyx.cdrepository.mongodb.collectionName:cd-repo}") String collectionName, MongoTemplate template) { - super(); - this.mappingContext = mappingContext; - this.template = template; - - mappingContext.addEntityMapping(ConceptDescription.class, collectionName); - } - - @Override - public CrudRepository getCrudRepository() { - @SuppressWarnings("unchecked") - MongoPersistentEntity entity = (MongoPersistentEntity) mappingContext.getPersistentEntity(ConceptDescription.class); - - return new SimpleMongoRepository<>(new MappingMongoEntityInformation<>(entity), template); - } - -} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/MongoDbConceptDescriptionBackendConfiguration.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/MongoDbConceptDescriptionBackendConfiguration.java new file mode 100644 index 000000000..c885bb0ec --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/MongoDbConceptDescriptionBackendConfiguration.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Configuration for the MongoDB concept description repository backend + * + * @author mateusmolina + */ +@Configuration +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend") +public class MongoDbConceptDescriptionBackendConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.cdrepository.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "cd-repo"; + + @Bean + MappingEntry cdMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, ConceptDescription.class); + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/DummyConceptDescriptionRepositoryConfig.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/DummyConceptDescriptionRepositoryConfig.java index 146c08837..1da2e423b 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/DummyConceptDescriptionRepositoryConfig.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/DummyConceptDescriptionRepositoryConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,14 +25,10 @@ package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.MongoTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * Configuration for tests @@ -42,21 +38,12 @@ */ @Configuration public class DummyConceptDescriptionRepositoryConfig { - public final static String COLLECTION = "cdRepositoryPersistencyTestCollection"; - public final static String DB = "BaSyxTestDb"; - @Bean - public ConceptDescriptionRepository createConceptDescriptionRepository(MongoTemplate template) { - return new SimpleConceptDescriptionRepositoryFactory(new ConceptDescriptionMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template)).create(); - } + static final String TEST_COLLECTION_NAME = "testConceptDescriptionCollection"; @Bean - public MongoTemplate createCDMongoTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, DB); + ConceptDescriptionRepository conceptDescriptionRepository(ConceptDescriptionRepositoryFactory factory) { + return factory.create(); } } \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepository.java index ece4fcc7a..b4f1cc0dc 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,119 +25,51 @@ package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; -import static org.junit.Assert.*; - -import java.util.Arrays; import java.util.Collection; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultConceptDescription; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.core.ConceptDescriptionRepositorySuite; -import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; +import org.springframework.test.context.junit4.SpringRunner; /** * Tests the {@link MongoDBConceptDescriptionRepository} * - * @author danish, kammognie + * @author danish, kammognie, mateusmolina * */ +@SpringBootTest +@RunWith(SpringRunner.class) public class TestMongoDBConceptDescriptionRepository extends ConceptDescriptionRepositorySuite { - private static final String CONFIGURED_CD_REPO_NAME = "configured-cd-repo-name"; - private static final String COLLECTION = "conceptDescTestCollection"; - - @Override - protected ConceptDescriptionRepository getConceptDescriptionRepository() { - MongoTemplate template = createTemplate(); - MongoDBUtilities.clearCollection(template, COLLECTION); + @Autowired + private MongoTemplate mongoTemplate; - ConceptDescriptionBackendProvider cdBackendProvider = new ConceptDescriptionMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template); - ConceptDescriptionRepositoryFactory cdRepositoryFactory = new SimpleConceptDescriptionRepositoryFactory(cdBackendProvider); + @Autowired + private ConceptDescriptionRepository conceptDescriptionRepository; - return cdRepositoryFactory.create(); - } + @Autowired + private ConceptDescriptionBackend conceptDescriptionRepositoryBackend; @Override - protected ConceptDescriptionRepository getConceptDescriptionRepository(Collection conceptDescriptions) { - MongoTemplate template = createTemplate(); - - MongoDBUtilities.clearCollection(template, COLLECTION); - - ConceptDescriptionBackendProvider cdBackendProvider = new ConceptDescriptionMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template); - ConceptDescriptionRepositoryFactory cdRepositoryFactory = new SimpleConceptDescriptionRepositoryFactory(cdBackendProvider, conceptDescriptions); - - return cdRepositoryFactory.create(); - } - - @Test - public void testConfiguredMongoDBConceptDescriptionRepositoryName() { - MongoTemplate template = createTemplate(); - - MongoDBUtilities.clearCollection(template, COLLECTION); - - ConceptDescriptionBackendProvider cdBackendProvider = new ConceptDescriptionMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template); - ConceptDescriptionRepository repo = new CrudConceptDescriptionRepository(cdBackendProvider, CONFIGURED_CD_REPO_NAME); - - assertEquals(CONFIGURED_CD_REPO_NAME, repo.getName()); - } - - @Test - public void conceptDescriptionIsPersisted() { - ConceptDescriptionRepository conceptDescriptionRepository = getConceptDescriptionRepository(); - ConceptDescription expectedConceptDescription = createDummyConceptDescriptionOnRepo(conceptDescriptionRepository); - ConceptDescription retrievedConceptDescription = getConceptDescriptionFromNewBackendInstance(conceptDescriptionRepository, expectedConceptDescription.getId()); - - assertEquals(expectedConceptDescription, retrievedConceptDescription); - } - - @Test - public void updatedConceptDescriptionIsPersisted() { - ConceptDescriptionRepository mongoDBConceptDescriptionRepository = getConceptDescriptionRepository(); - - ConceptDescription expectedConceptDescription = createDummyConceptDescriptionOnRepo(mongoDBConceptDescriptionRepository); - - addDescriptionToConceptDescription(expectedConceptDescription); - - mongoDBConceptDescriptionRepository.updateConceptDescription(expectedConceptDescription.getId(), expectedConceptDescription); - - ConceptDescription retrievedConceptDescription = getConceptDescriptionFromNewBackendInstance(mongoDBConceptDescriptionRepository, expectedConceptDescription.getId()); - - assertEquals(expectedConceptDescription, retrievedConceptDescription); - } - - private void addDescriptionToConceptDescription(ConceptDescription expectedConceptDescription) { - expectedConceptDescription.setDescription(Arrays.asList(new DefaultLangStringTextType.Builder().text("description").language("en").build())); - } - - private ConceptDescription getConceptDescriptionFromNewBackendInstance(ConceptDescriptionRepository conceptDescriptionRepository, String conceptDescriptionId) { - ConceptDescription retrievedConceptDescription = conceptDescriptionRepository.getConceptDescription(conceptDescriptionId); - - return retrievedConceptDescription; + protected ConceptDescriptionRepository getConceptDescriptionRepository() { + return conceptDescriptionRepository; } - private ConceptDescription createDummyConceptDescriptionOnRepo(ConceptDescriptionRepository conceptDescriptionRepository) { - ConceptDescription expectedConceptDescription = new DefaultConceptDescription.Builder().id("dummy").build(); - - conceptDescriptionRepository.createConceptDescription(expectedConceptDescription); - - return expectedConceptDescription; + @Before + public void cleanup() { + MongoDBUtilities.clearCollection(mongoTemplate, DummyConceptDescriptionRepositoryConfig.TEST_COLLECTION_NAME); } - private MongoTemplate createTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, "BaSyxTestDb"); + @Override + protected ConceptDescriptionRepository getConceptDescriptionRepository(Collection conceptDescriptions) { + return CrudConceptDescriptionRepositoryFactory.builder().backend(conceptDescriptionRepositoryBackend).remoteCollection(conceptDescriptions).create(); } } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepositoryPersistency.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepositoryPersistency.java index acb883eab..60c6ff2b7 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepositoryPersistency.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/TestMongoDBConceptDescriptionRepositoryPersistency.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -51,7 +51,7 @@ public static void initComponent() { @Before public void clearTemplate() { MongoTemplate mongoTemplate = applicationContext.getBean(MongoTemplate.class); - MongoDBUtilities.clearCollection(mongoTemplate, DummyConceptDescriptionRepositoryConfig.COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, DummyConceptDescriptionRepositoryConfig.TEST_COLLECTION_NAME); } @Override diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/application.properties b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..ce8cab5ff --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,11 @@ +basyx.backend = MongoDB + +basyx.cdrepository.mongodb.collectionName= testConceptDescriptionCollection + + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackendProvider.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackend.java similarity index 83% rename from basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackendProvider.java rename to basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackend.java index b7d58cd24..596ceb177 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackendProvider.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/ConceptDescriptionBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,18 +22,19 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; /** - * Backend provider for the {@link ConceptDescription} + * Backend interface for the {@link ConceptDescription} * - * @author mateusmolina, despen, danish + * @author mateusmolina */ -public interface ConceptDescriptionBackendProvider { - - public CrudRepository getCrudRepository(); +@Repository +public interface ConceptDescriptionBackend extends CrudRepository { } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepository.java index ec6db2645..97d7022e8 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -45,44 +45,30 @@ 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.springframework.data.repository.CrudRepository; +import org.springframework.lang.NonNull; /** - * Default Implementation for the {@link ConceptDescription} based on Spring - * {@link CrudRepository} + * Default Implementation for the {@link ConceptDescription} based on + * {@link ConceptDescriptionBackend} * * @author mateusmolina, despen, zhangzai, kammognie, danish * */ -public class CrudConceptDescriptionRepository implements ConceptDescriptionRepository { - - private CrudRepository conceptDescriptionBackend; - private String conceptDescriptionRepositoryName = null; - - public CrudConceptDescriptionRepository(ConceptDescriptionBackendProvider conceptDescriptionBackendProvider) { - this.conceptDescriptionBackend = conceptDescriptionBackendProvider.getCrudRepository(); - } - - public CrudConceptDescriptionRepository(ConceptDescriptionBackendProvider conceptDescriptionBackendProvider, String conceptDescriptionRepositoryName) { - this(conceptDescriptionBackendProvider); - - this.conceptDescriptionRepositoryName = conceptDescriptionRepositoryName; - } - - public CrudConceptDescriptionRepository(ConceptDescriptionBackendProvider aasBackendProvider, Collection conceptDescriptions) { - this(aasBackendProvider); - throwIfMissingId(conceptDescriptions); +public class CrudConceptDescriptionRepository implements ConceptDescriptionRepository { - assertIdUniqueness(conceptDescriptions); + private final ConceptDescriptionBackend conceptDescriptionBackend; + private final String conceptDescriptionRepositoryName; - initializeRemoteCollection(conceptDescriptions); + public CrudConceptDescriptionRepository(ConceptDescriptionBackend conceptDescriptionBackend, String cdRepositoryNameString) { + this.conceptDescriptionBackend = conceptDescriptionBackend; + conceptDescriptionRepositoryName = cdRepositoryNameString; } - public CrudConceptDescriptionRepository(ConceptDescriptionBackendProvider aasBackendProvider, Collection conceptDescriptions, String conceptDescriptionRepositoryName) { - this(aasBackendProvider, conceptDescriptions); + public CrudConceptDescriptionRepository(ConceptDescriptionBackend conceptDescriptionBackend, String cdRepositoryNameString, Collection remoteCollection) { + this(conceptDescriptionBackend, cdRepositoryNameString); - this.conceptDescriptionRepositoryName = conceptDescriptionRepositoryName; + initializeRemoteCollection(remoteCollection); } @Override @@ -172,10 +158,14 @@ public String getName() { return conceptDescriptionRepositoryName == null ? ConceptDescriptionRepository.super.getName() : conceptDescriptionRepositoryName; } - private void initializeRemoteCollection(Collection conceptDescriptions) { - if (conceptDescriptions == null || conceptDescriptions.isEmpty()) + private void initializeRemoteCollection(@NonNull Collection conceptDescriptions) { + if (conceptDescriptions.isEmpty()) return; + throwIfMissingId(conceptDescriptions); + + assertIdUniqueness(conceptDescriptions); + conceptDescriptions.stream().forEach(this::createConceptDescription); } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryFactory.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryFactory.java new file mode 100644 index 000000000..2d96d78da --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryFactory.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; + +import java.util.Collection; +import java.util.Optional; + +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +/** + * {@link CrudConceptDescriptionRepository} factory using a + * {@link ConceptDescriptionBackend} + * + * @author mateusmolina, danish + * + */ +@Component +@ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") +public class CrudConceptDescriptionRepositoryFactory implements ConceptDescriptionRepositoryFactory { + static final String DEFAULT_REPO_NAME = "cd-repo"; + + private final ConceptDescriptionBackend backend; + private final String repositoryName; + private Optional> remoteCollection = Optional.empty(); + + @Autowired + public CrudConceptDescriptionRepositoryFactory(ConceptDescriptionBackend backend, @Value("${basyx.cdrepo.name:"+DEFAULT_REPO_NAME+"}") String conceptDescriptionRepositoryName) { + this.backend = backend; + this.repositoryName = conceptDescriptionRepositoryName; + } + + public void setRemoteCollection(Collection collection){ + this.remoteCollection = Optional.of(collection); + } + + @Override + public ConceptDescriptionRepository create() { + return remoteCollection.map(conceptDescriptions -> new CrudConceptDescriptionRepository(backend, repositoryName, conceptDescriptions)).orElseGet(() -> new CrudConceptDescriptionRepository(backend, repositoryName)); + } + + public static Builder builder(){ + return new Builder(); + } + + public static class Builder { + private ConceptDescriptionBackend backend; + private Optional repositoryName = Optional.empty(); + private Optional> remoteCollection = Optional.empty(); + + + public Builder backend(ConceptDescriptionBackend backend){ + this.backend = backend; + return this; + } + + public Builder repositoryName(String repositoryName){ + this.repositoryName = Optional.of(repositoryName); + return this; + } + + public Builder remoteCollection(Collection remoteCollection){ + this.remoteCollection = Optional.of(remoteCollection); + return this; + } + + public CrudConceptDescriptionRepositoryFactory buildFactory(){ + assert backend != null; + + CrudConceptDescriptionRepositoryFactory factory = new CrudConceptDescriptionRepositoryFactory(backend, repositoryName.orElse(DEFAULT_REPO_NAME)); + + remoteCollection.ifPresent(factory::setRemoteCollection); + + return factory; + } + + public ConceptDescriptionRepository create(){ + return buildFactory().create(); + } + } +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/SimpleConceptDescriptionRepositoryFactory.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/SimpleConceptDescriptionRepositoryFactory.java deleted file mode 100644 index 1cae42799..000000000 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/SimpleConceptDescriptionRepositoryFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend; - -import java.util.Collection; - -import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepositoryFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; - -/** - * Simple Concept Description repository factory that creates a - * {@link CrudConceptDescriptionRepository} with a backend provider - * - * @author mateusmolina, danish - * - */ -@Component -@ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") -public class SimpleConceptDescriptionRepositoryFactory implements ConceptDescriptionRepositoryFactory { - - private ConceptDescriptionBackendProvider conceptDescriptionBackend; - private String conceptDescriptionRepositoryName = null; - private Collection conceptDescriptions; - - @Autowired(required = false) - public SimpleConceptDescriptionRepositoryFactory(ConceptDescriptionBackendProvider conceptDescriptionBackend) { - this.conceptDescriptionBackend = conceptDescriptionBackend; - } - - @Autowired(required = false) - public SimpleConceptDescriptionRepositoryFactory(ConceptDescriptionBackendProvider conceptDescriptionBackendProvider, @Value("${basyx.cdrepo.name:cd-repo}") String conceptDescriptionRepositoryName) { - this(conceptDescriptionBackendProvider); - - this.conceptDescriptionRepositoryName = conceptDescriptionRepositoryName; - } - - @Autowired(required = false) - public SimpleConceptDescriptionRepositoryFactory(ConceptDescriptionBackendProvider conceptDescriptionBackendProvider, Collection conceptDescriptions) { - this(conceptDescriptionBackendProvider); - - this.conceptDescriptions = conceptDescriptions; - } - - @Autowired(required = false) - public SimpleConceptDescriptionRepositoryFactory(ConceptDescriptionBackendProvider conceptDescriptionBackendProvider, Collection conceptDescriptions, - @Value("${basyx.cdrepo.name:cd-repo}") String conceptDescriptionRepositoryName) { - this(conceptDescriptionBackendProvider, conceptDescriptions); - - this.conceptDescriptionRepositoryName = conceptDescriptionRepositoryName; - } - - @Override - public ConceptDescriptionRepository create() { - - if (conceptDescriptions == null) - return new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptionRepositoryName); - - return new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptions, conceptDescriptionRepositoryName); - } - -} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryTest.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryTest.java index 974ac3aab..c6e95375a 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryTest.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/backend/CrudConceptDescriptionRepositoryTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -40,7 +40,7 @@ public class CrudConceptDescriptionRepositoryTest { @Test public void getConfiguredAasRepositoryName() { - ConceptDescriptionRepository repo = new CrudConceptDescriptionRepository(() -> null, CONFIGURED_CD_REPO_NAME); + ConceptDescriptionRepository repo = new CrudConceptDescriptionRepository(null, CONFIGURED_CD_REPO_NAME); assertEquals(CONFIGURED_CD_REPO_NAME, repo.getName()); } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/http/testconfig/DummyConceptDescriptionRepositoryConfig.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/http/testconfig/DummyConceptDescriptionRepositoryConfig.java index fbdb535cc..dd7e8aee0 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/http/testconfig/DummyConceptDescriptionRepositoryConfig.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-http/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/http/testconfig/DummyConceptDescriptionRepositoryConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,9 +26,8 @@ package org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.http.testconfig; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.ConceptDescriptionInMemoryBackendProvider; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.SimpleConceptDescriptionRepositoryFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.CrudConceptDescriptionRepositoryFactory; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.backend.InMemoryConceptDescriptionBackend; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -36,7 +35,7 @@ /** * Configuration for tests * - * @author danish, kammognie + * @author danish, kammognie, mateusmolina * */ @Configuration @@ -44,8 +43,7 @@ public class DummyConceptDescriptionRepositoryConfig { @Bean - @ConditionalOnMissingBean - public ConceptDescriptionRepository createConceptDescriptionRepository() { - return new SimpleConceptDescriptionRepositoryFactory(new ConceptDescriptionInMemoryBackendProvider()).create(); + ConceptDescriptionRepository createConceptDescriptionRepository() { + return CrudConceptDescriptionRepositoryFactory.builder().backend(new InMemoryConceptDescriptionBackend()).create(); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml index 1edfa7be2..3fea9c0cc 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-inmemory/pom.xml @@ -44,9 +44,7 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-backend-inmemory - test - org.springframework spring-context 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 53e7c15bb..bec27b212 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,22 +25,21 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; -import static org.junit.Assert.assertEquals; - -import java.util.Collection; - 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.backend.CrudSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; import org.eclipse.digitaltwin.basyx.submodelservice.DummySubmodelFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.junit.Test; import org.junit.Test.None; -import com.google.common.collect.Lists; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; /** * Tests the {@link CrudSubmodelRepository} with InMemory backend @@ -54,12 +53,12 @@ public class TestInMemorySubmodelRepository extends SubmodelRepositorySuite { @Override protected SubmodelRepository getSubmodelRepository() { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); + return CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).create(); } @Override protected SubmodelRepository getSubmodelRepository(Collection submodels) { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()), submodels).create(); + return CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).remoteCollection(submodels).create(); } @Override @@ -71,7 +70,7 @@ protected boolean fileExistsInStorage(String fileValue) { @Test public void getConfiguredInMemorySmRepositoryName() { - SubmodelRepository repo = new CrudSubmodelRepository(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository()), CONFIGURED_SM_REPO_NAME); + SubmodelRepository repo = CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).repositoryName(CONFIGURED_SM_REPO_NAME).create(); assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } @@ -91,11 +90,11 @@ public void assertIdUniqueness() { } private Collection createSubmodelCollectionWithCollidingIds() { - return Lists.newArrayList(DummySubmodelFactory.createTechnicalDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); + return List.of(DummySubmodelFactory.createTechnicalDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); } private Collection createSubmodelCollectionWithUniqueIds() { - return Lists.newArrayList(DummySubmodelFactory.createSimpleDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); + return List.of(DummySubmodelFactory.createSimpleDataSubmodel(), DummySubmodelFactory.createTechnicalDataSubmodel()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml index 64732a72d..294da0a3b 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml @@ -34,7 +34,7 @@ org.eclipse.digitaltwin.basyx - basyx.submodelservice-backend-inmemory + basyx.submodelservice-backend-mongodb org.eclipse.digitaltwin.basyx @@ -54,5 +54,10 @@ basyx.filerepository-backend-mongodb + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file 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 deleted file mode 100644 index cd414bb77..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/SubmodelMongoDBBackendProvider.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository; - -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -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.repository.support.MappingMongoEntityInformation; -import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; - -/** - * - * MongoDB Backend Provider for the {@link Submodel} - * - * @author mateusmolina, despen, danish - */ -@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -@Component -public class SubmodelMongoDBBackendProvider implements SubmodelBackendProvider { - - private BasyxMongoMappingContext mappingContext; - - private MongoTemplate template; - - @Autowired - public SubmodelMongoDBBackendProvider(BasyxMongoMappingContext mappingContext, @Value("${basyx.submodelrepository.mongodb.collectionName:submodel-repo}") String collectionName, MongoTemplate template) { - super(); - this.mappingContext = mappingContext; - this.template = template; - - mappingContext.addEntityMapping(Submodel.class, collectionName); - } - - @Override - public CrudRepository getCrudRepository() { - @SuppressWarnings("unchecked") - MongoPersistentEntity entity = (MongoPersistentEntity) mappingContext.getPersistentEntity(Submodel.class); - - return new SimpleMongoRepository<>(new MappingMongoEntityInformation<>(entity), template); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelServiceFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/MongoDbSubmodelRepositoryConfiguration.java similarity index 55% rename from basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelServiceFactory.java rename to basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/MongoDbSubmodelRepositoryConfiguration.java index 6ca0e49b4..f2b06d6d9 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelServiceFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/MongoDbSubmodelRepositoryConfiguration.java @@ -1,6 +1,6 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * + * Copyright (C) 2025 the Eclipse BaSyx Authors + * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -19,40 +19,35 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * + * * SPDX-License-Identifier: MIT ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelservice; +package org.eclipse.digitaltwin.basyx.submodelrepository.backend; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; /** - * SubmodelService factory returning an MongoDB backend SubmodelService + * Configuration for the MongoDB backend of the SubmodelRepository * - * @author zhangzai, mateusmolina - * + * @author mateusmolina */ -@ConditionalOnExpression("'${basyx.submodelservice.backend}'.equals('MongoDB') or '${basyx.backend}'.equals('MongoDB')") -@Component -public class MongoDBSubmodelServiceFactory implements SubmodelServiceFactory { - - private final FileRepository fileRepository; - private final SingleSubmodelMongoDBBackendProvider mongoDBBackendProvider; - - public MongoDBSubmodelServiceFactory(FileRepository fileRepository, SingleSubmodelMongoDBBackendProvider mongoDBBackendProvider) { - this.fileRepository = fileRepository; - this.mongoDBBackendProvider = mongoDBBackendProvider; - } - - @Override - public SubmodelService create(Submodel submodel) { - return new MongoDBSubmodelService(submodel, fileRepository, mongoDBBackendProvider.getCrudRepository()); - } - +@Configuration +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.submodelrepository.backend") +public class MongoDbSubmodelRepositoryConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.submodelrepository.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "submodel-repo"; + + @Bean + MappingEntry submodelMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, Submodel.class); + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/DummySubmodelRepositoryConfig.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/DummySubmodelRepositoryConfig.java index 6bee17b66..af89aa6c0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/DummySubmodelRepositoryConfig.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/DummySubmodelRepositoryConfig.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,16 +25,8 @@ package org.eclipse.digitaltwin.basyx.submodelrepository; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -import org.eclipse.digitaltwin.basyx.core.filerepository.MongoDBFileRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; /** * Configuration for tests @@ -45,25 +37,11 @@ @Configuration public class DummySubmodelRepositoryConfig { - public final static String COLLECTION = "submodelRepositoryPersistencyTestCollection"; - public final static String DB = "BaSyxTestDb"; + static final String TEST_COLLECTION = "submodelRepositoryTestCollection"; @Bean - public SubmodelRepository createSubmodelRepository(MongoTemplate template) { - return new SimpleSubmodelRepositoryFactory(new SubmodelMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, template), new InMemorySubmodelServiceFactory(new MongoDBFileRepository(configureDefaultGridFsTemplate(template)))).create(); - } - - @Bean - public MongoTemplate createSMMongoTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; - - MongoClient client = MongoClients.create(connectionURL); - - return new MongoTemplate(client, DB); - } - - private GridFsTemplate configureDefaultGridFsTemplate(MongoTemplate mongoTemplate) { - return new GridFsTemplate(mongoTemplate.getMongoDatabaseFactory(), mongoTemplate.getConverter()); + SubmodelRepository submodelRepository(SubmodelRepositoryFactory factory) { + return factory.create(); } } \ No newline at end of file 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 68f2d4474..48d04c4fb 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,56 +24,60 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.submodelrepository; -import static org.junit.Assert.assertEquals; - -import java.util.Collection; - import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -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.operation.InvokableOperation; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SubmodelBackendProvider; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.SubmodelBackend; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.gridfs.GridFsCriteria; import org.springframework.data.mongodb.gridfs.GridFsTemplate; +import org.springframework.test.context.junit4.SpringRunner; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; +import java.util.Collection; +@SpringBootTest +@RunWith(SpringRunner.class) public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { - private final String COLLECTION = "submodelTestCollection"; - private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; - private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); - private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); - private final GridFsTemplate GRIDFS_TEMPLATE = new GridFsTemplate(TEMPLATE.getMongoDatabaseFactory(), TEMPLATE.getConverter()); - private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private GridFsTemplate gridFsTemplate; + + @Autowired + private SubmodelRepository submodelRepository; + + @Autowired + private SubmodelBackend submodelBackend; + + @Autowired 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(fileRepository)); + return submodelRepository; + } - return submodelRepositoryFactory.create(); + @Before + public void cleanup() { + MongoDBUtilities.clearCollection(mongoTemplate, DummySubmodelRepositoryConfig.TEST_COLLECTION); + gridFsTemplate.delete(new Query(GridFsCriteria.whereFilename().ne(""))); } @Override protected SubmodelRepository getSubmodelRepository(Collection submodels) { - SubmodelRepository repo = getSubmodelRepository(); - - addSubmodelsToRepoWithoutInvokableOperations(submodels, repo); - - return repo; + return CrudSubmodelRepositoryFactory.builder().backend(submodelBackend).fileRepository(fileRepository).remoteCollection(submodels).create(); } @Override @@ -81,14 +85,6 @@ protected boolean fileExistsInStorage(String fileValue) { return fileRepository.exists(fileValue); } - @Test - public void getConfiguredMongoDBSmRepositoryName() { - SubmodelBackendProvider submodelBackendProvider = new SubmodelMongoDBBackendProvider(new BasyxMongoMappingContext(), COLLECTION, TEMPLATE); - SubmodelRepository repo = new SimpleSubmodelRepositoryFactory(submodelBackendProvider, new InMemorySubmodelServiceFactory(fileRepository), CONFIGURED_SM_REPO_NAME).create(); - - assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); - } - @Override @Ignore public void invokeOperation() { @@ -101,15 +97,4 @@ public void invokeNonOperation() { super.invokeNonOperation(); } - private static Submodel removeInvokableFromInvokableOperation(Submodel sm) { - sm.getSubmodelElements().stream().filter(InvokableOperation.class::isInstance).map(InvokableOperation.class::cast).forEach(o -> o.setInvokable(null)); - return sm; - } - - private static void addSubmodelsToRepoWithoutInvokableOperations(Collection submodels, SubmodelRepository repo) { - submodels.stream() - .map(TestMongoDBSubmodelRepository::removeInvokableFromInvokableOperation) // TODO: Remove this after MongoDB uses AAS4J serializer - .forEach(repo::createSubmodel); - } - } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepositoryPersistency.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepositoryPersistency.java index 00830ce32..a924463cf 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepositoryPersistency.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepositoryPersistency.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -49,7 +49,7 @@ public static void initComponent() { @Before public void clearTemplate() { MongoTemplate mongoTemplate = applicationContext.getBean(MongoTemplate.class); - MongoDBUtilities.clearCollection(mongoTemplate, DummySubmodelRepositoryConfig.COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, DummySubmodelRepositoryConfig.TEST_COLLECTION); } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/application.properties b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..6436b9eeb --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,10 @@ +basyx.backend = MongoDB + +basyx.submodelrepository.mongodb.collectionName= submodelRepositoryTestCollection + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml index 95c269913..23a86183c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml @@ -31,5 +31,9 @@ org.eclipse.digitaltwin.basyx basyx.filerepository-backend + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-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 305b8ad94..12205ba75 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,24 +25,10 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.backend; -import java.io.InputStream; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; -import org.eclipse.digitaltwin.basyx.core.exceptions.IdentificationMismatchException; -import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.*; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; @@ -50,53 +36,41 @@ 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.backend.SubmodelBackend; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.data.repository.CrudRepository; +import org.springframework.lang.NonNull; + +import java.io.InputStream; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** - * Default Implementation for the {@link SubmodelRepository} based on Spring - * {@link CrudRepository} - * + * Default Implementation for the {@link SubmodelRepository} using a {@link CrudRepository} as backend. + * * @author danish, mateusmolina * */ public class CrudSubmodelRepository implements SubmodelRepository { - private Logger logger = LoggerFactory.getLogger(CrudSubmodelRepository.class); - private CrudRepository submodelBackend; - - private SubmodelServiceFactory submodelServiceFactory; - - private String submodelRepositoryName = null; + private final SubmodelBackend submodelBackend; + private final SubmodelServiceFactory submodelServiceFactory; + private final String submodelRepositoryName; - public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory) { - this.submodelBackend = submodelBackendProvider.getCrudRepository(); + public CrudSubmodelRepository(SubmodelBackend submodelBackend, SubmodelServiceFactory submodelServiceFactory, String submodelRepositoryName) { + this.submodelBackend = submodelBackend; this.submodelServiceFactory = submodelServiceFactory; - } - - public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, String submodelRepositoryName) { - this(submodelBackendProvider, submodelServiceFactory); - this.submodelRepositoryName = submodelRepositoryName; - } - public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, Collection submodels) { - this(submodelBackendProvider, submodelServiceFactory); - - throwIfMissingId(submodels); - - throwIfHasCollidingIds(submodels); - - initializeRemoteCollection(submodels); } - public CrudSubmodelRepository(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, Collection submodels, String submodelRepositoryName) { - this(submodelBackendProvider, submodelServiceFactory, submodels); + public CrudSubmodelRepository(SubmodelBackend submodelBackend, SubmodelServiceFactory submodelServiceFactory, String submodelRepositoryName, + Collection submodels) { + this(submodelBackend, submodelServiceFactory, submodelRepositoryName); - this.submodelRepositoryName = submodelRepositoryName; + initializeRemoteCollection(submodels); } @Override @@ -169,72 +143,47 @@ public void deleteSubmodel(String submodelId) throws ElementDoesNotExistExceptio @Override public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { - return getSubmodelServiceOrThrow(submodelId).getSubmodelElements(pInfo); + return getService(submodelId).getSubmodelElements(pInfo); } @Override public SubmodelElement getSubmodelElement(String submodelId, String smeIdShortPath) throws ElementDoesNotExistException { - return getSubmodelServiceOrThrow(submodelId).getSubmodelElement(smeIdShortPath); + return getService(submodelId).getSubmodelElement(smeIdShortPath); } @Override public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { - return getSubmodelServiceOrThrow(submodelId).getSubmodelElementValue(smeIdShort); + return getService(submodelId).getSubmodelElementValue(smeIdShort); } @Override public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - submodelService.setSubmodelElementValue(smeIdShort, value); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).setSubmodelElementValue(smeIdShort, value); } @Override public void createSubmodelElement(String submodelId, SubmodelElement smElement) { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - submodelService.createSubmodelElement(smElement); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).createSubmodelElement(smElement); } @Override public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - submodelService.createSubmodelElement(idShortPath, smElement); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).createSubmodelElement(idShortPath, smElement); } @Override public void updateSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { - - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - SubmodelElement element = submodelService.getSubmodelElement(idShortPath); - - throwIfMismatchingIds(element.getIdShort(), submodelElement.getIdShort()); - - submodelService.updateSubmodelElement(idShortPath, submodelElement); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).updateSubmodelElement(idShortPath, submodelElement); } @Override public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - submodelService.deleteSubmodelElement(idShortPath); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).deleteSubmodelElement(idShortPath); } @Override public OperationVariable[] invokeOperation(String submodelId, String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { - return getSubmodelServiceOrThrow(submodelId).invokeOperation(idShortPath, input); + return getService(submodelId).invokeOperation(idShortPath, input); } @Override @@ -244,7 +193,6 @@ public SubmodelValueOnly getSubmodelByIdValueOnly(String submodelId) throws Elem @Override public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException { - Submodel submodel = getSubmodel(submodelId); return SubmodelMetadataUtil.extractMetadata(submodel); @@ -252,34 +200,36 @@ 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); - - return submodelService.getFileByPath(idShortPath); + return getService(submodelId).getFileByPath(idShortPath); } @Override public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - submodelService.setFileValue(idShortPath, fileName, inputStream); - - updateSubmodel(submodelId, submodelService.getSubmodel()); + getService(submodelId).setFileValue(idShortPath, fileName, inputStream); } @Override public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); + getService(submodelId).deleteFileValue(idShortPath); + } - submodelService.deleteFileValue(idShortPath); + @Override + public void patchSubmodelElements(String submodelId, List submodelElementList) { + getService(submodelId).patchSubmodelElements(submodelElementList); + } - updateSubmodel(submodelId, submodelService.getSubmodel()); + @Override + public InputStream getFileByFilePath(String submodelId, String filePath) { + return getService(submodelId).getFileByFilePath(filePath); } - - private void initializeRemoteCollection(Collection submodels) { - if (submodels == null || submodels.isEmpty()) + private void initializeRemoteCollection(@NonNull Collection submodels) { + if (submodels.isEmpty()) return; + throwIfMissingId(submodels); + throwIfHasCollidingIds(submodels); + submodels.stream().forEach(this::createSubmodel); } @@ -295,12 +245,6 @@ private void throwIfMissingId(Collection submodels) { submodels.stream().map(Submodel::getId).forEach(this::throwIfSubmodelIdEmptyOrNull); } - private SubmodelService getSubmodelServiceOrThrow(String submodelId) { - Submodel submodel = submodelBackend.findById(submodelId).orElseThrow(() -> new ElementDoesNotExistException(submodelId)); - - return submodelServiceFactory.create(submodel); - } - private void throwIfMismatchingIds(String existingId, String idToBeUpdated) { if (!existingId.equals(idToBeUpdated)) @@ -325,18 +269,8 @@ private void throwIfSubmodelDoesNotExist(String submodelId) { throw new ElementDoesNotExistException(submodelId); } - @Override - public void patchSubmodelElements(String submodelId, List submodelElementList) { - Submodel submodel = getSubmodel(submodelId); - submodel.setSubmodelElements(submodelElementList); - submodelBackend.save(submodel); - } - - @Override - public InputStream getFileByFilePath(String submodelId, String filePath) { - SubmodelService submodelService = getSubmodelServiceOrThrow(submodelId); - - return submodelService.getFileByFilePath(filePath); + private SubmodelService getService(String submodelId) { + return submodelServiceFactory.create(submodelId); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryFactory.java new file mode 100644 index 000000000..90b4a6253 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/CrudSubmodelRepositoryFactory.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelrepository.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.CrudSubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.SubmodelBackend; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Optional; + +/** + * Simple Submodel repository factory that creates a + * {@link CrudSubmodelRepository} with a backend provider and a service factory + * + * @author mateusmolina, danish + */ +@Component +@ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") +public class CrudSubmodelRepositoryFactory implements SubmodelRepositoryFactory { + + static final String DEFAULT_REPOSITORY_NAME = "sm-repo"; + + private final SubmodelBackend backend; + private final SubmodelServiceFactory submodelServiceFactory; + private final String submodelRepositoryName; + private Optional> submodels = Optional.empty(); + + @Autowired + public CrudSubmodelRepositoryFactory(SubmodelBackend submodelRepositoryBackend, SubmodelServiceFactory submodelServiceFactory, @Value("${basyx.smrepo.name:" + DEFAULT_REPOSITORY_NAME + "}") String submodelRepositoryName) { + this.backend = submodelRepositoryBackend; + this.submodelServiceFactory = submodelServiceFactory; + this.submodelRepositoryName = submodelRepositoryName; + } + + public void setRemoteCollection(Collection submodels) { + this.submodels = Optional.of(submodels); + } + + @Override + public SubmodelRepository create() { + return submodels.map(submodelCollection -> new CrudSubmodelRepository(backend, submodelServiceFactory, submodelRepositoryName, submodelCollection)) + .orElseGet(() -> new CrudSubmodelRepository(backend, submodelServiceFactory, submodelRepositoryName)); + } + + public static Builder builder(){ + return new Builder(); + } + + public static class Builder { + private SubmodelBackend submodelBackend; + private FileRepository fileRepository; + private Optional repositoryName = Optional.empty(); + private Optional> submodels = Optional.empty(); + + public Builder backend(SubmodelBackend submodelBackend) { + this.submodelBackend = submodelBackend; + return this; + } + + public Builder fileRepository(FileRepository fileRepository) { + this.fileRepository = fileRepository; + return this; + } + + public Builder repositoryName(String repositoryName){ + this.repositoryName = Optional.of(repositoryName); + return this; + } + + public Builder remoteCollection(Collection submodels){ + this.submodels = Optional.of(submodels); + return this; + } + + public CrudSubmodelRepositoryFactory buildFactory(){ + assert submodelBackend != null; + assert fileRepository != null; + + CrudSubmodelServiceFactory submodelServiceFactory = new CrudSubmodelServiceFactory(submodelBackend, fileRepository); + + CrudSubmodelRepositoryFactory submodelRepositoryFactory = new CrudSubmodelRepositoryFactory(submodelBackend, submodelServiceFactory, repositoryName.orElse(DEFAULT_REPOSITORY_NAME)); + + submodels.ifPresent(submodelRepositoryFactory::setRemoteCollection); + + return submodelRepositoryFactory; + } + + public SubmodelRepository create(){ + return buildFactory().create(); + } + + } + +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SimpleSubmodelRepositoryFactory.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SimpleSubmodelRepositoryFactory.java deleted file mode 100644 index 815fe03bc..000000000 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SimpleSubmodelRepositoryFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.backend; - -import java.util.Collection; - -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; - -/** - * Simple Submodel repository factory that creates a - * {@link CrudSubmodelRepository} with a backend provider and a service factory - * - * @author mateusmolina, danish - * - */ -@Component -@ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") -public class SimpleSubmodelRepositoryFactory implements SubmodelRepositoryFactory { - - private SubmodelBackendProvider submodelBackendProvider; - private SubmodelServiceFactory submodelServiceFactory; - private String submodelRepositoryName = null; - private Collection submodels; - - @Autowired(required = false) - public SimpleSubmodelRepositoryFactory(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory) { - this.submodelBackendProvider = submodelBackendProvider; - this.submodelServiceFactory = submodelServiceFactory; - } - - @Autowired(required = false) - public SimpleSubmodelRepositoryFactory(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, @Value("${basyx.smrepo.name:sm-repo}") String submodelRepositoryName) { - this(submodelBackendProvider, submodelServiceFactory); - - this.submodelRepositoryName = submodelRepositoryName; - } - - @Autowired(required = false) - public SimpleSubmodelRepositoryFactory(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, Collection submodels) { - this(submodelBackendProvider, submodelServiceFactory); - - this.submodels = submodels; - } - - @Autowired(required = false) - public SimpleSubmodelRepositoryFactory(SubmodelBackendProvider submodelBackendProvider, SubmodelServiceFactory submodelServiceFactory, Collection submodels, - @Value("${basyx.smrepo.name:sm-repo}") String submodelRepositoryName) { - this(submodelBackendProvider, submodelServiceFactory, submodels); - - this.submodelRepositoryName = submodelRepositoryName; - } - - @Override - public SubmodelRepository create() { - - if (submodels == null) - return new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodelRepositoryName); - - return new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodels, submodelRepositoryName); - } - -} 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 9f3c658dd..1722ae13d 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,12 +25,10 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.backend; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.junit.Test; -import org.springframework.data.repository.CrudRepository; /** * Tests {@link CrudSubmodelRepository} @@ -43,20 +41,9 @@ public class CrudSubmodelRepositoryTest { @Test public void getConfiguredAasRepositoryName() { - SubmodelRepository repo = new CrudSubmodelRepository(createSubmodelProvider(), null, CONFIGURED_SUBMODEL_REPO_NAME); + SubmodelRepository repo = new CrudSubmodelRepository(null, null, CONFIGURED_SUBMODEL_REPO_NAME); assertEquals(CONFIGURED_SUBMODEL_REPO_NAME, repo.getName()); } - private SubmodelBackendProvider createSubmodelProvider() { - - return new SubmodelBackendProvider() { - @Override - public CrudRepository getCrudRepository() { - return null; - } - - }; - } - } 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 d92904467..6c070c39d 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 @@ -1,7 +1,5 @@ -package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; - /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,6 +23,8 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ +package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; @@ -54,11 +54,10 @@ import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; 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; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.eclipse.digitaltwin.basyx.submodelservice.value.PropertyValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.paho.client.mqttv3.IMqttClient; @@ -312,8 +311,8 @@ private static SubmodelRepository createMqttSubmodelRepository(MqttClient client FileRepository fileRepository = new InMemoryFileRepository(); SAVED_FILE_PATH = fileRepository.save(new FileMetadata(FILE_SUBMODEL_ELEMENT_NAME, "", getInputStreamOfDummyFile(FILE_SUBMODEL_ELEMENT_CONTENT))); - - SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(fileRepository)); + + SubmodelRepositoryFactory repoFactory = CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(fileRepository).buildFactory(); 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 7f6800b3c..e32159d7f 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,19 +25,9 @@ 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; -import java.util.List; - -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.Qualifier; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.digitaltwin.aas4j.v3.model.*; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultQualifier; @@ -49,12 +39,11 @@ import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; import org.eclipse.digitaltwin.basyx.http.SerializationExtension; import org.eclipse.digitaltwin.basyx.operation.InvokableOperation; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; import org.eclipse.digitaltwin.basyx.submodelrepository.http.SubmodelRepositoryHTTPSerializationExtension; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -65,8 +54,12 @@ 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 java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; /** * Tests the {@link OperationDelegationSubmodelRepository} feature @@ -189,7 +182,7 @@ private void createExpectationsForPost(String path, String requestBody, String e } private static SubmodelRepository createOperationDelegationSubmodelRepository(OperationDelegation operationDelegation) { - SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())); + SubmodelRepositoryFactory repoFactory = CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).buildFactory(); return new OperationDelegationSubmodelRepositoryFactory(repoFactory, operationDelegation).create(); } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/SubmodelRepositoryRegistryLinkDescriptorGenerationTest.java b/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/SubmodelRepositoryRegistryLinkDescriptorGenerationTest.java index f220de890..2208e1c4e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/SubmodelRepositoryRegistryLinkDescriptorGenerationTest.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/SubmodelRepositoryRegistryLinkDescriptorGenerationTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,12 +25,6 @@ package org.eclipse.digitaltwin.basyx.submodelrepository.feature.registry.integration; -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.client.internal.ApiException; @@ -40,18 +34,23 @@ import org.eclipse.digitaltwin.basyx.submodelregistry.client.factory.SubmodelDescriptorFactory; import org.eclipse.digitaltwin.basyx.submodelregistry.client.mapper.AttributeMapper; import org.eclipse.digitaltwin.basyx.submodelregistry.client.model.SubmodelDescriptor; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.backend.SimpleSubmodelRepositoryFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.mockito.Mockito; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; + /** - * Test suite for {@link RegistryIntegrationAasRepository} feature + * Test suite for RegistryIntegrationAasRepository feature */ @RunWith(Parameterized.class) public class SubmodelRepositoryRegistryLinkDescriptorGenerationTest { @@ -113,7 +112,7 @@ private Submodel createDummySubmodel() { } protected SubmodelRepository getSubmodelRepository() { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); + return CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).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 b7293f2c7..a63a9815b 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -26,10 +26,9 @@ 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; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; +import org.eclipse.digitaltwin.basyx.submodelrepository.backend.CrudSubmodelRepositoryFactory; +import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelBackend; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -48,6 +47,6 @@ public class DummySubmodelRepositoryConfig { @Bean @ConditionalOnMissingBean public SubmodelRepository createSubmodelRepository() { - return new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())).create(); + return CrudSubmodelRepositoryFactory.builder().backend(new InMemorySubmodelBackend()).fileRepository(new InMemoryFileRepository()).create(); } } diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml index 8f3759f2f..27762e031 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/pom.xml @@ -27,7 +27,14 @@ tests test - + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend + + + org.eclipse.digitaltwin.basyx + basyx.backend.inmemory.core + org.springframework spring-context diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelBackend.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelBackend.java new file mode 100644 index 000000000..edf7bb1a5 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelBackend.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice; + +import org.eclipse.digitaltwin.aas4j.v3.model.*; +import org.eclipse.digitaltwin.basyx.common.backend.inmemory.core.InMemoryCrudRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.SubmodelBackend; +import org.eclipse.digitaltwin.basyx.submodelservice.pathparsing.HierarchicalSubmodelElementParser; +import org.eclipse.digitaltwin.basyx.submodelservice.pathparsing.SubmodelElementIdShortHelper; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.eclipse.digitaltwin.basyx.submodelservice.value.factory.SubmodelElementValueMapperFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.TreeMap; +import java.util.stream.Collectors; + +/** + * Implements the SubmodelService as in-memory variant + * + * @author schnicke, danish, mateusmolina + */ +@ConditionalOnExpression("'${basyx.submodelservice.backend}'.equals('InMemory') or '${basyx.backend}'.equals('InMemory')") +@Component +public class InMemorySubmodelBackend extends InMemoryCrudRepository implements SubmodelBackend { + + public InMemorySubmodelBackend() { + super(Submodel::getId); + } + + @Override + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) { + List allSubmodels = getSubmodel(submodelId).getSubmodelElements(); + + TreeMap submodelMap = allSubmodels.stream().collect(Collectors.toMap(SubmodelElement::getIdShort, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, SubmodelElement::getIdShort); + return paginationSupport.getPaged(pInfo); + } + + @Override + public SubmodelElement getSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + return getParser(submodelId).getSubmodelElementFromIdShortPath(idShortPath); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String submodelId, String idShort) throws ElementDoesNotExistException { + return SubmodelElementValueMapperFactory.create(getSubmodelElement(submodelId, idShort)).getValue(); + } + + @SuppressWarnings("unchecked") + @Override + public synchronized void setSubmodelElementValue(String submodelId, String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { + SubmodelElementValueMapperFactory.create(getSubmodelElement(submodelId, idShort)).setValue(value); + } + + @Override + public synchronized void createSubmodelElement(String submodelId, SubmodelElement submodelElement) throws CollidingIdentifierException { + List smElements = getSubmodel(submodelId).getSubmodelElements(); + throwIfSubmodelElementExists(submodelId, submodelElement.getIdShort()); + smElements.add(submodelElement); + } + + @Override + public synchronized void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException, CollidingIdentifierException { + throwIfSubmodelElementExists(submodelId, getFullIdShortPath(idShortPath, submodelElement.getIdShort())); + + SubmodelElement parentSme = getParser(submodelId).getSubmodelElementFromIdShortPath(idShortPath); + if (parentSme instanceof SubmodelElementList list) { + List submodelElements = list.getValue(); + submodelElements.add(submodelElement); + } else if (parentSme instanceof SubmodelElementCollection collection) { + List submodelElements = collection.getValue(); + submodelElements.add(submodelElement); + } else if (parentSme instanceof Entity entity) { + List submodelElements = entity.getStatements(); + submodelElements.add(submodelElement); + } + } + + @Override + public synchronized void updateSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) { + deleteSubmodelElement(submodelId, idShortPath); + + String idShortPathParentSME = getParser(submodelId).getIdShortPathOfParentElement(idShortPath); + if (idShortPath.equals(idShortPathParentSME)) { + createSubmodelElement(submodelId, submodelElement); + return; + } + + createSubmodelElement(submodelId, idShortPathParentSME, submodelElement); + } + + @Override + public synchronized void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + + Submodel submodel = getSubmodel(submodelId); + if (!SubmodelElementIdShortHelper.isNestedIdShortPath(idShortPath)) { + deleteFlatSubmodelElement(submodel, idShortPath); + return; + } + deleteNestedSubmodelElement(submodel, idShortPath); + + } + + @Override + public synchronized void patchSubmodelElements(String submodelId, List submodelElementList) { + getSubmodel(submodelId).setSubmodelElements(submodelElementList); + } + + private static void deleteNestedSubmodelElement(Submodel submodel, String idShortPath) { + HierarchicalSubmodelElementParser parser = new HierarchicalSubmodelElementParser(submodel); + SubmodelElement sme = parser.getSubmodelElementFromIdShortPath(idShortPath); + if (SubmodelElementIdShortHelper.isDirectParentASubmodelElementList(idShortPath)) { + deleteNestedSubmodelElementFromList(parser, idShortPath, sme); + } else { + deleteNestedSubmodelElementFromCollectionOrEntity(parser, idShortPath, sme); + } + } + + private static void deleteNestedSubmodelElementFromList(HierarchicalSubmodelElementParser parser, String idShortPath, SubmodelElement sme) { + String collectionId = SubmodelElementIdShortHelper.extractDirectParentSubmodelElementListIdShort(idShortPath); + SubmodelElementList list = (SubmodelElementList) parser.getSubmodelElementFromIdShortPath(collectionId); + list.getValue().remove(sme); + } + + private static void deleteNestedSubmodelElementFromCollectionOrEntity(HierarchicalSubmodelElementParser parser, String idShortPath, SubmodelElement sme) { + String collectionId = SubmodelElementIdShortHelper.extractDirectParentSubmodelElementCollectionIdShort(idShortPath); + SubmodelElement parent = parser.getSubmodelElementFromIdShortPath(collectionId); + if (parent instanceof SubmodelElementCollection collection) { + collection.getValue().remove(sme); + } else if (parent instanceof Entity entity) { + entity.getStatements().remove(sme); + } + } + + private static void deleteFlatSubmodelElement(Submodel submodel, String idShortPath) throws ElementDoesNotExistException { + int index = findIndexOfElementTobeDeleted(submodel, idShortPath); + if (index >= 0) { + submodel.getSubmodelElements().remove(index); + return; + } + throw new ElementDoesNotExistException(); + } + + private static int findIndexOfElementTobeDeleted(Submodel submodel, String idShortPath) { + for (SubmodelElement sme : submodel.getSubmodelElements()) { + if (sme.getIdShort().equals(idShortPath)) { + return submodel.getSubmodelElements().indexOf(sme); + } + } + return -1; + } + + private static String getFullIdShortPath(String idShortPath, String submodelElementId) { + return idShortPath + "." + submodelElementId; + } + + private void throwIfSubmodelElementExists(String submodelId, String submodelElementId) { + try { + getSubmodelElement(submodelId, submodelElementId); + throw new CollidingIdentifierException(submodelElementId); + } catch (ElementDoesNotExistException e) { + } + } + + private HierarchicalSubmodelElementParser getParser(String submodelId) { + return new HierarchicalSubmodelElementParser(getSubmodel(submodelId)); + } + + private Submodel getSubmodel(String submodelId) { + return findById(submodelId).orElseThrow(() -> new ElementDoesNotExistException(submodelId)); + } +} 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 deleted file mode 100644 index 30f2a8d39..000000000 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java +++ /dev/null @@ -1,389 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.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.Entity; -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; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; -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.operation.InvokableOperation; -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, mateusmolina - * - */ -public class InMemorySubmodelService implements SubmodelService { - - private final Submodel submodel; - private HierarchicalSubmodelElementParser parser; - private SubmodelElementIdShortHelper helper = new SubmodelElementIdShortHelper(); - - private final FileRepository fileRepository; - - private final Object submodelLock = new Object(); - - /** - * Creates the InMemory SubmodelService containing the passed Submodel - * - * @param submodel - */ - public InMemorySubmodelService(Submodel submodel, FileRepository fileRepository) { - this.submodel = submodel; - this.fileRepository = fileRepository; - parser = new HierarchicalSubmodelElementParser(submodel); - } - - @Override - public Submodel getSubmodel() { - return submodel; - } - - @Override - 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)); - - PaginationSupport paginationSupport = new PaginationSupport<>(submodelMap, SubmodelElement::getIdShort); - CursorResult> paginatedSubmodels = paginationSupport.getPaged(pInfo); - return paginatedSubmodels; - } - - @Override - public SubmodelElement getSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - return parser.getSubmodelElementFromIdShortPath(idShortPath); - } - - @Override - public SubmodelElementValue getSubmodelElementValue(String idShort) throws ElementDoesNotExistException { - SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); - - return submodelElementValueFactory.create(getSubmodelElement(idShort)).getValue(); - } - - @SuppressWarnings("unchecked") - @Override - public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { - synchronized (submodelLock) { - SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); - - ValueMapper valueMapper = submodelElementValueFactory.create(getSubmodelElement(idShort)); - - valueMapper.setValue(value); - } - } - - @Override - public void createSubmodelElement(SubmodelElement submodelElement) throws CollidingIdentifierException { - synchronized (submodelLock) { - List smElements = submodel.getSubmodelElements(); - throwIfSubmodelElementExists(submodelElement.getIdShort()); - smElements.add(submodelElement); - } - } - - private void throwIfSubmodelElementExists(String submodelElementId) { - try { - getSubmodelElement(submodelElementId); - throw new CollidingIdentifierException(submodelElementId); - } catch (ElementDoesNotExistException e) { - return; - } - } - - @Override - public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException, CollidingIdentifierException { - synchronized (submodelLock) { - throwIfSubmodelElementExists(getFullIdShortPath(idShortPath, submodelElement.getIdShort())); - - SubmodelElement parentSme = parser.getSubmodelElementFromIdShortPath(idShortPath); - if (parentSme instanceof SubmodelElementList) { - SubmodelElementList list = (SubmodelElementList) parentSme; - List submodelElements = list.getValue(); - submodelElements.add(submodelElement); - } else if (parentSme instanceof SubmodelElementCollection) { - SubmodelElementCollection collection = (SubmodelElementCollection) parentSme; - List submodelElements = collection.getValue(); - submodelElements.add(submodelElement); - } else if (parentSme instanceof Entity) { - Entity entity = (Entity) parentSme; - List submodelElements = entity.getStatements(); - submodelElements.add(submodelElement); - } - } - } - - @Override - public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) { - synchronized (submodelLock) { - deleteSubmodelElement(idShortPath); - - String idShortPathParentSME = parser.getIdShortPathOfParentElement(idShortPath); - if (idShortPath.equals(idShortPathParentSME)) { - createSubmodelElement(submodelElement); - return; - } - createSubmodelElement(idShortPathParentSME, submodelElement); - } - } - - @Override - public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - synchronized (submodelLock) { - deleteAssociatedFileIfAny(idShortPath); - - if (!helper.isNestedIdShortPath(idShortPath)) { - deleteFlatSubmodelElement(idShortPath); - return; - } - deleteNestedSubmodelElement(idShortPath); - } - } - - private void deleteNestedSubmodelElement(String idShortPath) { - SubmodelElement sme = parser.getSubmodelElementFromIdShortPath(idShortPath); - if (helper.isDirectParentASubmodelElementList(idShortPath)) { - deleteNestedSubmodelElementFromList(idShortPath, sme); - } else { - deleteNestedSubmodelElementFromCollectionOrEntity(idShortPath, sme); - } - } - - private void deleteNestedSubmodelElementFromList(String idShortPath, SubmodelElement sme) { - String collectionId = helper.extractDirectParentSubmodelElementListIdShort(idShortPath); - SubmodelElementList list = (SubmodelElementList) parser.getSubmodelElementFromIdShortPath(collectionId); - list.getValue().remove(sme); - } - - private void deleteNestedSubmodelElementFromCollectionOrEntity(String idShortPath, SubmodelElement sme) { - String collectionId = helper.extractDirectParentSubmodelElementCollectionIdShort(idShortPath); - SubmodelElement parent = parser.getSubmodelElementFromIdShortPath(collectionId); - if (parent instanceof SubmodelElementCollection) { - SubmodelElementCollection collection = (SubmodelElementCollection) parent; - collection.getValue().remove(sme); - } else if (parent instanceof Entity) { - Entity entity = (Entity) parent; - entity.getStatements().remove(sme); - } - } - - private void deleteFlatSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - int index = findIndexOfElementTobeDeleted(idShortPath); - if (index >= 0) { - submodel.getSubmodelElements().remove(index); - return; - } - throw new ElementDoesNotExistException(); - } - - private int findIndexOfElementTobeDeleted(String idShortPath) { - for (SubmodelElement sme : submodel.getSubmodelElements()) { - if (sme.getIdShort().equals(idShortPath)) { - return submodel.getSubmodelElements().indexOf(sme); - } - } - return -1; - } - - @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; - } - - @Override - 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 { - synchronized (submodelLock) { - 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); - - if (fileRepository.exists(fileMetadata.getFileName())) - fileRepository.delete(fileMetadata.getFileName()); - - 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 { - synchronized (submodelLock) { - SubmodelElement submodelElement = getSubmodelElement(idShortPath); - - throwIfSmElementIsNotAFile(submodelElement); - - File fileSubmodelElement = (File) submodelElement; - String filePath = fileSubmodelElement.getValue(); - - fileRepository.delete(filePath); - - FileBlobValue fileValue = new FileBlobValue(" ", " "); - - setSubmodelElementValue(idShortPath, fileValue); - } - } - - @Override - public InputStream getFileByFilePath(String filePath) throws FileDoesNotExistException{ - return fileRepository.find(filePath); - } - - 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/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestCreateAndRemoveUnderPath.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestCreateAndRemoveUnderPath.java index 68baba6a5..0da689f0d 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestCreateAndRemoveUnderPath.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestCreateAndRemoveUnderPath.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -36,7 +36,8 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; -import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelService; +import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.CrudSubmodelServiceFactory; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -44,12 +45,12 @@ public class TestCreateAndRemoveUnderPath { private Submodel submodel; - private InMemorySubmodelService service; + private SubmodelService service; @Before public void init() throws DeserializationException, IOException { submodel = loadSubmodel(); - service = new InMemorySubmodelService(submodel, null); + service = new CrudSubmodelServiceFactory(new InMemorySubmodelBackend(), new InMemoryFileRepository()).create(submodel); } @Test 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 60926d00d..aa19f1bc9 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -28,6 +28,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.CrudSubmodelServiceFactory; /** * @@ -38,7 +39,7 @@ public class TestInMemorySubmodelService extends SubmodelServiceSuite { @Override protected SubmodelService getSubmodelService(Submodel submodel) { - return new InMemorySubmodelServiceFactory(new InMemoryFileRepository()).create(submodel); + return new CrudSubmodelServiceFactory(new InMemorySubmodelBackend(), new InMemoryFileRepository()).create(submodel); } @Override diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/pom.xml b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/pom.xml index 621d3b62a..641f1b99e 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/pom.xml +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/pom.xml @@ -16,6 +16,10 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-core + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend + org.eclipse.digitaltwin.basyx basyx.submodelservice-core @@ -44,5 +48,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-test + test + \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelService.java deleted file mode 100644 index 182add728..000000000 --- a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/MongoDBSubmodelService.java +++ /dev/null @@ -1,179 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelservice; - -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.FeatureNotSupportedException; -import org.eclipse.digitaltwin.basyx.core.exceptions.FileDoesNotExistException; -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.submodelservice.value.SubmodelElementValue; -import org.springframework.data.repository.CrudRepository; - -/** - * Implements the SubmodelService as MongoDB variant - * - * @author zhangzai, mateusmolina - * - */ -public class MongoDBSubmodelService implements SubmodelService { - - private final FileRepository fileRepository; - - private CrudRepository crudRepository; - - private Submodel submodel; - - /** - * Creates the MongoDB SubmodelService containing the passed Submodel - * - * @param submodel - */ - - public MongoDBSubmodelService(Submodel submodel, FileRepository fileRepository, CrudRepository crudRepository) { - this.submodel = submodel; - this.fileRepository = fileRepository; - this.crudRepository = crudRepository; - crudRepository.save(submodel); - } - - private InMemorySubmodelService getInMemorySubmodelService() { - return new InMemorySubmodelService(getSubmodel(), fileRepository); - } - - @Override - public Submodel getSubmodel() { - return crudRepository.findById(submodel.getId()).get(); - } - - @Override - public CursorResult> getSubmodelElements(PaginationInfo pInfo) { - return getInMemorySubmodelService().getSubmodelElements(pInfo); - } - - @Override - public SubmodelElement getSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - return getInMemorySubmodelService().getSubmodelElement(idShortPath); - } - - @Override - public SubmodelElementValue getSubmodelElementValue(String idShortPath) throws ElementDoesNotExistException { - return getInMemorySubmodelService().getSubmodelElementValue(idShortPath); - } - - @Override - public void setSubmodelElementValue(String idShortPath, SubmodelElementValue value) throws ElementDoesNotExistException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.setSubmodelElementValue(idShortPath, value); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - - } - - @Override - public void createSubmodelElement(SubmodelElement submodelElement) { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.createSubmodelElement(submodelElement); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - } - - @Override - public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.createSubmodelElement(idShortPath, submodelElement); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - - } - - @Override - public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.updateSubmodelElement(idShortPath, submodelElement); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - } - - @Override - public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.deleteSubmodelElement(idShortPath); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - - } - - @Override - public void patchSubmodelElements(List submodelElementList) { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.patchSubmodelElements(submodelElementList); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - } - - @Override - public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { - throw new FeatureNotSupportedException("invokeOperation"); - } - - @Override - public File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - return getInMemorySubmodelService().getFileByPath(idShortPath); - } - - @Override - public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.setFileValue(idShortPath, fileName, inputStream); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - } - - @Override - public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - InMemorySubmodelService inMemorySubmodelService = getInMemorySubmodelService(); - inMemorySubmodelService.deleteFileValue(idShortPath); - Submodel submodel = inMemorySubmodelService.getSubmodel(); - crudRepository.save(submodel); - - } - - @Override - public InputStream getFileByFilePath(String filePath) { - return getInMemorySubmodelService().getFileByFilePath(filePath); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SingleSubmodelMongoDBBackendProvider.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SingleSubmodelMongoDBBackendProvider.java deleted file mode 100644 index 5d81441ec..000000000 --- a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SingleSubmodelMongoDBBackendProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelservice; - -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; -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.repository.support.MappingMongoEntityInformation; -import org.springframework.data.mongodb.repository.support.SimpleMongoRepository; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; - -/** - * - * MongoDB Backend Provider for the {@link Submodel} - * - * @author mateusmolina, zhangzai - */ -@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") -@Component -public class SingleSubmodelMongoDBBackendProvider { - - private BasyxMongoMappingContext mappingContext; - - private MongoTemplate template; - - @Autowired - public SingleSubmodelMongoDBBackendProvider(BasyxMongoMappingContext mappingContext, @Value("${basyx.submodelservice.mongodb.collectionName:submodel-service}") String collectionName, MongoTemplate template) { - super(); - this.mappingContext = mappingContext; - this.template = template; - - mappingContext.addEntityMapping(Submodel.class, collectionName); - } - - public CrudRepository getCrudRepository() { - @SuppressWarnings("unchecked") - MongoPersistentEntity entity = (MongoPersistentEntity) mappingContext.getPersistentEntity(Submodel.class); - - return new SimpleMongoRepository<>(new MappingMongoEntityInformation<>(entity), template); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/IdShortPathParser.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/IdShortPathParser.java new file mode 100644 index 000000000..877918118 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/IdShortPathParser.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.springframework.lang.NonNull; + +/** + * Parses a string representation of a path to an IdShort in a submodel. + * + * @author mateusmolina + */ +public final class IdShortPathParser { + + public static Deque parse(@NonNull String idShortPath) { + Deque paths = new ArrayDeque<>(); + StringBuilder currentPath = new StringBuilder(); + StringBuilder currentIndex = new StringBuilder(); + boolean parsingIndex = false; + + for (char c : idShortPath.toCharArray()) { + if (c == '.') { + if (currentPath.length() > 0) { + paths.add(new IdShortPath(currentPath.toString())); + currentPath.setLength(0); + } + } else if (c == '[') { + if (currentPath.length() > 0) { + paths.add(new IdShortPath(currentPath.toString())); + currentPath.setLength(0); + } + parsingIndex = true; + } else if (c == ']') { + if (parsingIndex && currentIndex.length() > 0) { + paths.add(new IndexPath(Integer.parseInt(currentIndex.toString()))); + currentIndex.setLength(0); + parsingIndex = false; + } + } else { + if (parsingIndex) { + currentIndex.append(c); + } else { + currentPath.append(c); + } + } + } + + if (currentPath.length() > 0) { + paths.add(new IdShortPath(currentPath.toString())); + } + + return paths; + } + + public sealed interface GenericPath permits IdShortPath, IndexPath { + } + + public record IdShortPath(String idShort) implements GenericPath { + } + + public record IndexPath(int index) implements GenericPath { + } +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelBackendConfiguration.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelBackendConfiguration.java new file mode 100644 index 000000000..159e1cd12 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelBackendConfiguration.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.common.mongocore.MappingEntry; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; + +/** + * Configuration for the SubmodelBackend with MongoDB backend + * + * @author mateusmolina + * + */ +@Configuration +@ConditionalOnExpression("'${basyx.backend}'.equals('MongoDB')") +@EnableMongoRepositories(basePackages = "org.eclipse.digitaltwin.basyx.submodelservice.backend") +public class MongoDbSubmodelBackendConfiguration { + + static final String COLLECTION_NAME_FIELD = "basyx.submodelservice.mongodb.collectionName"; + static final String DEFAULT_COLLECTION_NAME = "submodel-service"; + + @Bean + @ConditionalOnMissingBean + MappingEntry submodelMappingEntry(@Value("${" + COLLECTION_NAME_FIELD + ":" + DEFAULT_COLLECTION_NAME + "}") String collectionName) { + return MappingEntry.of(collectionName, Submodel.class); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelOperations.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelOperations.java new file mode 100644 index 000000000..0cb0d959b --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoDbSubmodelOperations.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bson.Document; +import org.eclipse.digitaltwin.aas4j.v3.model.Entity; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.SubmodelOperations; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.MongoFilterBuilder.MongoFilterResult; +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; +import org.springframework.data.mongodb.core.BulkOperations; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import com.mongodb.client.result.UpdateResult; + +/** + * MongoDb implementation of the {@link SubmodelOperations} + * + * @author mateusmolina + */ +public class MongoDbSubmodelOperations implements SubmodelOperations { + + private static final String SUBMODEL_ELEMENTS_KEY = "submodelElements"; + + private final MongoOperations mongoOperations; + private final String collectionName; + + public MongoDbSubmodelOperations(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + this.collectionName = mongoOperations.getCollectionName(Submodel.class); + } + + @Override + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { + List ops = new ArrayList<>(); + + ops.add(Aggregation.match(Criteria.where("_id").is(submodelId))); + + if (pInfo.getCursor() != null && !pInfo.getCursor().isEmpty()) { + Document addCursorIndex = new Document("$addFields", + new Document("cursorIndex", new Document("$cond", Arrays.asList(new Document("$eq", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY + ".idShort", pInfo.getCursor())), -1)), 0, + new Document("$add", Arrays.asList(new Document("$indexOfArray", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY + ".idShort", pInfo.getCursor())), 1)))))); + ops.add(context -> addCursorIndex); + + int limit = (pInfo.getLimit() != null && pInfo.getLimit() > 0) ? pInfo.getLimit() : Integer.MAX_VALUE; + + Document projectSlice = new Document("$project", new Document(SUBMODEL_ELEMENTS_KEY, new Document("$slice", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY, "$cursorIndex", limit)))); + ops.add(context -> projectSlice); + } else { + if (pInfo.getLimit() != null && pInfo.getLimit() > 0) { + Document projectSlice = new Document("$project", new Document(SUBMODEL_ELEMENTS_KEY, new Document("$slice", Arrays.asList("$" + SUBMODEL_ELEMENTS_KEY, 0, pInfo.getLimit())))); + ops.add(context -> projectSlice); + } + } + + ops.add(Aggregation.unwind(SUBMODEL_ELEMENTS_KEY)); + + ops.add(Aggregation.replaceRoot("$" + SUBMODEL_ELEMENTS_KEY)); + + Aggregation aggregation = Aggregation.newAggregation(ops); + AggregationResults results = mongoOperations.aggregate(aggregation, collectionName, SubmodelElement.class); + List elements = results.getMappedResults(); + + if (elements.isEmpty() && !existsSubmodel(submodelId)) { + throw new ElementDoesNotExistException(submodelId); + } + + String nextCursor = null; + if (!elements.isEmpty()) + nextCursor = elements.get(elements.size() - 1).getIdShort(); + + return new CursorResult<>(nextCursor, elements); + } + + @Override + public SubmodelElement getSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + List ops = MongoFilterBuilder.buildAggregationOperations(submodelId, idShortPath); + Aggregation aggregation = Aggregation.newAggregation(ops); + + try { + AggregationResults results = mongoOperations.aggregate(aggregation, collectionName, SubmodelElement.class); + SubmodelElement element = results.getUniqueMappedResult(); + if (element == null) { + throw new ElementDoesNotExistException(idShortPath); + } + return element; + } catch (Exception e) { + throw new ElementDoesNotExistException(idShortPath); + } + } + + @Override + public void createSubmodelElement(String submodelId, SubmodelElement smElement) { + Query query = new Query(Criteria.where("_id").is(submodelId)); + Update update = new Update().push(SUBMODEL_ELEMENTS_KEY, smElement); + UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); + if (result.getModifiedCount() == 0 && !existsSubmodel(submodelId)) { + throw new ElementDoesNotExistException(submodelId); + } + } + + @Override + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + SubmodelElement parentSme = getSubmodelElement(submodelId, idShortPath); + + if (parentSme instanceof SubmodelElementList list) { + List submodelElements = list.getValue(); + submodelElements.add(submodelElement); + } else if (parentSme instanceof SubmodelElementCollection collection) { + List submodelElements = collection.getValue(); + submodelElements.add(submodelElement); + } else if (parentSme instanceof Entity entity) { + List submodelElements = entity.getStatements(); + submodelElements.add(submodelElement); + } + + updateSubmodelElement(submodelId, idShortPath, parentSme); + } + + @Override + public void updateSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + MongoFilterResult filterResult = MongoFilterBuilder.parse(idShortPath); + + Query query = new Query(Criteria.where("_id").is(submodelId)); + Update update = new Update().set(filterResult.key(), submodelElement); + + filterResult.filters().forEach(update::filterArray); + + UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); + + if (result.getModifiedCount() == 0) { + if (!existsSubmodel(submodelId)) + throw new ElementDoesNotExistException(submodelId); + + throw new ElementDoesNotExistException(idShortPath); + } + } + + @Override + public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + MongoFilterResult filterResult = MongoFilterBuilder.parse(idShortPath); + + Query query = new Query(Criteria.where("_id").is(submodelId)); + Update update = new Update().unset(filterResult.key()); + + filterResult.filters().forEach(update::filterArray); + + UpdateResult result = mongoOperations.updateFirst(query, update, collectionName); + if (result.getModifiedCount() == 0) { + if (!existsSubmodel(submodelId)) + throw new ElementDoesNotExistException(submodelId); + throw new ElementDoesNotExistException(idShortPath); + } + } + + @Override + public void patchSubmodelElements(String submodelId, List submodelElementList) { + BulkOperations bulkOps = mongoOperations.bulkOps(BulkOperations.BulkMode.ORDERED, collectionName); + + for (SubmodelElement element : submodelElementList) { + Query query = new Query(Criteria.where("_id").is(submodelId).and(SUBMODEL_ELEMENTS_KEY + ".idShort").is(element.getIdShort())); + Update update = new Update().set(SUBMODEL_ELEMENTS_KEY + ".$", element); + bulkOps.updateOne(query, update); + } + + bulkOps.execute(); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + return SubmodelElementValueMapperFactory.create(getSubmodelElement(submodelId, smeIdShort)).getValue(); + } + + @Override + public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { + SubmodelElement submodelElement = getSubmodelElement(submodelId, smeIdShort); + ValueMapper valueMapper = SubmodelElementValueMapperFactory.create(submodelElement); + + valueMapper.setValue(value); + + updateSubmodelElement(submodelId, smeIdShort, submodelElement); + } + + private boolean existsSubmodel(String submodelId) { + return mongoOperations.exists(new Query(Criteria.where("_id").is(submodelId)), collectionName); + } + +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoFilterBuilder.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoFilterBuilder.java new file mode 100644 index 000000000..80be137be --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/MongoFilterBuilder.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import org.eclipse.digitaltwin.basyx.submodelservice.backend.IdShortPathParser.GenericPath; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.IdShortPathParser.IdShortPath; +import org.eclipse.digitaltwin.basyx.submodelservice.backend.IdShortPathParser.IndexPath; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.aggregation.AggregationOperation; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull; +import org.springframework.data.mongodb.core.aggregation.Fields; +import org.springframework.data.mongodb.core.aggregation.UnwindOperation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; +import org.springframework.lang.NonNull; + +/** + * Builds MongoDB filters and aggregation operations for submodel elements. + * + * @author mateusmolina + * + */ +public final class MongoFilterBuilder{ + static final String KEY_VALUE = "value"; + static final String KEY_STATEMENTS = "statements"; + static final String KEY_SUBMODEL_ELEMENTS = "submodelElements"; + static final String KEY_ID_SHORT = "idShort"; + + public static MongoFilterResult parse(@NonNull String idShortPath) { + Deque paths = IdShortPathParser.parse(idShortPath); + + assert !paths.isEmpty(); + + StringBuilder updateKey = new StringBuilder(); + List filterArray = new ArrayList<>(); + int filterCounter = 0; + + updateKey.append(KEY_SUBMODEL_ELEMENTS); + + GenericPath rootPath = paths.pop(); + + assert rootPath instanceof IdShortPath; + + String placeholder = "elem" + filterCounter++; + updateKey.append(".$[").append(placeholder).append("]"); + filterArray.add(buildCriteria(joinKeys(placeholder, KEY_ID_SHORT), ((IdShortPath) rootPath).idShort())); + + while (!paths.isEmpty()) { + GenericPath segment = paths.pop(); + updateKey.append(".").append(KEY_VALUE); + if (segment instanceof IdShortPath idPath) { + placeholder = "elem" + filterCounter++; + updateKey.append(".$[").append(placeholder).append("]"); + filterArray.add(buildCriteria(joinKeys(placeholder, KEY_ID_SHORT), idPath.idShort())); + } else if (segment instanceof IndexPath ixPath) { + updateKey.append(".").append(ixPath.index()); + } + } + return new MongoFilterResult(updateKey.toString(), filterArray); + } + + public static List buildAggregationOperations(@NonNull String submodelId, @NonNull String idShortPath) { + + Deque paths = IdShortPathParser.parse(idShortPath); + List ops = new ArrayList<>(); + + ops.add(Aggregation.match(Criteria.where("_id").is(submodelId))); + ops.add(Aggregation.unwind(KEY_SUBMODEL_ELEMENTS)); + + GenericPath currentPath = paths.pop(); + assert currentPath instanceof IdShortPath; + + ops.add(Aggregation.match(Criteria.where(joinKeys(KEY_SUBMODEL_ELEMENTS, KEY_ID_SHORT)).is(((IdShortPath) currentPath).idShort()))); + ops.add(Aggregation.replaceRoot("$"+KEY_SUBMODEL_ELEMENTS)); + + while (!paths.isEmpty()) { + currentPath = paths.pop(); + ops.add(new UnwindOperation(Fields.field("$"+KEY_VALUE), true)); + ops.add(new UnwindOperation(Fields.field("$"+KEY_STATEMENTS), true)); + if (currentPath instanceof IdShortPath idPath) { + Criteria inValue = Criteria.where(joinKeys(KEY_VALUE, KEY_ID_SHORT)).is(idPath.idShort()); + Criteria inStatements = Criteria.where(joinKeys(KEY_STATEMENTS, KEY_ID_SHORT)).is(idPath.idShort()); + ops.add(Aggregation.match(new Criteria().orOperator(inValue, inStatements))); + } else if (currentPath instanceof IndexPath ixPath) { + ops.add(Aggregation.skip(ixPath.index())); + ops.add(Aggregation.limit(1)); + } + ops.add(Aggregation.replaceRoot(IfNull.ifNull("$"+KEY_VALUE).then("$"+KEY_STATEMENTS))); + + } + + return ops; + } + + static CriteriaDefinition buildCriteria(String key, String value) { + return Criteria.where(key).is(value); + } + + static String joinKeys(String... keys) { + StringBuilder sb = new StringBuilder(); + for (String key : keys) { + sb.append(key).append('.'); + } + return sb.substring(0, sb.length() - 1); + } + + public record MongoFilterResult(String key, List filters) { + } +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/resources/META-INF/spring.factories b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..300cb86c5 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.eclipse.digitaltwin.basyx.submodelservice.backend.SubmodelOperations=org.eclipse.digitaltwin.basyx.submodelservice.backend.MongoDbSubmodelOperations diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummyMongoDbSubmodelServiceComponent.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummyMongoDbSubmodelServiceComponent.java new file mode 100644 index 000000000..38587defc --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/DummyMongoDbSubmodelServiceComponent.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "org.eclipse.digitaltwin.basyx") +public class DummyMongoDbSubmodelServiceComponent { + + public static void main(String[] args) { + SpringApplication.run(DummyMongoDbSubmodelServiceComponent.class, args); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestMongoDBSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestMongoDBSubmodelService.java index 66198a9b4..b9e730599 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestMongoDBSubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelservice/TestMongoDBSubmodelService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,22 +23,20 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.submodelservice; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.basyx.common.mongocore.BasyxMongoMappingContext; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotSupportedException; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; -import org.eclipse.digitaltwin.basyx.core.filerepository.MongoDBFileRepository; -import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.gridfs.GridFsTemplate; - -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; +import org.springframework.test.context.junit4.SpringRunner; /** * Test for mongoDb submodel service backend @@ -46,37 +44,37 @@ * @author zhangzai, mateusmolina * */ +@SpringBootTest +@RunWith(SpringRunner.class) public class TestMongoDBSubmodelService extends SubmodelServiceSuite { - private final String COLLECTION = "submodelTestCollection"; - private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; - private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); - private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); - private final GridFsTemplate GRIDFS_TEMPLATE = new GridFsTemplate(TEMPLATE.getMongoDatabaseFactory(), TEMPLATE.getConverter()); - private FileRepository fileRepository = new MongoDBFileRepository(GRIDFS_TEMPLATE); - @After + static final String TEST_COLLECTION = "submodelServiceTestCollection"; + + @Autowired + private FileRepository fileRepository; + + @Autowired + private SubmodelServiceFactory submodelServiceFactory; + + @Autowired + private MongoTemplate mongoTemplate; + + @Before public void clear() { - MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); + MongoDBUtilities.clearCollection(mongoTemplate, TEST_COLLECTION); } @Override protected SubmodelService getSubmodelService(Submodel submodel) { - BasyxMongoMappingContext mappingContext = new BasyxMongoMappingContext(); - return new MongoDBSubmodelServiceFactory(fileRepository, new SingleSubmodelMongoDBBackendProvider(mappingContext, COLLECTION, TEMPLATE)).create(submodel); + return submodelServiceFactory.create(submodel); } @Override - @Test(expected = FeatureNotSupportedException.class) + @Test(expected = ElementDoesNotExistException.class) public void invokeOperation() { super.invokeOperation(); } - @Override - @Test(expected = FeatureNotSupportedException.class) - public void invokeNonOperation() { - super.invokeNonOperation(); - } - @Override protected boolean fileExistsInStorage(String fileValue) { return fileRepository.exists(fileValue); diff --git a/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/resources/application.properties b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/resources/application.properties new file mode 100644 index 000000000..cc467e7c0 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend-mongodb/src/test/resources/application.properties @@ -0,0 +1,10 @@ +basyx.backend = MongoDB + +basyx.submodelservice.mongodb.collectionName= submodelServiceTestCollection + +spring.data.mongodb.host=127.0.0.1 +spring.data.mongodb.port=27017 +spring.data.mongodb.database=aas-env +spring.data.mongodb.authentication-database=admin +spring.data.mongodb.username=mongoAdmin +spring.data.mongodb.password=mongoPassword \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-backend/pom.xml b/basyx.submodelservice/basyx.submodelservice-backend/pom.xml new file mode 100644 index 000000000..137edfa0e --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice + ${revision} + + + basyx.submodelservice-backend + BaSyx submodelservice-backend + BaSyx SubmodelService Backend + + + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-core + + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter + + + org.springframework.data + spring-data-commons + + + + \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelService.java new file mode 100644 index 000000000..b56e7dfe1 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelService.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +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.exceptions.NotInvokableException; +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.operation.InvokableOperation; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.springframework.lang.NonNull; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +/** + * Default {@link SubmodelService} based on {@link SubmodelBackend} + * + * @author mateusmolina + */ +public class CrudSubmodelService implements SubmodelService { + + private final SubmodelBackend backend; + private final String submodelId; + private final SubmodelFileOperations submodelFileOperations; + + public CrudSubmodelService(SubmodelBackend submodelRepositoryBackend, FileRepository fileRepository, @NonNull Submodel submodel) { + this(submodelRepositoryBackend, fileRepository, submodel.getId()); + hostSubmodel(submodel); + } + + public CrudSubmodelService(SubmodelBackend submodelRepositoryBackend, FileRepository fileRepository, @NonNull String submodelId) { + this.backend = submodelRepositoryBackend; + this.submodelId = submodelId; + this.submodelFileOperations = new SubmodelFileOperations(fileRepository, submodelRepositoryBackend); + } + + @Override + public Submodel getSubmodel() { + return this.backend.findById(this.submodelId).orElseThrow(ElementDoesNotExistException::new); + } + + @Override + public CursorResult> getSubmodelElements(PaginationInfo pInfo) { + return backend.getSubmodelElements(submodelId, pInfo); + } + + @Override + public SubmodelElement getSubmodelElement(String idShortPath) throws ElementDoesNotExistException { + return backend.getSubmodelElement(submodelId, idShortPath); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String idShortPath) throws ElementDoesNotExistException { + return backend.getSubmodelElementValue(submodelId, idShortPath); + } + + @Override + public void setSubmodelElementValue(String idShortPath, SubmodelElementValue value) throws ElementDoesNotExistException { + backend.setSubmodelElementValue(submodelId, idShortPath, value); + } + + @Override + public void createSubmodelElement(SubmodelElement submodelElement) { + backend.createSubmodelElement(submodelId, submodelElement); + } + + @Override + public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + backend.createSubmodelElement(submodelId, idShortPath, submodelElement); + } + + @Override + public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + deleteAssociatedFileIfAny(idShortPath); + + backend.updateSubmodelElement(submodelId, idShortPath, submodelElement); + } + + @Override + public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { + deleteAssociatedFileIfAny(idShortPath); + + backend.deleteSubmodelElement(submodelId, idShortPath); + } + + @Override + public void patchSubmodelElements(List submodelElementList) { + backend.patchSubmodelElements(submodelId, submodelElementList); + } + + @Override + public OperationVariable[] invokeOperation(String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { + SubmodelElement sme = getSubmodelElement(idShortPath); + + if (!(sme instanceof InvokableOperation operation)) + throw new NotInvokableException(idShortPath); + + return operation.invoke(input); + } + + @Override + public File getFileByPath(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + return submodelFileOperations.getFile(submodelId, idShortPath); + } + + @Override + public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + submodelFileOperations.setFileValue(submodelId, idShortPath, fileName, inputStream); + } + + @Override + public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + submodelFileOperations.deleteFileValue(submodelId, idShortPath); + } + + @Override + public InputStream getFileByFilePath(String filePath) { + return submodelFileOperations.getInputStream(filePath); + } + + private void hostSubmodel(Submodel submodel) { + this.backend.save(submodel); + } + + private void deleteAssociatedFileIfAny(String idShortPath) { + try { + submodelFileOperations.deleteFileValue(submodelId, idShortPath); + } catch (Exception e) { + } + } +} 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/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelServiceFactory.java similarity index 59% rename from basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java rename to basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelServiceFactory.java index 2ae48ef10..b0a16df0a 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelServiceFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/CrudSubmodelServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,33 +23,40 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelservice; +package org.eclipse.digitaltwin.basyx.submodelservice.backend; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelService; +import org.eclipse.digitaltwin.basyx.submodelservice.SubmodelServiceFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; /** - * SubmodelService factory returning an in-memory backend SubmodelService + * Factory component for the {@link CrudSubmodelService} * - * @author schnicke, mateusmolina - * + * @author 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, fileRepository); - } +@ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${basyx.backend:}')") +public class CrudSubmodelServiceFactory implements SubmodelServiceFactory{ + + private final SubmodelBackend backend; + private final FileRepository fileRepository; + + public CrudSubmodelServiceFactory(SubmodelBackend backend, FileRepository fileRepository) { + this.backend = backend; + this.fileRepository = fileRepository; + } + + @Override + public SubmodelService create(Submodel submodel) { + return new CrudSubmodelService(backend, fileRepository, submodel); + } + + @Override + public SubmodelService create(String submodelId) { + return new CrudSubmodelService(backend, fileRepository, submodelId); + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelBackend.java similarity index 80% rename from basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java rename to basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelBackend.java index ae3edc9db..5ea928b0e 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/SubmodelBackendProvider.java +++ b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelBackend.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,18 +23,18 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelrepository.backend; +package org.eclipse.digitaltwin.basyx.submodelservice.backend; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; /** - * Backend provider for the {@link Submodel} + * Submodel backend based on {@link CrudRepository} * - * @author mateusmolina, despen, danish + * @author mateusmolina */ -public interface SubmodelBackendProvider { - - public CrudRepository getCrudRepository(); - +@Repository +public interface SubmodelBackend extends CrudRepository, SubmodelOperations { + } diff --git a/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelFileOperations.java b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelFileOperations.java new file mode 100644 index 000000000..d6f8572b5 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelFileOperations.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.digitaltwin.aas4j.v3.model.File; +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.exceptions.FileHandlingException; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; +import org.eclipse.digitaltwin.basyx.http.Base64UrlEncodedIdentifier; +import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; + +/** + * Collection of methods that use the {@link FileRepository} to store and retrieve files. + * + * @author mateusmolina + */ +public class SubmodelFileOperations { + private final FileRepository fileRepository; + private final SubmodelOperations submodelOperations; + + public SubmodelFileOperations(FileRepository fileRepository, SubmodelOperations operations) { + this.fileRepository = fileRepository; + this.submodelOperations = operations; + } + + public java.io.File getFile(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + SubmodelElement submodelElement = submodelOperations.getSubmodelElement(submodelId, idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + String filePath = getFilePath(fileSmElement); + + InputStream fileContent = getFileInputStream(filePath); + + return createFile(filePath, fileContent); + } + + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + SubmodelElement submodelElement = submodelOperations.getSubmodelElement(submodelId, idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSmElement = (File) submodelElement; + + if (fileRepository.exists(fileSmElement.getValue())) + fileRepository.delete(fileSmElement.getValue()); + + String uniqueFileName = createUniqueFileName(submodelId, idShortPath, fileName); + + FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); + + if (fileRepository.exists(fileMetadata.getFileName())) + fileRepository.delete(fileMetadata.getFileName()); + + String filePath = fileRepository.save(fileMetadata); + + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + + submodelOperations.setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + SubmodelElement submodelElement = submodelOperations.getSubmodelElement(submodelId, idShortPath); + + throwIfSmElementIsNotAFile(submodelElement); + + File fileSubmodelElement = (File) submodelElement; + String filePath = fileSubmodelElement.getValue(); + + fileRepository.delete(filePath); + + FileBlobValue fileValue = new FileBlobValue(" ", " "); + + submodelOperations.setSubmodelElementValue(submodelId, idShortPath, fileValue); + } + + public InputStream getInputStream(String filePath) throws FileDoesNotExistException{ + return fileRepository.find(filePath); + } + + private static 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 static 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 static String getFilePath(File fileSubmodelElement) { + return fileSubmodelElement.getValue(); + } + + private static String createUniqueFileName(String submodelId, String idShortPath, String fileName) { + return Base64UrlEncodedIdentifier.encodeIdentifier(submodelId) + "-" + idShortPath.replace("/", "-") + "-" + fileName; + } + + private static void throwIfSmElementIsNotAFile(SubmodelElement submodelElement) { + + if (!isFileSubmodelElement(submodelElement)) + throw new ElementNotAFileException(submodelElement.getIdShort()); + } + + private static 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/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelOperations.java b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelOperations.java new file mode 100644 index 000000000..39f2269c2 --- /dev/null +++ b/basyx.submodelservice/basyx.submodelservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/backend/SubmodelOperations.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (C) 2025 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.submodelservice.backend; + +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.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; + +import java.util.List; + +/** + * This interface provides backend-level operations for managing + * {@link Submodel}s + * + * @author mateusmolina + */ +public interface SubmodelOperations { + + /** + * Retrieves all Submodel Elements for the given Submodel. + * + * @param submodelId the identifier of the Submodel + * @param pInfo the pagination information + * @return a {@code CursorResult} containing a list of Submodel Elements + * @throws ElementDoesNotExistException if the Submodel with the specified {@code submodelId} does not exist + */ + CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException; + + /** + * Retrieves a specific Submodel Element from the given Submodel. + * + * @param submodelId the identifier of the Submodel + * @param smeIdShortPath the short path of the Submodel Element + * @return the requested Submodel Element + * @throws ElementDoesNotExistException if the Submodel or Submodel Element does not exist + */ + SubmodelElement getSubmodelElement(String submodelId, String smeIdShortPath) throws ElementDoesNotExistException; + + /** + * Retrieves the value of a specific Submodel Element. + * + * @param submodelId the identifier of the Submodel + * @param smeIdShort the short identifier of the Submodel Element + * @return the value of the specified Submodel Element + * @throws ElementDoesNotExistException if the Submodel or Submodel Element does not exist + */ + SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException; + + /** + * Sets the value of a specific Submodel Element. + * + * @param submodelId the identifier of the Submodel + * @param smeIdShort the short identifier of the Submodel Element + * @param value the new value to be set + * @throws ElementDoesNotExistException if the Submodel or Submodel Element does not exist + */ + void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException; + + /** + * Creates a new Submodel Element in the specified Submodel. + * + * @param submodelId the identifier of the Submodel + * @param smElement the Submodel Element to be created + */ + void createSubmodelElement(String submodelId, SubmodelElement smElement); + + /** + * Creates a new Submodel Element at the specified path within a Submodel. + * + * @param submodelId the identifier of the Submodel + * @param idShortPath the short path where the Submodel Element should be created + * @param smElement the Submodel Element to be created + * @throws ElementDoesNotExistException if the Submodel does not exist + */ + void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException; + + /** + * Updates an existing Submodel Element. + * + * @param submodelId the identifier of the Submodel + * @param idShortPath the short path of the Submodel Element to be updated + * @param submodelElement the new Submodel Element data + * @throws ElementDoesNotExistException if the Submodel or Submodel Element does not exist + */ + void updateSubmodelElement(String submodelId, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException; + + /** + * Deletes a specific Submodel Element. + * + * @param submodelId the identifier of the Submodel + * @param idShortPath the short path of the Submodel Element to be deleted + * @throws ElementDoesNotExistException if the Submodel or Submodel Element does not exist + */ + void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException; + + /** + * Applies partial modifications to multiple Submodel Elements. + * + * @param submodelId the identifier of the Submodel + * @param submodelElementList the list of Submodel Elements with updates + */ + void patchSubmodelElements(String submodelId, List submodelElementList); +} diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/SubmodelElementValueJsonDeserializer.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/SubmodelElementValueJsonDeserializer.java deleted file mode 100644 index 6c2f9edbb..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/SubmodelElementValueJsonDeserializer.java +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization; - -import java.io.IOException; -import org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory.SubmodelElementValueDeserializationFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Deserializes a SubmodelElementValue as described in DotAAS Part 2 - * - * @author danish - * - */ -public class SubmodelElementValueJsonDeserializer extends JsonDeserializer { - - private SubmodelElementValueDeserializationFactory submodelElementValueDeserializationFactory = new SubmodelElementValueDeserializationFactory(); - - @Override - public SubmodelElementValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - try { - ObjectMapper mapper = (ObjectMapper) p.getCodec(); - JsonNode node = mapper.readTree(p); - - return submodelElementValueDeserializationFactory.create(mapper, node); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/ValueOnlyJsonDeserializer.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/ValueOnlyJsonDeserializer.java deleted file mode 100644 index f051c36ed..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/ValueOnlyJsonDeserializer.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization; - -import java.io.IOException; -import org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory.SubmodelElementValueDeserializationFactory; -import org.eclipse.digitaltwin.basyx.submodelservice.value.ValueOnly; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Deserializes the ValueOnly as described in DotAAS Part 2 - * - * @author danish - * - */ -public class ValueOnlyJsonDeserializer extends JsonDeserializer { - - private SubmodelElementValueDeserializationFactory submodelElementValueDeserializationFactory = new SubmodelElementValueDeserializationFactory(); - - @Override - public ValueOnly deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - try { - ObjectMapper mapper = (ObjectMapper) p.getCodec(); - JsonNode node = mapper.readTree(p); - - String idShort = node.fieldNames().next(); - - return new ValueOnly(idShort, submodelElementValueDeserializationFactory.create(mapper, node.get(idShort))); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/MultiLanguagePropertyValueDeserializationFactory.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/MultiLanguagePropertyValueDeserializationFactory.java deleted file mode 100644 index eccc8fe2e..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/MultiLanguagePropertyValueDeserializationFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.eclipse.digitaltwin.aas4j.v3.model.LangStringTextType; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; -import org.eclipse.digitaltwin.basyx.submodelservice.value.MultiLanguagePropertyValue; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * Factory class to create deserialized {@link MultiLanguagePropertyValue} based on - * the content - * - * @author danish - * - */ -public class MultiLanguagePropertyValueDeserializationFactory { - - private JsonNode node; - - public MultiLanguagePropertyValueDeserializationFactory(JsonNode node) { - this.node = node; - } - - /** - * Deserializes the corresponding {@link MultiLanguagePropertyValue} based on the - * JSON content - * - * @return {@link MultiLanguagePropertyValue} - * - */ - public MultiLanguagePropertyValue create() { - List langStrings = createLangStrings(node); - - return new MultiLanguagePropertyValue(langStrings); - } - - private static List createLangStrings(JsonNode node) { - List langStrings = new ArrayList<>(); - - for (JsonNode element : node) { - langStrings.add(createLangString(element)); - } - - return langStrings; - } - - private static DefaultLangStringTextType createLangString(JsonNode arrayNode) { - Iterator fieldNames = arrayNode.fieldNames(); - String language = fieldNames.next(); - String text = arrayNode.get(language).asText(); - - return new DefaultLangStringTextType.Builder().text(text).language(language).build(); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementCollectionValueDeserializationFactory.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementCollectionValueDeserializationFactory.java deleted file mode 100644 index 7235d8579..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementCollectionValueDeserializationFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory; - -import java.util.List; - -import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementCollectionValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.ValueOnly; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Factory class to create deserialized {@link SubmodelElementCollectionValue} based on - * the content - * - * @author danish - * - */ -public class SubmodelElementCollectionValueDeserializationFactory { - - private ObjectMapper objectMapper; - private JsonNode node; - - public SubmodelElementCollectionValueDeserializationFactory(ObjectMapper objectMapper, JsonNode node) { - this.objectMapper = objectMapper; - this.node = node; - } - - /** - * Deserializes the corresponding {@link SubmodelElementCollectionValue} based on the - * JSON content - * - * @return {@link SubmodelElementCollectionValue} - * - */ - public SubmodelElementCollectionValue create() throws JsonProcessingException { - List valueOnlies = objectMapper.readValue(node.toString(), new TypeReference>() {}); - - return new SubmodelElementCollectionValue(valueOnlies); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementListValueDeserializationFactory.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementListValueDeserializationFactory.java deleted file mode 100644 index 2d623ab93..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementListValueDeserializationFactory.java +++ /dev/null @@ -1,68 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory; - -import java.util.List; - -import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementListValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Factory class to create deserialized {@link SubmodelElementListValue} based on - * the content - * - * @author danish - * - */ -public class SubmodelElementListValueDeserializationFactory { - - private ObjectMapper objectMapper; - private JsonNode node; - - public SubmodelElementListValueDeserializationFactory(ObjectMapper objectMapper, JsonNode node) { - this.objectMapper = objectMapper; - this.node = node; - } - - /** - * Deserializes the corresponding {@link SubmodelElementListValue} based on the - * JSON content - * - * @return {@link SubmodelElementListValue} - * - */ - public SubmodelElementListValue create() throws JsonProcessingException { - List submodelElementValues = objectMapper.readValue(node.toString(), new TypeReference>() {}); - - return new SubmodelElementListValue(submodelElementValues); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementValueDeserializationFactory.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementValueDeserializationFactory.java deleted file mode 100644 index af311468c..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/factory/SubmodelElementValueDeserializationFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.factory; - -import org.eclipse.digitaltwin.basyx.submodelservice.value.AnnotatedRelationshipElementValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.EntityValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.PropertyValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.RangeValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.ReferenceElementValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.ReferenceValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.RelationshipElementValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import static org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.util.SubmodelElementValueDeserializationUtil.*; - -import org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.exception.SubmodelElementValueDeserializationException; - -/** - * Factory class to create deserialized {@link SubmodelElementValue} based on - * the content - * - * @author danish - * - */ -public class SubmodelElementValueDeserializationFactory { - - /** - * Deserializes the corresponding {@link SubmodelElementValue} based on the - * JSON content - * - * @return SubmodelELementValue - * - * @throws JsonProcessingException - * @throws SubmodelElementValueDeserializationException - */ - public SubmodelElementValue create(ObjectMapper mapper, JsonNode node) throws JsonProcessingException { - if (isTypeOfRangeValue(node)) { - return mapper.convertValue(node, RangeValue.class); - } else if (isTypeOfMultiLanguagePropertyValue(node)) { - return new MultiLanguagePropertyValueDeserializationFactory(node).create(); - } else if (isTypeOfFileBlobValue(node)) { - return mapper.convertValue(node, FileBlobValue.class); - } else if (isTypeOfPropertyValue(node)) { - return mapper.convertValue(node, PropertyValue.class); - } else if (isTypeOfEntityValue(node)) { - return mapper.convertValue(node, EntityValue.class); - } else if (isTypeOfReferenceElementValue(node)) { - return new ReferenceElementValue(mapper.convertValue(node, ReferenceValue.class)); - } else if (isTypeOfRelationshipElementValue(node)) { - return mapper.convertValue(node, RelationshipElementValue.class); - } else if (isTypeOfAnnotatedRelationshipElementValue(node)) { - return mapper.convertValue(node, AnnotatedRelationshipElementValue.class); - } else if (isTypeOfSubmodelElementCollectionValue(node)) { - return new SubmodelElementCollectionValueDeserializationFactory(mapper, node).create(); - } else if (isTypeOfSubmodelElementListValue(node)) { - return new SubmodelElementListValueDeserializationFactory(mapper, node).create(); - } - - throw new SubmodelElementValueDeserializationException(); - } -} diff --git a/basyx.submodelservice/basyx.submodelservice-core/deserialization/util/SubmodelElementValueDeserializationUtil.java b/basyx.submodelservice/basyx.submodelservice-core/deserialization/util/SubmodelElementValueDeserializationUtil.java deleted file mode 100644 index d4ebddf0b..000000000 --- a/basyx.submodelservice/basyx.submodelservice-core/deserialization/util/SubmodelElementValueDeserializationUtil.java +++ /dev/null @@ -1,199 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.submodelrepository.http.deserialization.util; - -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.util.Optional; -import java.util.stream.Stream; - -import org.eclipse.digitaltwin.basyx.submodelservice.value.AnnotatedRelationshipElementValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.EntityValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.FileBlobValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.RangeValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.ReferenceValue; -import org.eclipse.digitaltwin.basyx.submodelservice.value.RelationshipElementValue; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * An utility class to check and validate the type for Deserialization - * - * @author danish - * - */ -public class SubmodelElementValueDeserializationUtil { - - private SubmodelElementValueDeserializationUtil() { - throw new IllegalStateException("Utility class"); - } - - public static boolean isTypeOfPropertyValue(JsonNode node) { - return node.isValueNode() && node.isTextual(); - } - - public static boolean isTypeOfFileBlobValue(JsonNode node) { - return isTypeOf(FileBlobValue.class, node); - } - - public static boolean isTypeOfRangeValue(JsonNode node) { - return isTypeOf(RangeValue.class, node); - } - - public static boolean isTypeOfMultiLanguagePropertyValue(JsonNode node) { - return node.isArray() && hasStructureOfMultiLanguagePropertyValue(node); - } - - public static boolean isTypeOfEntityValue(JsonNode node) { - return isTypeOf(EntityValue.class, node); - } - - public static boolean isTypeOfReferenceElementValue(JsonNode node) { - return isTypeOf(ReferenceValue.class, node); - } - - public static boolean isTypeOfRelationshipElementValue(JsonNode node) { - return isTypeOf(RelationshipElementValue.class, node); - } - - public static boolean isTypeOfAnnotatedRelationshipElementValue(JsonNode node) { - return isTypeOf(AnnotatedRelationshipElementValue.class, node); - } - - public static boolean isTypeOfSubmodelElementCollectionValue(JsonNode node) { - return node.isArray() && hasStructureOfSubmodelElementCollectionValue(node); - } - - public static boolean isTypeOfSubmodelElementListValue(JsonNode node) { - return node.isArray() && hasStructureOfSubmodelElementListValue(node); - } - - private static boolean isTypeOf(Class clazz, JsonNode node) { - Field[] superClassFields = clazz.getSuperclass().getDeclaredFields(); - Field[] fields = clazz.getDeclaredFields(); - - if (superClassFields.length != 0) - fields = merge(superClassFields, fields); - - int countOfOptionalAttributes = getOptionalAttributesCount(fields); - int totalCountOfAttributes = fields.length; - - if (!isSizeOfAttributesValid(node.size(), countOfOptionalAttributes, totalCountOfAttributes)) - return false; - - for (Field field : fields) { - if (!isOptionalAttribute(field) && !node.has(field.getName())) - return false; - } - - return true; - } - - private static boolean isSizeOfAttributesValid(int nodeSize, int countOfOptionalAttributes, - int totalCountOfAttributes) { - if (countOfOptionalAttributes == 0) - return nodeSize == totalCountOfAttributes; - - return nodeSize >= (totalCountOfAttributes - countOfOptionalAttributes); - } - - private static int getOptionalAttributesCount(Field[] fields) { - int count = 0; - - for (Field field : fields) { - if (isOptionalAttribute(field)) - count++; - } - - return count; - } - - private static boolean isOptionalAttribute(Field field) { - return Optional.class.isAssignableFrom(field.getType()); - } - - private static Field[] merge(Field[] fieldArray1, Field[] fieldArray2) { - int lengthFieldArray1 = Array.getLength(fieldArray1); - int lengthFieldArray2 = Array.getLength(fieldArray2); - Field[] result = (Field[]) Array.newInstance(Field.class, lengthFieldArray1 + lengthFieldArray2); - System.arraycopy(fieldArray1, 0, result, 0, lengthFieldArray1); - System.arraycopy(fieldArray2, 0, result, lengthFieldArray1, lengthFieldArray2); - - return result; - } - - private static boolean hasStructureOfMultiLanguagePropertyValue(JsonNode node) { - for (JsonNode element : node) { - if (!isValidLanguagePropertyValue(element)) - return false; - } - - return true; - } - - private static boolean hasStructureOfSubmodelElementCollectionValue(JsonNode node) { - for (JsonNode element : node) { - if (!isValidValueOnly(element)) - return false; - } - - return true; - } - - private static boolean hasStructureOfSubmodelElementListValue(JsonNode node) { - for (JsonNode element : node) { - if (!isInstanceOfSubmodelElementValue(element)) - return false; - } - - return true; - } - - private static boolean isValidValueOnly(JsonNode element) { - String idShort = element.fieldNames().next(); - - return isInstanceOfSubmodelElementValue(element.get(idShort)); - } - - private static boolean isInstanceOfSubmodelElementValue(JsonNode node) { - return Stream.of(isTypeOfRangeValue(node), isTypeOfMultiLanguagePropertyValue(node), - isTypeOfFileBlobValue(node), isTypeOfPropertyValue(node), isTypeOfEntityValue(node), - isTypeOfReferenceElementValue(node), isTypeOfRelationshipElementValue(node), - isTypeOfAnnotatedRelationshipElementValue(node), isTypeOfSubmodelElementCollectionValue(node), isTypeOfSubmodelElementListValue(node)) - .anyMatch(result -> result); - } - - private static boolean isValidLanguagePropertyValue(JsonNode element) { - return element.isObject() && ((ObjectNode) element).size() == 1 && isLanguageTextOfTypeText(element); - } - - private static boolean isLanguageTextOfTypeText(JsonNode element) { - String language = element.fieldNames().next(); - - return element.get(language).isTextual(); - } - -} diff --git a/basyx.submodelservice/basyx.submodelservice-core/pom.xml b/basyx.submodelservice/basyx.submodelservice-core/pom.xml index 4183c527d..7b1fa972d 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/pom.xml +++ b/basyx.submodelservice/basyx.submodelservice-core/pom.xml @@ -35,5 +35,9 @@ commons-io test + + org.eclipse.digitaltwin.basyx + basyx.filerepository-backend + \ No newline at end of file diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceFactory.java b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceFactory.java index e314ae1f8..84e03d77a 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/SubmodelServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -23,7 +23,6 @@ * SPDX-License-Identifier: MIT ******************************************************************************/ - package org.eclipse.digitaltwin.basyx.submodelservice; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; @@ -42,4 +41,14 @@ public interface SubmodelServiceFactory { * @return */ public SubmodelService create(Submodel submodel); + + /** + * Creates a new SubmodelService containing the Submodel with the given id + * + * The submodel is assumed to be already stored in the backend + * + * @param submodelId + * @return the created SubmodelService + */ + public SubmodelService create(String submodelId); } diff --git a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/feature/DecoratedSubmodelServiceFactory.java b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/feature/DecoratedSubmodelServiceFactory.java index 794f50680..d88709d82 100644 --- a/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/feature/DecoratedSubmodelServiceFactory.java +++ b/basyx.submodelservice/basyx.submodelservice-core/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/feature/DecoratedSubmodelServiceFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 the Eclipse BaSyx Authors + * Copyright (C) 2025 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -48,4 +48,9 @@ public DecoratedSubmodelServiceFactory(SubmodelServiceFactory toDecorate, List basyx.submodelservice-core basyx.submodelservice-http + basyx.submodelservice-backend basyx.submodelservice-backend-inmemory basyx.submodelservice-backend-mongodb basyx.submodelservice-feature-mqtt diff --git a/pom.xml b/pom.xml index 3d87ac5c0..06bcbf63a 100644 --- a/pom.xml +++ b/pom.xml @@ -479,6 +479,11 @@ basyx.submodelservice-core ${revision} + + org.eclipse.digitaltwin.basyx + basyx.submodelservice-backend + ${revision} + org.eclipse.digitaltwin.basyx basyx.submodelservice-backend-inmemory @@ -573,6 +578,11 @@ basyx.aasservice-core ${revision} + + org.eclipse.digitaltwin.basyx + basyx.aasservice-backend + ${revision} + org.eclipse.digitaltwin.basyx basyx.aasservice-backend-inmemory