diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/pom.xml b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/pom.xml index f20e475d9..4fca3236b 100644 --- a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/pom.xml +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/pom.xml @@ -15,6 +15,10 @@ basyx.aasdiscoveryservice-backend + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.aasdiscoveryservice-core 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/SimpleAasDiscoveryFactory.java index 6f552bbba..601c640d1 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/SimpleAasDiscoveryFactory.java @@ -74,7 +74,7 @@ public SimpleAasDiscoveryFactory(AasDiscoveryBackendProvider aasBackendProvider, @Override public AasDiscoveryService create() { - return new CrudAasDiscovery(aasBackendProvider, aasDiscoveryName); + return new ThreadSafeAasDiscovery(new CrudAasDiscovery(aasBackendProvider, aasDiscoveryName)); } } diff --git a/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/ThreadSafeAasDiscovery.java b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/ThreadSafeAasDiscovery.java new file mode 100644 index 000000000..bf118f0c9 --- /dev/null +++ b/basyx.aasdiscoveryservice/basyx.aasdiscoveryservice-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasdiscoveryservice/backend/ThreadSafeAasDiscovery.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * 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; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.AasDiscoveryService; +import org.eclipse.digitaltwin.basyx.aasdiscoveryservice.core.model.AssetLink; +import org.eclipse.digitaltwin.basyx.common.backend.InstanceScopedThreadSafeAccess; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +/** + * A thread-safe wrapper for the {@link AasDiscoveryService} + * + * @author mateusmolina + */ +public class ThreadSafeAasDiscovery implements AasDiscoveryService { + + private final AasDiscoveryService decoratedAasDiscovery; + private final InstanceScopedThreadSafeAccess access = new InstanceScopedThreadSafeAccess(); + + public ThreadSafeAasDiscovery(AasDiscoveryService decoratedAasDiscovery) { + this.decoratedAasDiscovery = decoratedAasDiscovery; + } + + @Override + public CursorResult> getAllAssetAdministrationShellIdsByAssetLink(PaginationInfo pInfo, List assetIds) { + return decoratedAasDiscovery.getAllAssetAdministrationShellIdsByAssetLink(pInfo, assetIds); + } + + @Override + public List getAllAssetLinksById(String shellIdentifier) { + return access.read(() -> decoratedAasDiscovery.getAllAssetLinksById(shellIdentifier), shellIdentifier); + } + + @Override + public List createAllAssetLinksById(String shellIdentifier, List assetIds) { + return access.write(() -> decoratedAasDiscovery.createAllAssetLinksById(shellIdentifier, assetIds), shellIdentifier); + } + + @Override + public void deleteAllAssetLinksById(String shellIdentifier) { + access.write(() -> decoratedAasDiscovery.deleteAllAssetLinksById(shellIdentifier), shellIdentifier); + } + + @Override + public String getName() { + return decoratedAasDiscovery.getName(); + } +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerMultithreadingTestSuite.java b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerMultithreadingTestSuite.java new file mode 100644 index 000000000..8ff6b6948 --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerMultithreadingTestSuite.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.aasenvironment.client; + +import static org.junit.Assert.*; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.IntStream; + +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Key; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.junit.Test; + +public abstract class ConnectedAasManagerMultithreadingTestSuite { + static final int N_THREADS = 20; + + protected abstract AasRepository getAasRepository(); + + protected abstract ConnectedAasManager getConnectedAasManager(); + + @Test + public void testParallelSubmodelCreation() throws ExecutionException, InterruptedException { + AssetAdministrationShell shell = createShell(); + + ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS); + ConcurrentLinkedDeque createdSubmodelIds = new ConcurrentLinkedDeque<>(); + + List> futures = IntStream.range(0, N_THREADS).mapToObj(i -> executorService.submit(() -> createdSubmodelIds.add(createSubmodel(shell.getId(), i)))).toList(); + + try { + for (int i = 0; i < N_THREADS; i++) { + futures.get(i).get(); + } + } finally { + executorService.shutdown(); + } + + createdSubmodelIds.forEach(submodelId -> assertSubmodelWasCreatedAndRegistered(shell.getId(), submodelId)); + } + + void assertSubmodelWasCreatedAndRegistered(String shellId, String submodelId) { + assertEquals(submodelId, getConnectedAasManager().getSubmodelService(submodelId).getSubmodel().getId()); + assertTrue(getAasRepository().getSubmodelReferences(shellId, PaginationInfo.NO_LIMIT).getResult().stream().map(Reference::getKeys).flatMap(Collection::stream).map(Key::getValue).anyMatch(submodelId::equals)); + } + + private AssetAdministrationShell createShell() { + String id = UUID.randomUUID().toString(); + DefaultAssetAdministrationShell shell = new DefaultAssetAdministrationShell.Builder().id(id).build(); + getConnectedAasManager().createAas(shell); + return getConnectedAasManager().getAasService(id).getAAS(); + } + + private String createSubmodel(String aasId, int threadId) { + try { + String id = aasId + "-thread" + threadId; + DefaultSubmodel submodel = new DefaultSubmodel.Builder().id(id).build(); + getConnectedAasManager().createSubmodelInAas(aasId, submodel); + return id; + } catch (Exception e) { + throw new RuntimeException("Failed at thread " + threadId, e); + } + } + +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreading.java b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingInMemory.java similarity index 55% rename from basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreading.java rename to basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingInMemory.java index 8ff0ea860..001301975 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreading.java +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingInMemory.java @@ -25,60 +25,33 @@ package org.eclipse.digitaltwin.basyx.aasenvironment.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.stream.IntStream; - -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.Key; -import org.eclipse.digitaltwin.aas4j.v3.model.Reference; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; -import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.client.ConnectedSubmodelRepository; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; -public class TestConnectedAasManagerMultithreading { +public class TestConnectedAasManagerMultithreadingInMemory extends ConnectedAasManagerMultithreadingTestSuite { static final String AAS_REPOSITORY_BASE_PATH = "http://localhost:8081"; static final String SM_REPOSITORY_BASE_PATH = "http://localhost:8081"; static final String AAS_REGISTRY_BASE_PATH = "http://localhost:8050"; static final String SM_REGISTRY_BASE_PATH = "http://localhost:8060"; - static final int N_THREADS = 20; - - static ConfigurableApplicationContext appContext; - static AasRepository aasRepository; - static SubmodelRepository smRepository; + static ConnectedAasManager aasManager; static ConnectedAasRepository connectedAasRepository; static ConnectedSubmodelRepository connectedSmRepository; static RegistryAndDiscoveryInterfaceApi aasRegistryApi; static SubmodelRegistryApi smRegistryApi; - - static ConnectedAasManager aasManager; + static ConfigurableApplicationContext appContext; @BeforeClass public static void setupRepositories() { appContext = new SpringApplication(DummyAasEnvironmentComponent.class).run(new String[] {}); - connectedAasRepository = new ConnectedAasRepository(AAS_REPOSITORY_BASE_PATH); connectedSmRepository = new ConnectedSubmodelRepository(SM_REPOSITORY_BASE_PATH); aasRegistryApi = new RegistryAndDiscoveryInterfaceApi(AAS_REGISTRY_BASE_PATH); @@ -98,32 +71,6 @@ public static void stopContext() { appContext.close(); } - @Test - public void testParallelSubmodelCreation() throws ExecutionException, InterruptedException { - AssetAdministrationShell shell = createShell(); - - ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS); - ConcurrentLinkedDeque createdSubmodelIds = new ConcurrentLinkedDeque<>(); - - List> futures = IntStream.range(0, N_THREADS).mapToObj(i -> executorService.submit(() -> createdSubmodelIds.add(createSubmodel(shell.getId(), i)))).toList(); - - try { - for (int i = 0; i < N_THREADS; i++) { - futures.get(i).get(); - } - } finally { - executorService.shutdown(); - } - - createdSubmodelIds.forEach(submodelId -> assertSubmodelWasCreatedAndRegistered(shell.getId(), submodelId)); - } - - static void assertSubmodelWasCreatedAndRegistered(String shellId, String submodelId) { - assertEquals(submodelId, aasManager.getSubmodelService(submodelId).getSubmodel().getId()); - assertTrue(connectedAasRepository.getSubmodelReferences(shellId, PaginationInfo.NO_LIMIT).getResult().stream().map(Reference::getKeys).flatMap(Collection::stream).map(Key::getValue).anyMatch(submodelId::equals)); - } - - private static void cleanUpRegistries() { try { aasRegistryApi.deleteAllShellDescriptors(); @@ -137,22 +84,13 @@ private static void cleanUpRegistries() { } } - private static AssetAdministrationShell createShell() { - String id = UUID.randomUUID().toString(); - DefaultAssetAdministrationShell shell = new DefaultAssetAdministrationShell.Builder().id(id).build(); - aasManager.createAas(shell); - return aasManager.getAasService(id).getAAS(); + @Override + public AasRepository getAasRepository() { + return connectedAasRepository; } - private static String createSubmodel(String aasId, int threadId) { - try { - String id = aasId + "-thread" + threadId; - DefaultSubmodel submodel = new DefaultSubmodel.Builder().id(id).build(); - aasManager.createSubmodelInAas(aasId, submodel); - return id; - } catch (Exception e) { - throw new RuntimeException("Failed at thread " + threadId, e); - } + @Override + public ConnectedAasManager getConnectedAasManager() { + return aasManager; } - } diff --git a/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingMongoDb.java b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingMongoDb.java new file mode 100644 index 000000000..d92b08272 --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreadingMongoDb.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.aasenvironment.client; + +import org.eclipse.digitaltwin.basyx.aasregistry.client.api.RegistryAndDiscoveryInterfaceApi; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.aasrepository.client.ConnectedAasRepository; +import org.eclipse.digitaltwin.basyx.submodelregistry.client.api.SubmodelRegistryApi; +import org.eclipse.digitaltwin.basyx.submodelrepository.client.ConnectedSubmodelRepository; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; + +public class TestConnectedAasManagerMultithreadingMongoDb extends ConnectedAasManagerMultithreadingTestSuite { + static final String AAS_REPOSITORY_BASE_PATH = "http://localhost:8081"; + static final String SM_REPOSITORY_BASE_PATH = "http://localhost:8081"; + static final String AAS_REGISTRY_BASE_PATH = "http://localhost:8050"; + static final String SM_REGISTRY_BASE_PATH = "http://localhost:8060"; + + static ConnectedAasManager aasManager; + static ConnectedAasRepository connectedAasRepository; + static ConnectedSubmodelRepository connectedSmRepository; + static RegistryAndDiscoveryInterfaceApi aasRegistryApi; + static SubmodelRegistryApi smRegistryApi; + static ConfigurableApplicationContext appContext; + + @BeforeClass + public static void setupRepositories() { + appContext = new SpringApplicationBuilder(DummyAasEnvironmentComponent.class).profiles("mongodb").run(new String[] {}); + connectedAasRepository = new ConnectedAasRepository(AAS_REPOSITORY_BASE_PATH); + connectedSmRepository = new ConnectedSubmodelRepository(SM_REPOSITORY_BASE_PATH); + aasRegistryApi = new RegistryAndDiscoveryInterfaceApi(AAS_REGISTRY_BASE_PATH); + smRegistryApi = new SubmodelRegistryApi(SM_REGISTRY_BASE_PATH); + aasManager = new ConnectedAasManager(AAS_REGISTRY_BASE_PATH, AAS_REPOSITORY_BASE_PATH, SM_REGISTRY_BASE_PATH, SM_REPOSITORY_BASE_PATH); + + cleanUpRegistries(); + } + + @After + public void cleanUpComponents() { + cleanUpRegistries(); + } + + @AfterClass + public static void stopContext() { + appContext.close(); + } + + private static void cleanUpRegistries() { + try { + aasRegistryApi.deleteAllShellDescriptors(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + try { + smRegistryApi.deleteAllSubmodelDescriptors(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + @Override + public AasRepository getAasRepository() { + return connectedAasRepository; + } + + @Override + public ConnectedAasManager getConnectedAasManager() { + return aasManager; + } +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-client/src/test/resources/application-mongodb.properties b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/resources/application-mongodb.properties new file mode 100644 index 000000000..4afb4e37e --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/resources/application-mongodb.properties @@ -0,0 +1,8 @@ +basyx.backend = MongoDB + +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.aasregistry/basyx.aasregistry-service-inmemory-storage/pom.xml b/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/pom.xml index 3be02e79d..2ddafd618 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/pom.xml +++ b/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/pom.xml @@ -17,6 +17,10 @@ jar + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.aasregistry-service diff --git a/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAasRegistryStorageDecorator.java b/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAasRegistryStorageDecorator.java index 471588c6d..08a7041f2 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAasRegistryStorageDecorator.java +++ b/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAasRegistryStorageDecorator.java @@ -38,6 +38,7 @@ import org.eclipse.digitaltwin.basyx.aasregistry.service.errors.SubmodelNotFoundException; import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.AasRegistryStorage; import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.DescriptorFilter; +import org.eclipse.digitaltwin.basyx.common.backend.ThreadSafeAccess; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; @@ -50,37 +51,37 @@ public class ThreadSafeAasRegistryStorageDecorator implements AasRegistryStorage @Override public CursorResult> getAllAasDescriptors(@NonNull PaginationInfo pRequest, @NonNull DescriptorFilter filter) { - return access.read(storage::getAllAasDescriptors, pRequest, filter); + return access.read(() -> storage.getAllAasDescriptors(pRequest, filter)); } @Override public void removeAasDescriptor(@NonNull String aasDescriptorId) { - access.write(storage::removeAasDescriptor, aasDescriptorId); + access.write(() -> storage.removeAasDescriptor(aasDescriptorId)); } @Override public AssetAdministrationShellDescriptor getAasDescriptor(@NonNull String aasDescriptorId) throws AasDescriptorNotFoundException { - return access.read(storage::getAasDescriptor, aasDescriptorId); + return access.read(() -> storage.getAasDescriptor(aasDescriptorId)); } @Override public CursorResult> getAllSubmodels(@NonNull String aasDescriptorId, @NonNull PaginationInfo pRequest) throws AasDescriptorNotFoundException { - return access.read(storage::getAllSubmodels, aasDescriptorId, pRequest); + return access.read(() -> storage.getAllSubmodels(aasDescriptorId, pRequest)); } @Override public SubmodelDescriptor getSubmodel(@NonNull String aasDescriptorId, @NonNull String submodelId) { - return access.read(storage::getSubmodel, aasDescriptorId, submodelId); + return access.read(() -> storage.getSubmodel(aasDescriptorId, submodelId)); } @Override public void insertSubmodel(@NonNull String aasDescriptorId, @NonNull SubmodelDescriptor submodel) { - access.write(storage::insertSubmodel, aasDescriptorId, submodel); + access.write(() -> storage.insertSubmodel(aasDescriptorId, submodel)); } @Override public void removeSubmodel(@NonNull String aasDescrId, @NonNull String submodelId) { - access.write(storage::removeSubmodel, aasDescrId, submodelId); + access.write(() -> storage.removeSubmodel(aasDescrId, submodelId)); } @Override @@ -90,21 +91,21 @@ public Set clear() { @Override public ShellDescriptorSearchResponse searchAasDescriptors(ShellDescriptorSearchRequest request) { - return access.read(storage::searchAasDescriptors, request); + return access.read(() -> storage.searchAasDescriptors(request)); } @Override public void insertAasDescriptor(@Valid AssetAdministrationShellDescriptor descr) throws AasDescriptorAlreadyExistsException { - access.write(storage::insertAasDescriptor, descr); + access.write(() -> storage.insertAasDescriptor(descr)); } @Override public void replaceAasDescriptor(@NonNull String aasDescritorId, @NonNull AssetAdministrationShellDescriptor descriptor) throws AasDescriptorNotFoundException { - access.write(storage::replaceAasDescriptor, aasDescritorId, descriptor); + access.write(() -> storage.replaceAasDescriptor(aasDescritorId, descriptor)); } @Override public void replaceSubmodel(@NonNull String aasDescriptorId, @NonNull String submodelId, @NonNull SubmodelDescriptor submodel) throws AasDescriptorNotFoundException, SubmodelNotFoundException { - access.write(storage::replaceSubmodel, aasDescriptorId, submodelId, submodel); + access.write(() -> storage.replaceSubmodel(aasDescriptorId, submodelId, submodel)); } } diff --git a/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAccess.java b/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAccess.java deleted file mode 100644 index 3956c8977..000000000 --- a/basyx.aasregistry/basyx.aasregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/storage/memory/ThreadSafeAccess.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) - * - * 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.aasregistry.service.storage.memory; - -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -class ThreadSafeAccess { - - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final ReadLock readLock = lock.readLock(); - private final WriteLock writeLock = lock.writeLock(); - - public T write(Supplier supplier) { - return runWithLock(supplier, writeLock); - } - - public void write(Consumer consumer, A arg1) { - runWithLock(consumer, arg1, readLock); - } - - public void write(BiConsumer consumer, A arg1, B arg2) { - runWithLock(consumer, arg1, arg2, writeLock); - } - - public void write(TriConsumer consumer, A arg1, B arg2, C arg3) { - runWithLock(consumer, arg1, arg2, arg3, writeLock); - } - - public T read(Function func, A arg1) { - return runWithLock(func, arg1, readLock); - } - - public T read(BiFunction func, A arg1, B arg2) { - return runWithLock(func, arg1, arg2, readLock); - } - - private T runWithLock(Supplier supplier, Lock lock) { - try { - lock.lock(); - return supplier.get(); - } finally { - lock.unlock(); - } - } - - private void runWithLock(Consumer consumer, A arg1, Lock lock) { - try { - lock.lock(); - consumer.accept(arg1); - } finally { - lock.unlock(); - } - } - - private T runWithLock(Function func, A arg1, Lock lock) { - try { - lock.lock(); - return func.apply(arg1); - } finally { - lock.unlock(); - } - } - - private T runWithLock(BiFunction func, A arg1, B arg2, Lock lock) { - try { - lock.lock(); - return func.apply(arg1, arg2); - } finally { - lock.unlock(); - } - } - - private void runWithLock(BiConsumer consumer, A arg1, B arg2, Lock lock) { - try { - lock.lock(); - consumer.accept(arg1, arg2); - } finally { - lock.unlock(); - } - } - - private void runWithLock(TriConsumer consumer, A arg1, B arg2, C arg3, Lock lock) { - try { - lock.lock(); - consumer.accept(arg1, arg2, arg3); - } finally { - lock.unlock(); - } - } - - @FunctionalInterface - public static interface TriConsumer { - - void accept(S s, T t, U u); - - } -} \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml index 5936dd14a..2c6e699ce 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-backend/pom.xml @@ -15,6 +15,10 @@ basyx.aasrepository-backend + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.aasrepository-core diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleAasRepositoryFactory.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleAasRepositoryFactory.java index c9bbdb3c3..a23059ca2 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleAasRepositoryFactory.java +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleAasRepositoryFactory.java @@ -64,7 +64,7 @@ public SimpleAasRepositoryFactory(AasBackendProvider aasBackendProvider, AasServ @Override public AasRepository create() { - return new CrudAasRepository(aasBackendProvider, aasServiceFactory, aasRepositoryName); + return new ThreadSafeAasRepository(new CrudAasRepository(aasBackendProvider, aasServiceFactory, aasRepositoryName)); } } diff --git a/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeAasRepository.java new file mode 100644 index 000000000..842176907 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeAasRepository.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * 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.aasrepository.backend; + +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.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.common.backend.InstanceScopedThreadSafeAccess; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +/** + * A thread-safe wrapper for the {@link AasRepository} + * + * @author mateusmolina + */ +public class ThreadSafeAasRepository implements AasRepository { + + private final AasRepository decoratedAasRepository; + private final InstanceScopedThreadSafeAccess access = new InstanceScopedThreadSafeAccess(); + + public ThreadSafeAasRepository(AasRepository decoratedRepository) { + this.decoratedAasRepository = decoratedRepository; + } + + @Override + public CursorResult> getAllAas(PaginationInfo pInfo) { + return decoratedAasRepository.getAllAas(pInfo); + } + + @Override + public AssetAdministrationShell getAas(String aasId) throws ElementDoesNotExistException { + return access.read(() -> decoratedAasRepository.getAas(aasId), aasId); + } + + @Override + public void createAas(AssetAdministrationShell aas) throws CollidingIdentifierException, MissingIdentifierException { + decoratedAasRepository.createAas(aas); + } + + @Override + public void deleteAas(String aasId) { + access.write(() -> decoratedAasRepository.deleteAas(aasId), aasId); + } + + @Override + public void updateAas(String aasId, AssetAdministrationShell aas) { + access.write(() -> decoratedAasRepository.updateAas(aasId, aas), aasId); + } + + @Override + public CursorResult> getSubmodelReferences(String aasId, PaginationInfo pInfo) { + return access.read(() -> decoratedAasRepository.getSubmodelReferences(aasId, pInfo), aasId); + } + + @Override + public void addSubmodelReference(String aasId, Reference submodelReference) { + access.write(() -> decoratedAasRepository.addSubmodelReference(aasId, submodelReference), aasId); + } + + @Override + public void removeSubmodelReference(String aasId, String submodelId) { + access.write(() -> decoratedAasRepository.removeSubmodelReference(aasId, submodelId), aasId); + } + + @Override + public void setAssetInformation(String aasId, AssetInformation aasInfo) throws ElementDoesNotExistException { + access.write(() -> decoratedAasRepository.setAssetInformation(aasId, aasInfo), aasId); + } + + @Override + public AssetInformation getAssetInformation(String aasId) throws ElementDoesNotExistException { + return access.read(() -> decoratedAasRepository.getAssetInformation(aasId), aasId); + } + + @Override + public File getThumbnail(String aasId) { + return access.read(() -> decoratedAasRepository.getThumbnail(aasId), aasId); + } + + @Override + public void setThumbnail(String aasId, String fileName, String contentType, InputStream inputStream) { + access.write(() -> decoratedAasRepository.setThumbnail(aasId, fileName, contentType, inputStream), aasId); + } + + @Override + public void deleteThumbnail(String aasId) { + access.write(() -> decoratedAasRepository.deleteThumbnail(aasId), aasId); + } + + @Override + public String getName() { + return decoratedAasRepository.getName(); + } + +} 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/InMemoryAasService.java index 0fc9b9516..1421ed428 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/InMemoryAasService.java @@ -90,31 +90,27 @@ public CursorResult> getSubmodelReferences(PaginationInfo pInfo) return paginatedSubmodelReference; } - @Override public void addSubmodelReference(Reference submodelReference) { + throwExceptionIfReferenceIsAlreadyPresent(submodelReference); + List submodelsRefs = aas.getSubmodels(); - synchronized (submodelsRefs) { - throwExceptionIfReferenceIsAlreadyPresent(submodelReference); - submodelsRefs.add(submodelReference); - } + submodelsRefs.add(submodelReference); } @Override public void removeSubmodelReference(String submodelId) { List submodelsRefs = aas.getSubmodels(); - synchronized (submodelsRefs) { - submodelsRefs.remove(getSubmodelReferenceById(submodelId)); - } + submodelsRefs.remove(getSubmodelReferenceById(submodelId)); } @Override public void setAssetInformation(AssetInformation aasInfo) { - aas.setAssetInformation(aasInfo); + aas.setAssetInformation(aasInfo); } - + @Override - public AssetInformation getAssetInformation() { + public AssetInformation getAssetInformation() { return aas.getAssetInformation(); } @@ -130,10 +126,8 @@ private Reference getSubmodelReferenceById(String submodelId) { return specificSubmodelReference; } - private TreeMap convertToTreeMap(List submodelReferences, - Function idResolver) { - return submodelReferences.stream().collect(Collectors - .toMap(reference -> idResolver.apply(reference), ref -> ref, (ref1, ref2) -> ref1, TreeMap::new)); + private TreeMap convertToTreeMap(List submodelReferences, Function idResolver) { + return submodelReferences.stream().collect(Collectors.toMap(reference -> idResolver.apply(reference), ref -> ref, (ref1, ref2) -> ref1, TreeMap::new)); } private Function extractSubmodelID() { @@ -165,9 +159,9 @@ public File getThumbnail() { public void setThumbnail(String fileName, String contentType, InputStream inputStream) { FileMetadata thumbnailMetadata = new FileMetadata(fileName, contentType, inputStream); - if(fileRepository.exists(thumbnailMetadata.getFileName())) + if (fileRepository.exists(thumbnailMetadata.getFileName())) fileRepository.delete(thumbnailMetadata.getFileName()); - + String filePath = fileRepository.save(thumbnailMetadata); setAssetInformation(configureAssetInformationThumbnail(getAssetInformation(), contentType, filePath)); @@ -218,7 +212,7 @@ private File getResourceContent(Resource resource) throws IOException { private void throwExceptionIfReferenceIsAlreadyPresent(Reference submodelReference) { Optional submodelIdKey = getSubmodelTypeKey(submodelReference); - if(submodelIdKey.isEmpty()) + if (submodelIdKey.isEmpty()) return; String submodelId = submodelIdKey.get().getValue(); if (isSubmodelIdAlreadyReferenced(submodelId)) { @@ -233,7 +227,7 @@ private boolean isSubmodelIdAlreadyReferenced(String submodelId) { private static Optional getSubmodelTypeKey(Reference submodelReference) { Optional submodelIdKey = submodelReference.getKeys().stream().filter(key -> { KeyTypes type = key.getType(); - if(type == null) + if (type == null) throw new MissingKeyTypeException(); return type.equals(KeyTypes.SUBMODEL); }).findFirst(); diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml b/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml index f142caf38..9b9d6fa9e 100644 --- a/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/pom.xml @@ -9,6 +9,10 @@ BaSyx AASX File Server Backend + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.aasxfileserver-core 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/SimpleAASXFileServerFactory.java index 58d4dd093..6131aed5f 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/SimpleAASXFileServerFactory.java @@ -74,7 +74,7 @@ public SimpleAASXFileServerFactory(AASXFileServerBackendProvider aasxFileServerB @Override public AASXFileServer create() { - return new CrudAASXFileServer(aasxFileServerBackendProvider, aasxFileServerName); + return new ThreadSafeAASXFileServer(new CrudAASXFileServer(aasxFileServerBackendProvider, aasxFileServerName)); } } diff --git a/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/ThreadSafeAASXFileServer.java b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/ThreadSafeAASXFileServer.java new file mode 100644 index 000000000..79051c929 --- /dev/null +++ b/basyx.aasxfileserver/basyx.aasxfileserver-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasxfileserver/backend/ThreadSafeAASXFileServer.java @@ -0,0 +1,83 @@ + +/******************************************************************************* + * 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.backend; + +import java.io.InputStream; +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.PackageDescription; +import org.eclipse.digitaltwin.basyx.aasxfileserver.AASXFileServer; +import org.eclipse.digitaltwin.basyx.common.backend.InstanceScopedThreadSafeAccess; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +/** + * A thread-safe wrapper for the {@link AASXFileServer} + * + * @author mateusmolina + */ +public class ThreadSafeAASXFileServer implements AASXFileServer { + + private final AASXFileServer decoratedAasxFileServer; + private final InstanceScopedThreadSafeAccess access = new InstanceScopedThreadSafeAccess(); + + public ThreadSafeAASXFileServer(AASXFileServer aasxFileServer) { + this.decoratedAasxFileServer = aasxFileServer; + } + + @Override + public CursorResult> getAllAASXPackageIds(String shellId, PaginationInfo pInfo) { + return decoratedAasxFileServer.getAllAASXPackageIds(shellId, pInfo); + } + + @Override + public InputStream getAASXByPackageId(String packageId) throws ElementDoesNotExistException { + return access.read(() -> decoratedAasxFileServer.getAASXByPackageId(packageId), packageId); + } + + @Override + public void updateAASXByPackageId(String packageId, List shellIds, InputStream file, String filename) throws ElementDoesNotExistException { + access.write(() -> decoratedAasxFileServer.updateAASXByPackageId(packageId, shellIds, file, filename), packageId); + } + + @Override + public PackageDescription createAASXPackage(List shellIds, InputStream file, String filename) { + return decoratedAasxFileServer.createAASXPackage(shellIds, file, filename); + } + + @Override + public void deleteAASXByPackageId(String packageId) throws ElementDoesNotExistException { + access.write(() -> decoratedAasxFileServer.deleteAASXByPackageId(packageId), packageId); + } + + @Override + public String getName() { + return decoratedAasxFileServer.getName(); + } + +} diff --git a/basyx.common/basyx.backend/pom.xml b/basyx.common/basyx.backend/pom.xml new file mode 100644 index 000000000..59a5f257b --- /dev/null +++ b/basyx.common/basyx.backend/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + org.eclipse.digitaltwin.basyx + basyx.common + ${revision} + + basyx.backend + BaSyx Backend Common + BaSyx Backend Common + + + + org.eclipse.digitaltwin.basyx + basyx.core + + + \ No newline at end of file diff --git a/basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/InstanceScopedThreadSafeAccess.java b/basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/InstanceScopedThreadSafeAccess.java new file mode 100644 index 000000000..40e0a5601 --- /dev/null +++ b/basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/InstanceScopedThreadSafeAccess.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.common.backend; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +/** + * Utility class for thread-safe access at the instance level + * + * @author mateusmolina + */ +public class InstanceScopedThreadSafeAccess { + private final ConcurrentHashMap accessMap = new ConcurrentHashMap<>(); + + public T read(Supplier supplier, Object instanceLock) { + return getAccess(instanceLock).read(supplier); + } + + public void read(Runnable action, Object instanceLock) { + getAccess(instanceLock).read(action); + } + + public T write(Supplier supplier, Object instanceLock) { + return getAccess(instanceLock).write(supplier); + } + + public void write(Runnable action, Object instanceLock) { + getAccess(instanceLock).write(action); + } + + public void removeLock(Object instanceLock) { + accessMap.remove(instanceLock); + } + + private ThreadSafeAccess getAccess(Object instanceLock) { + return accessMap.computeIfAbsent(instanceLock, k -> new ThreadSafeAccess()); + } +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java b/basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/ThreadSafeAccess.java similarity index 58% rename from basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java rename to basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/ThreadSafeAccess.java index 62c039ee8..ec78fd955 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeAccess.java +++ b/basyx.common/basyx.backend/src/main/java/org/eclipse/digitaltwin/basyx/common/backend/ThreadSafeAccess.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2023 DFKI GmbH (https://www.dfki.de/en/web) + * Copyright (C) 2024 DFKI GmbH (https://www.dfki.de/en/web) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,39 +22,37 @@ * * SPDX-License-Identifier: MIT ******************************************************************************/ -package org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.memory; +package org.eclipse.digitaltwin.basyx.common.backend; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; -class ThreadSafeAccess { - +/** + * Utility class for thread-safe access + * + * @author Gerhard Sonnenberg DFKI GmbH, mateusmolina + */ +public class ThreadSafeAccess { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private final ReadLock readLock = lock.readLock(); - private final WriteLock writeLock = lock.writeLock(); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); - public void write(Consumer consumer, A arg1) { - runWithLock(consumer, arg1, readLock); + public T write(Supplier supplier) { + return runWithLock(supplier, writeLock); } - public void write(BiConsumer consumer, A arg1, B arg2) { - runWithLock(consumer, arg1, arg2, writeLock); + public void write(Runnable action) { + runWithLock(action, writeLock); } - public T read(Function func, A arg1) { - return runWithLock(func, arg1, readLock); - } - - public T write(Supplier supplier) { - return runWithLock(supplier, writeLock); + public T read(Supplier supplier) { + return runWithLock(supplier, readLock); } + public void read(Runnable action) { + runWithLock(action, readLock); + } private T runWithLock(Supplier supplier, Lock lock) { try { lock.lock(); @@ -64,28 +62,10 @@ private T runWithLock(Supplier supplier, Lock lock) { } } - private void runWithLock(Consumer consumer, A arg1, Lock lock) { - try { - lock.lock(); - consumer.accept(arg1); - } finally { - lock.unlock(); - } - } - - private T runWithLock(Function func, A arg1, Lock lock) { - try { - lock.lock(); - return func.apply(arg1); - } finally { - lock.unlock(); - } - } - - private void runWithLock(BiConsumer consumer, A arg1, B arg2, Lock lock) { + private void runWithLock(Runnable action, Lock lock) { try { lock.lock(); - consumer.accept(arg1, arg2); + action.run(); } finally { lock.unlock(); } diff --git a/basyx.common/pom.xml b/basyx.common/pom.xml index fe6807eaa..b07bb063c 100644 --- a/basyx.common/pom.xml +++ b/basyx.common/pom.xml @@ -20,6 +20,7 @@ basyx.mongocore basyx.authorization basyx.client + basyx.backend basyx.backend.inmemory.core basyx.filerepository-backend basyx.filerepository-backend-inmemory diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/pom.xml index ac5d4cf21..8a9d3dea4 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/pom.xml +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/pom.xml @@ -14,6 +14,10 @@ Concept Description Repository Backend + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.conceptdescriptionrepository-core diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleConceptDescriptionRepositoryFactory.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleConceptDescriptionRepositoryFactory.java index 5a6851f7a..59a01d5e9 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleConceptDescriptionRepositoryFactory.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/SimpleConceptDescriptionRepositoryFactory.java @@ -80,9 +80,9 @@ public SimpleConceptDescriptionRepositoryFactory(ConceptDescriptionBackendProvid public ConceptDescriptionRepository create() { if (conceptDescriptions == null) - return new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptionRepositoryName); + return new ThreadSafeConceptDescriptionRepository(new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptionRepositoryName)); - return new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptions, conceptDescriptionRepositoryName); + return new ThreadSafeConceptDescriptionRepository(new CrudConceptDescriptionRepository(conceptDescriptionBackend, conceptDescriptions, conceptDescriptionRepositoryName)); } } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeConceptDescriptionRepository.java new file mode 100644 index 000000000..053a65e54 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/backend/ThreadSafeConceptDescriptionRepository.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.aasrepository.backend; + +import java.util.List; + +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.basyx.common.backend.InstanceScopedThreadSafeAccess; +import org.eclipse.digitaltwin.basyx.common.backend.ThreadSafeAccess; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; +import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; + +/** + * Thread-safe wrapper for the {@link ConceptDescriptionRepository} + * + * @author mateusmolina + */ +public class ThreadSafeConceptDescriptionRepository implements ConceptDescriptionRepository { + + private final InstanceScopedThreadSafeAccess access = new InstanceScopedThreadSafeAccess(); + private final ConceptDescriptionRepository decoratedRepository; + + public ThreadSafeConceptDescriptionRepository(ConceptDescriptionRepository decoratedRepository) { + this.decoratedRepository = decoratedRepository; + } + + @Override + public CursorResult> getAllConceptDescriptions(PaginationInfo pInfo) { + return decoratedRepository.getAllConceptDescriptions(pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByIdShort(String idShort, PaginationInfo pInfo) { + return decoratedRepository.getAllConceptDescriptionsByIdShort(idShort, pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByIsCaseOf(Reference isCaseOf, PaginationInfo pInfo) { + return decoratedRepository.getAllConceptDescriptionsByIsCaseOf(isCaseOf, pInfo); + } + + @Override + public CursorResult> getAllConceptDescriptionsByDataSpecificationReference(Reference dataSpecificationReference, PaginationInfo pInfo) { + return decoratedRepository.getAllConceptDescriptionsByDataSpecificationReference(dataSpecificationReference, pInfo); + } + + @Override + public ConceptDescription getConceptDescription(String conceptDescriptionId) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getConceptDescription(conceptDescriptionId), conceptDescriptionId); + } + + @Override + public void updateConceptDescription(String conceptDescriptionId, ConceptDescription conceptDescription) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.updateConceptDescription(conceptDescriptionId, conceptDescription), conceptDescriptionId); + } + + @Override + public void createConceptDescription(ConceptDescription conceptDescription) throws CollidingIdentifierException, MissingIdentifierException { + decoratedRepository.createConceptDescription(conceptDescription); + } + + @Override + public void deleteConceptDescription(String conceptDescriptionId) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.deleteConceptDescription(conceptDescriptionId), conceptDescriptionId); + } + + @Override + public String getName() { + return decoratedRepository.getName(); + } + +} diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml index 0d000659c..c978ba818 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/pom.xml @@ -17,6 +17,10 @@ jar + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.submodelregistry-service diff --git a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java index 8eeeda022..94c76abe9 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java +++ b/basyx.submodelregistry/basyx.submodelregistry-service-inmemory-storage/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/service/storage/memory/ThreadSafeSubmodelRegistryStorageDecorator.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Set; +import org.eclipse.digitaltwin.basyx.common.backend.ThreadSafeAccess; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; @@ -45,7 +46,7 @@ public class ThreadSafeSubmodelRegistryStorageDecorator implements SubmodelRegis @Override public CursorResult> getAllSubmodelDescriptors(PaginationInfo pRequest) { - return access.read(storage::getAllSubmodelDescriptors, pRequest); + return access.read(() -> storage.getAllSubmodelDescriptors(pRequest)); } @Override @@ -55,22 +56,22 @@ public Set clear() { @Override public SubmodelDescriptor getSubmodelDescriptor( String submodelId) throws SubmodelNotFoundException { - return access.read(storage::getSubmodelDescriptor, submodelId); + return access.read(() -> storage.getSubmodelDescriptor(submodelId)); } @Override public void insertSubmodelDescriptor(SubmodelDescriptor descr) throws SubmodelAlreadyExistsException { - access.write(storage::insertSubmodelDescriptor, descr); + access.write(() -> storage.insertSubmodelDescriptor(descr)); } @Override public void removeSubmodelDescriptor(String submodelId) throws SubmodelNotFoundException { - access.write(storage::removeSubmodelDescriptor, submodelId); + access.write(() -> storage.removeSubmodelDescriptor(submodelId)); } @Override public void replaceSubmodelDescriptor(String submodelId, SubmodelDescriptor descr) throws SubmodelNotFoundException { - access.write(storage::replaceSubmodelDescriptor, submodelId, descr); + access.write(() -> storage.replaceSubmodelDescriptor(submodelId, descr)); } } \ 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..c9ad568ba 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/pom.xml @@ -15,6 +15,10 @@ BaSyx Submodelrepository backend + + org.eclipse.digitaltwin.basyx + basyx.backend + org.eclipse.digitaltwin.basyx basyx.submodelrepository-core 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 index 815fe03bc..54d342957 100644 --- 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 @@ -84,9 +84,9 @@ public SimpleSubmodelRepositoryFactory(SubmodelBackendProvider submodelBackendPr public SubmodelRepository create() { if (submodels == null) - return new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodelRepositoryName); + return new ThreadSafeSubmodelRepository(new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodelRepositoryName)); - return new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodels, submodelRepositoryName); + return new ThreadSafeSubmodelRepository(new CrudSubmodelRepository(submodelBackendProvider, submodelServiceFactory, submodels, submodelRepositoryName)); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/ThreadSafeSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/ThreadSafeSubmodelRepository.java new file mode 100644 index 000000000..5c1d9ac39 --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/backend/ThreadSafeSubmodelRepository.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * 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.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.common.backend.InstanceScopedThreadSafeAccess; +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.MissingIdentifierException; +import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; +import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; + +/** + * A thread-safe wrapper for the {@link SubmodelRepository} + * + * @author mateusmolina + */ +public class ThreadSafeSubmodelRepository implements SubmodelRepository { + + private final InstanceScopedThreadSafeAccess access = new InstanceScopedThreadSafeAccess(); + private final SubmodelRepository decoratedRepository; + + public ThreadSafeSubmodelRepository(SubmodelRepository submodelRepository) { + this.decoratedRepository = submodelRepository; + } + + @Override + public CursorResult> getAllSubmodels(PaginationInfo pInfo) { + return decoratedRepository.getAllSubmodels(pInfo); + } + + @Override + public CursorResult> getAllSubmodels(String semanticId, PaginationInfo pInfo) { + return decoratedRepository.getAllSubmodels(semanticId, pInfo); + } + + @Override + public Submodel getSubmodel(String submodelId) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodel(submodelId), submodelId); + } + + @Override + public void updateSubmodel(String submodelId, Submodel submodel) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.updateSubmodel(submodelId, submodel), submodelId); + } + + @Override + public void createSubmodel(Submodel submodel) throws CollidingIdentifierException, MissingIdentifierException { + decoratedRepository.createSubmodel(submodel); + } + + @Override + public void updateSubmodelElement(String submodelIdentifier, String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.updateSubmodelElement(submodelIdentifier, idShortPath, submodelElement), submodelIdentifier); + } + + @Override + public void deleteSubmodel(String submodelId) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.deleteSubmodel(submodelId), submodelId); + } + + @Override + public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodelElements(submodelId, pInfo), submodelId); + } + + @Override + public SubmodelElement getSubmodelElement(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodelElement(submodelId, smeIdShort), submodelId); + } + + @Override + public SubmodelElementValue getSubmodelElementValue(String submodelId, String smeIdShort) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodelElementValue(submodelId, smeIdShort), submodelId); + } + + @Override + public void setSubmodelElementValue(String submodelId, String smeIdShort, SubmodelElementValue value) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.setSubmodelElementValue(submodelId, smeIdShort, value), submodelId); + } + + @Override + public void createSubmodelElement(String submodelId, SubmodelElement smElement) { + access.write(() -> decoratedRepository.createSubmodelElement(submodelId, smElement), submodelId); + } + + @Override + public void createSubmodelElement(String submodelId, String idShortPath, SubmodelElement smElement) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.createSubmodelElement(submodelId, idShortPath, smElement), submodelId); + } + + @Override + public void deleteSubmodelElement(String submodelId, String idShortPath) throws ElementDoesNotExistException { + access.write(() -> decoratedRepository.deleteSubmodelElement(submodelId, idShortPath), submodelId); + } + + @Override + public OperationVariable[] invokeOperation(String submodelId, String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { + return decoratedRepository.invokeOperation(submodelId, idShortPath, input); + } + + @Override + public SubmodelValueOnly getSubmodelByIdValueOnly(String submodelId) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodelByIdValueOnly(submodelId), submodelId); + } + + @Override + public Submodel getSubmodelByIdMetadata(String submodelId) throws ElementDoesNotExistException { + return access.read(() -> decoratedRepository.getSubmodelByIdMetadata(submodelId), submodelId); + } + + @Override + public File getFileByPathSubmodel(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + return access.read(() -> decoratedRepository.getFileByPathSubmodel(submodelId, idShortPath), submodelId); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { + access.write(() -> decoratedRepository.setFileValue(submodelId, idShortPath, fileName, inputStream), submodelId); + } + + @Override + public void deleteFileValue(String submodelId, String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { + access.write(() -> decoratedRepository.deleteFileValue(submodelId, idShortPath), submodelId); + } + + @Override + public void patchSubmodelElements(String submodelId, List submodelElementList) { + access.write(() -> decoratedRepository.patchSubmodelElements(submodelId, submodelElementList), submodelId); + } + + @Override + public InputStream getFileByFilePath(String submodelId, String filePath) { + return access.read(() -> decoratedRepository.getFileByFilePath(submodelId, filePath), submodelId); + } + + @Override + public String getName() { + return decoratedRepository.getName(); + } +} diff --git a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java index 87754a510..0a425817a 100644 --- a/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java +++ b/basyx.submodelservice/basyx.submodelservice-backend-inmemory/src/main/java/org/eclipse/digitaltwin/basyx/submodelservice/InMemorySubmodelService.java @@ -73,8 +73,6 @@ public class InMemorySubmodelService implements SubmodelService { private final FileRepository fileRepository; - private final Object submodelLock = new Object(); - /** * Creates the InMemory SubmodelService containing the passed Submodel * @@ -117,22 +115,18 @@ public SubmodelElementValue getSubmodelElementValue(String idShort) throws Eleme @SuppressWarnings("unchecked") @Override public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { - synchronized (submodelLock) { - SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); + SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); - ValueMapper valueMapper = submodelElementValueFactory.create(getSubmodelElement(idShort)); + ValueMapper valueMapper = submodelElementValueFactory.create(getSubmodelElement(idShort)); - valueMapper.setValue(value); - } + valueMapper.setValue(value); } @Override public void createSubmodelElement(SubmodelElement submodelElement) throws CollidingIdentifierException { - synchronized (submodelLock) { - List smElements = submodel.getSubmodelElements(); - throwIfSubmodelElementExists(submodelElement.getIdShort()); - smElements.add(submodelElement); - } + List smElements = submodel.getSubmodelElements(); + throwIfSubmodelElementExists(submodelElement.getIdShort()); + smElements.add(submodelElement); } private void throwIfSubmodelElementExists(String submodelElementId) { @@ -146,51 +140,45 @@ private void throwIfSubmodelElementExists(String submodelElementId) { @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); - list.setValue(submodelElements); - return; - } - if (parentSme instanceof SubmodelElementCollection) { - SubmodelElementCollection collection = (SubmodelElementCollection) parentSme; - List submodelElements = collection.getValue(); - submodelElements.add(submodelElement); - collection.setValue(submodelElements); - } + 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); + list.setValue(submodelElements); + return; + } + if (parentSme instanceof SubmodelElementCollection) { + SubmodelElementCollection collection = (SubmodelElementCollection) parentSme; + List submodelElements = collection.getValue(); + submodelElements.add(submodelElement); + collection.setValue(submodelElements); } } @Override public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) { - synchronized (submodelLock) { - deleteSubmodelElement(idShortPath); + deleteSubmodelElement(idShortPath); - String idShortPathParentSME = parser.getIdShortPathOfParentElement(idShortPath); - if (idShortPath.equals(idShortPathParentSME)) { - createSubmodelElement(submodelElement); - return; - } - createSubmodelElement(idShortPathParentSME, submodelElement); + 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); + deleteAssociatedFileIfAny(idShortPath); - if (!helper.isNestedIdShortPath(idShortPath)) { - deleteFlatSubmodelElement(idShortPath); - return; - } - deleteNestedSubmodelElement(idShortPath); + if (!helper.isNestedIdShortPath(idShortPath)) { + deleteFlatSubmodelElement(idShortPath); + return; } + deleteNestedSubmodelElement(idShortPath); } private void deleteNestedSubmodelElement(String idShortPath) { @@ -268,52 +256,48 @@ public java.io.File getFileByPath(String idShortPath) throws ElementDoesNotExist @Override public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - synchronized (submodelLock) { - SubmodelElement submodelElement = getSubmodelElement(idShortPath); + SubmodelElement submodelElement = getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement); + throwIfSmElementIsNotAFile(submodelElement); - File fileSmElement = (File) submodelElement; + File fileSmElement = (File) submodelElement; - if (fileRepository.exists(fileSmElement.getValue())) - fileRepository.delete(fileSmElement.getValue()); + if (fileRepository.exists(fileSmElement.getValue())) + fileRepository.delete(fileSmElement.getValue()); - String uniqueFileName = createUniqueFileName(idShortPath, fileName); + String uniqueFileName = createUniqueFileName(idShortPath, fileName); - FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); + FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); - if (fileRepository.exists(fileMetadata.getFileName())) - fileRepository.delete(fileMetadata.getFileName()); + if (fileRepository.exists(fileMetadata.getFileName())) + fileRepository.delete(fileMetadata.getFileName()); - String filePath = fileRepository.save(fileMetadata); + String filePath = fileRepository.save(fileMetadata); - FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); - setSubmodelElementValue(idShortPath, fileValue); - } + setSubmodelElementValue(idShortPath, fileValue); } @Override public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - synchronized (submodelLock) { - SubmodelElement submodelElement = getSubmodelElement(idShortPath); + SubmodelElement submodelElement = getSubmodelElement(idShortPath); - throwIfSmElementIsNotAFile(submodelElement); + throwIfSmElementIsNotAFile(submodelElement); - File fileSubmodelElement = (File) submodelElement; - String filePath = fileSubmodelElement.getValue(); + File fileSubmodelElement = (File) submodelElement; + String filePath = fileSubmodelElement.getValue(); - fileRepository.delete(filePath); + fileRepository.delete(filePath); - FileBlobValue fileValue = new FileBlobValue(" ", " "); + FileBlobValue fileValue = new FileBlobValue(" ", " "); - setSubmodelElementValue(idShortPath, fileValue); - } + setSubmodelElementValue(idShortPath, fileValue); } @Override - public InputStream getFileByFilePath(String filePath) throws FileDoesNotExistException{ + public InputStream getFileByFilePath(String filePath) throws FileDoesNotExistException { return fileRepository.find(filePath); } diff --git a/pom.xml b/pom.xml index 36dd3ae3b..648b2d17b 100644 --- a/pom.xml +++ b/pom.xml @@ -448,6 +448,11 @@ basyx.filerepository-backend-mongodb ${revision} + + org.eclipse.digitaltwin.basyx + basyx.backend + ${revision} + org.eclipse.digitaltwin.basyx basyx.backend.inmemory.core