diff --git a/basyx.aasenvironment/basyx.aasenvironment-client/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerHelper.java b/basyx.aasenvironment/basyx.aasenvironment-client/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerHelper.java index d6e0ab8c6..3d8a90ec7 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-client/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerHelper.java +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/ConnectedAasManagerHelper.java @@ -60,13 +60,13 @@ static ObjectMapper buildObjectMapper() { static AasDescriptorFactory buildAasDescriptorFactory(String... aasRepositoryBaseUrls) { AttributeMapper attributeMapper = new AttributeMapper(objectMapper); - return new AasDescriptorFactory(null, List.of(aasRepositoryBaseUrls), attributeMapper); + return new AasDescriptorFactory(List.of(aasRepositoryBaseUrls), attributeMapper); } static SubmodelDescriptorFactory buildSmDescriptorFactory(String... aasRepositoryBaseUrls) { org.eclipse.digitaltwin.basyx.submodelregistry.client.mapper.AttributeMapper attributeMapperSm = new org.eclipse.digitaltwin.basyx.submodelregistry.client.mapper.AttributeMapper( objectMapper); - return new SubmodelDescriptorFactory(null, List.of(aasRepositoryBaseUrls), attributeMapperSm); + return new SubmodelDescriptorFactory(List.of(aasRepositoryBaseUrls), attributeMapperSm); } } 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/TestConnectedAasManagerMultithreading.java new file mode 100644 index 000000000..8ff0ea860 --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-client/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/client/TestConnectedAasManagerMultithreading.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * 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.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 { + 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 ConnectedAasRepository connectedAasRepository; + static ConnectedSubmodelRepository connectedSmRepository; + static RegistryAndDiscoveryInterfaceApi aasRegistryApi; + static SubmodelRegistryApi smRegistryApi; + + static ConnectedAasManager aasManager; + + @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); + 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(); + } + + @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(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + try { + smRegistryApi.deleteAllSubmodelDescriptors(); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + 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(); + } + + 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); + } + } + +} diff --git a/basyx.aasregistry/basyx.aasregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/main/client/factory/AasDescriptorFactory.java b/basyx.aasregistry/basyx.aasregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/main/client/factory/AasDescriptorFactory.java index 723b6fbea..1fe0709bb 100644 --- a/basyx.aasregistry/basyx.aasregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/main/client/factory/AasDescriptorFactory.java +++ b/basyx.aasregistry/basyx.aasregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/main/client/factory/AasDescriptorFactory.java @@ -53,15 +53,12 @@ public class AasDescriptorFactory { private static final String AAS_INTERFACE = "AAS-3.0"; private static final String AAS_REPOSITORY_PATH = "shells"; - private AssetAdministrationShell shell; - private List aasRepositoryURLs; + private final List aasRepositoryURLs; + private static AttributeMapper attributeMapper; - private AttributeMapper attributeMapper; - - public AasDescriptorFactory(AssetAdministrationShell shell, List aasRepositoryBaseURLs, AttributeMapper attributeMapper) { - this.shell = shell; + public AasDescriptorFactory(List aasRepositoryBaseURLs, AttributeMapper attributeMapper) { this.aasRepositoryURLs = createAasRepositoryUrls(aasRepositoryBaseURLs); - this.attributeMapper = attributeMapper; + AasDescriptorFactory.attributeMapper = attributeMapper; } /** @@ -69,7 +66,7 @@ public AasDescriptorFactory(AssetAdministrationShell shell, List aasRepo * * @return the created AssetAdministrationShellDescriptor */ - public AssetAdministrationShellDescriptor create() { + public AssetAdministrationShellDescriptor create(AssetAdministrationShell shell) { AssetAdministrationShellDescriptor descriptor = new AssetAdministrationShellDescriptor(); @@ -77,7 +74,7 @@ public AssetAdministrationShellDescriptor create() { setIdShort(shell.getIdShort(), descriptor); - setEndpointItem(shell.getId(), descriptor); + setEndpointItem(shell.getId(), descriptor, aasRepositoryURLs); setDescription(shell.getDescription(), descriptor); @@ -96,12 +93,7 @@ public AssetAdministrationShellDescriptor create() { return descriptor; } - public AssetAdministrationShellDescriptor create(AssetAdministrationShell shell) { - this.shell = shell; - return create(); - } - - private void setDescription(List descriptions, AssetAdministrationShellDescriptor descriptor) { + private static void setDescription(List descriptions, AssetAdministrationShellDescriptor descriptor) { if (descriptions == null || descriptions.isEmpty()) return; @@ -109,7 +101,7 @@ private void setDescription(List descriptions, AssetAdminist descriptor.setDescription(attributeMapper.mapDescription(descriptions)); } - private void setDisplayName(List displayNames, AssetAdministrationShellDescriptor descriptor) { + private static void setDisplayName(List displayNames, AssetAdministrationShellDescriptor descriptor) { if (displayNames == null || displayNames.isEmpty()) return; @@ -117,7 +109,7 @@ private void setDisplayName(List displayNames, AssetAdminist descriptor.setDisplayName(attributeMapper.mapDisplayName(displayNames)); } - private void setExtensions(List extensions, AssetAdministrationShellDescriptor descriptor) { + private static void setExtensions(List extensions, AssetAdministrationShellDescriptor descriptor) { if (extensions == null || extensions.isEmpty()) return; @@ -125,7 +117,7 @@ private void setExtensions(List extensions, AssetAdministrationShellD descriptor.setExtensions(attributeMapper.mapExtensions(extensions)); } - private void setAdministration(AdministrativeInformation administration, AssetAdministrationShellDescriptor descriptor) { + private static void setAdministration(AdministrativeInformation administration, AssetAdministrationShellDescriptor descriptor) { if (administration == null) return; @@ -133,7 +125,7 @@ private void setAdministration(AdministrativeInformation administration, AssetAd descriptor.setAdministration(attributeMapper.mapAdministration(administration)); } - private void setAssetKind(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { + private static void setAssetKind(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { if (assetInformation == null || assetInformation.getAssetKind() == null) return; @@ -141,7 +133,7 @@ private void setAssetKind(AssetInformation assetInformation, AssetAdministration descriptor.setAssetKind(attributeMapper.mapAssetKind(assetInformation.getAssetKind())); } - private void setAssetType(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { + private static void setAssetType(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { if (assetInformation == null || assetInformation.getAssetType() == null) return; @@ -149,7 +141,7 @@ private void setAssetType(AssetInformation assetInformation, AssetAdministration descriptor.setAssetType(assetInformation.getAssetType()); } - private void setGlobalAssetId(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { + private static void setGlobalAssetId(AssetInformation assetInformation, AssetAdministrationShellDescriptor descriptor) { if (assetInformation == null || assetInformation.getGlobalAssetId() == null) return; @@ -157,7 +149,7 @@ private void setGlobalAssetId(AssetInformation assetInformation, AssetAdministra descriptor.setGlobalAssetId(assetInformation.getGlobalAssetId()); } - private void setEndpointItem(String shellId, AssetAdministrationShellDescriptor descriptor) { + private static void setEndpointItem(String shellId, AssetAdministrationShellDescriptor descriptor, List aasRepositoryURLs) { for (String eachUrl : aasRepositoryURLs) { Endpoint endpoint = new Endpoint(); endpoint.setInterface(AAS_INTERFACE); @@ -168,7 +160,7 @@ private void setEndpointItem(String shellId, AssetAdministrationShellDescriptor } } - private ProtocolInformation createProtocolInformation(String shellId, String url) { + private static ProtocolInformation createProtocolInformation(String shellId, String url) { String href = String.format("%s/%s", url, Base64UrlEncodedIdentifier.encodeIdentifier(shellId)); ProtocolInformation protocolInformation = new ProtocolInformation(); @@ -178,15 +170,15 @@ private ProtocolInformation createProtocolInformation(String shellId, String url return protocolInformation; } - private void setIdShort(String idShort, AssetAdministrationShellDescriptor descriptor) { + private static void setIdShort(String idShort, AssetAdministrationShellDescriptor descriptor) { descriptor.setIdShort(idShort); } - private void setId(String shellId, AssetAdministrationShellDescriptor descriptor) { + private static void setId(String shellId, AssetAdministrationShellDescriptor descriptor) { descriptor.setId(shellId); } - private String getProtocol(String endpoint) { + private static String getProtocol(String endpoint) { try { return new URL(endpoint).getProtocol(); } catch (MalformedURLException e) { @@ -194,7 +186,7 @@ private String getProtocol(String endpoint) { } } - private List createAasRepositoryUrls(List aasRepositoryBaseURLs) { + private static List createAasRepositoryUrls(List aasRepositoryBaseURLs) { List toReturn = new ArrayList<>(aasRepositoryBaseURLs.size()); for (String eachUrl : aasRepositoryBaseURLs) { toReturn.add(RepositoryUrlHelper.createRepositoryUrl(eachUrl, AAS_REPOSITORY_PATH)); diff --git a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java index a938e76d4..f5a8a238c 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/registry/integration/RegistryIntegrationAasRepository.java @@ -78,7 +78,7 @@ public AssetAdministrationShell getAas(String shellId) throws ElementDoesNotExis @Override public void createAas(AssetAdministrationShell shell) throws CollidingIdentifierException { - AssetAdministrationShellDescriptor descriptor = new AasDescriptorFactory(shell, aasRepositoryRegistryLink.getAasRepositoryBaseURLs(), attributeMapper).create(); + AssetAdministrationShellDescriptor descriptor = new AasDescriptorFactory(aasRepositoryRegistryLink.getAasRepositoryBaseURLs(), attributeMapper).create(shell); decorated.createAas(shell); 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 7c751945b..8b3739033 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 @@ -103,8 +103,8 @@ public void testExternalUrl() { private AssetAdministrationShellDescriptor createAndRetrieveDescriptor(AssetAdministrationShell shell) { registryIntegrationAasRepository.createAas(shell); - AasDescriptorFactory descriptorFactory = new AasDescriptorFactory(shell, mockedRegistryLink.getAasRepositoryBaseURLs(), mockedAttributeMapper); - return descriptorFactory.create(); + AasDescriptorFactory descriptorFactory = new AasDescriptorFactory(mockedRegistryLink.getAasRepositoryBaseURLs(), mockedAttributeMapper); + return descriptorFactory.create(shell); } private AssetAdministrationShell createDummyAas() { 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 563dfc8f4..0fc9b9516 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 @@ -93,16 +93,19 @@ public CursorResult> getSubmodelReferences(PaginationInfo pInfo) @Override public void addSubmodelReference(Reference submodelReference) { - throwExceptionIfReferenceIsAlreadyPresent(submodelReference); - - aas.getSubmodels().add(submodelReference); + List submodelsRefs = aas.getSubmodels(); + synchronized (submodelsRefs) { + throwExceptionIfReferenceIsAlreadyPresent(submodelReference); + submodelsRefs.add(submodelReference); + } } @Override public void removeSubmodelReference(String submodelId) { - Reference specificSubmodelReference = getSubmodelReferenceById(submodelId); - - aas.getSubmodels().remove(specificSubmodelReference); + List submodelsRefs = aas.getSubmodels(); + synchronized (submodelsRefs) { + submodelsRefs.remove(getSubmodelReferenceById(submodelId)); + } } @Override diff --git a/basyx.submodelregistry/basyx.submodelregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/client/factory/SubmodelDescriptorFactory.java b/basyx.submodelregistry/basyx.submodelregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/client/factory/SubmodelDescriptorFactory.java index e5487241c..a7376be7c 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/client/factory/SubmodelDescriptorFactory.java +++ b/basyx.submodelregistry/basyx.submodelregistry-client-native/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/client/factory/SubmodelDescriptorFactory.java @@ -53,15 +53,12 @@ public class SubmodelDescriptorFactory { private static final String SUBMODEL_INTERFACE = "SUBMODEL-3.0"; private static final String SUBMODEL_REPOSITORY_PATH = "submodels"; - private Submodel submodel; - private List submodelRepositoryURLs; + private final List submodelRepositoryURLs; + private static AttributeMapper attributeMapper; - private AttributeMapper attributeMapper; - - public SubmodelDescriptorFactory(Submodel submodel, List submodelRepositoryBaseURLs, AttributeMapper attributeMapper) { - this.submodel = submodel; + public SubmodelDescriptorFactory(List submodelRepositoryBaseURLs, AttributeMapper attributeMapper) { this.submodelRepositoryURLs = createSubmodelRepositoryUrls(submodelRepositoryBaseURLs); - this.attributeMapper = attributeMapper; + SubmodelDescriptorFactory.attributeMapper = attributeMapper; } /** @@ -69,7 +66,7 @@ public SubmodelDescriptorFactory(Submodel submodel, List submodelReposit * * @return the created {@link SubmodelDescriptor} */ - public SubmodelDescriptor create() { + public SubmodelDescriptor create(Submodel submodel) { SubmodelDescriptor descriptor = new SubmodelDescriptor(); @@ -77,7 +74,7 @@ public SubmodelDescriptor create() { setIdShort(submodel.getIdShort(), descriptor); - setEndpointItem(submodel.getId(), descriptor); + setEndpointItem(submodel.getId(), descriptor, submodelRepositoryURLs); setDescription(submodel.getDescription(), descriptor); @@ -94,12 +91,7 @@ public SubmodelDescriptor create() { return descriptor; } - public SubmodelDescriptor create(Submodel submodel) { - this.submodel = submodel; - return create(); - } - - private void setDescription(List descriptions, SubmodelDescriptor descriptor) { + private static void setDescription(List descriptions, SubmodelDescriptor descriptor) { if (descriptions == null || descriptions.isEmpty()) return; @@ -107,7 +99,7 @@ private void setDescription(List descriptions, SubmodelDescr descriptor.setDescription(attributeMapper.mapDescription(descriptions)); } - private void setDisplayName(List displayNames, SubmodelDescriptor descriptor) { + private static void setDisplayName(List displayNames, SubmodelDescriptor descriptor) { if (displayNames == null || displayNames.isEmpty()) return; @@ -115,7 +107,7 @@ private void setDisplayName(List displayNames, SubmodelDescr descriptor.setDisplayName(attributeMapper.mapDisplayName(displayNames)); } - private void setExtensions(List extensions, SubmodelDescriptor descriptor) { + private static void setExtensions(List extensions, SubmodelDescriptor descriptor) { if (extensions == null || extensions.isEmpty()) return; @@ -123,7 +115,7 @@ private void setExtensions(List extensions, SubmodelDescriptor descri descriptor.setExtensions(attributeMapper.mapExtensions(extensions)); } - private void setAdministration(AdministrativeInformation administration, SubmodelDescriptor descriptor) { + private static void setAdministration(AdministrativeInformation administration, SubmodelDescriptor descriptor) { if (administration == null) return; @@ -131,7 +123,7 @@ private void setAdministration(AdministrativeInformation administration, Submode descriptor.setAdministration(attributeMapper.mapAdministration(administration)); } - private void setSemanticId(Reference reference, SubmodelDescriptor descriptor) { + private static void setSemanticId(Reference reference, SubmodelDescriptor descriptor) { if (reference == null) return; @@ -139,7 +131,7 @@ private void setSemanticId(Reference reference, SubmodelDescriptor descriptor) { descriptor.setSemanticId(attributeMapper.mapSemanticId(reference)); } - private void setSupplementalSemanticId(List supplementalSemanticIds, SubmodelDescriptor descriptor) { + private static void setSupplementalSemanticId(List supplementalSemanticIds, SubmodelDescriptor descriptor) { if (supplementalSemanticIds == null || supplementalSemanticIds.isEmpty()) return; @@ -147,7 +139,7 @@ private void setSupplementalSemanticId(List supplementalSemanticIds, descriptor.setSupplementalSemanticId(attributeMapper.mapSupplementalSemanticId(supplementalSemanticIds)); } - private void setEndpointItem(String shellId, SubmodelDescriptor descriptor) { + private static void setEndpointItem(String shellId, SubmodelDescriptor descriptor, List submodelRepositoryURLs) { for (String eachUrl : submodelRepositoryURLs) { Endpoint endpoint = new Endpoint(); @@ -159,7 +151,7 @@ private void setEndpointItem(String shellId, SubmodelDescriptor descriptor) { } } - private ProtocolInformation createProtocolInformation(String shellId, String url) { + private static ProtocolInformation createProtocolInformation(String shellId, String url) { String href = String.format("%s/%s", url, Base64UrlEncodedIdentifier.encodeIdentifier(shellId)); ProtocolInformation protocolInformation = new ProtocolInformation(); @@ -169,15 +161,15 @@ private ProtocolInformation createProtocolInformation(String shellId, String url return protocolInformation; } - private void setIdShort(String idShort, SubmodelDescriptor descriptor) { + private static void setIdShort(String idShort, SubmodelDescriptor descriptor) { descriptor.setIdShort(idShort); } - private void setId(String shellId, SubmodelDescriptor descriptor) { + private static void setId(String shellId, SubmodelDescriptor descriptor) { descriptor.setId(shellId); } - private String getProtocol(String endpoint) { + private static String getProtocol(String endpoint) { try { return new URL(endpoint).getProtocol(); } catch (MalformedURLException e) { @@ -185,7 +177,7 @@ private String getProtocol(String endpoint) { } } - private List createSubmodelRepositoryUrls(List submodelRepositoryBaseURLs) { + private static List createSubmodelRepositoryUrls(List submodelRepositoryBaseURLs) { List toReturn = new ArrayList<>(submodelRepositoryBaseURLs.size()); for (String eachUrl : submodelRepositoryBaseURLs) { toReturn.add(RepositoryUrlHelper.createRepositoryUrl(eachUrl, SUBMODEL_REPOSITORY_PATH)); diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/RegistryIntegrationSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/RegistryIntegrationSubmodelRepository.java index 3b9638aed..ead8f6708 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/RegistryIntegrationSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-registry-integration/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/registry/integration/RegistryIntegrationSubmodelRepository.java @@ -91,7 +91,7 @@ public void updateSubmodel(String submodelId, Submodel submodel) throws ElementD @Override public void createSubmodel(Submodel submodel) throws CollidingIdentifierException { - SubmodelDescriptor descriptor = new SubmodelDescriptorFactory(submodel, submodelRepositoryRegistryLink.getSubmodelRepositoryBaseURLs(), attributeMapper).create(); + SubmodelDescriptor descriptor = new SubmodelDescriptorFactory(submodelRepositoryRegistryLink.getSubmodelRepositoryBaseURLs(), attributeMapper).create(submodel); decorated.createSubmodel(submodel); 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 a414b8108..f220de890 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 @@ -104,8 +104,8 @@ public void testExternalUrl() throws ApiException { private SubmodelDescriptor createAndRetrieveDescriptor(Submodel submodel) throws ApiException { registryIntegrationSubmodelRepository.createSubmodel(submodel); - SubmodelDescriptorFactory descriptorFactory = new SubmodelDescriptorFactory(submodel, mockedRegistryLink.getSubmodelRepositoryBaseURLs(), mockedAttributeMapper); - return descriptorFactory.create(); + SubmodelDescriptorFactory descriptorFactory = new SubmodelDescriptorFactory(mockedRegistryLink.getSubmodelRepositoryBaseURLs(), mockedAttributeMapper); + return descriptorFactory.create(submodel); } private Submodel createDummySubmodel() { 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 ab232f2ab..87754a510 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,6 +73,8 @@ public class InMemorySubmodelService implements SubmodelService { private final FileRepository fileRepository; + private final Object submodelLock = new Object(); + /** * Creates the InMemory SubmodelService containing the passed Submodel * @@ -115,20 +117,22 @@ public SubmodelElementValue getSubmodelElementValue(String idShort) throws Eleme @SuppressWarnings("unchecked") @Override public void setSubmodelElementValue(String idShort, SubmodelElementValue value) throws ElementDoesNotExistException { - SubmodelElementValueMapperFactory submodelElementValueFactory = new SubmodelElementValueMapperFactory(); + synchronized (submodelLock) { + 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 { - throwIfSubmodelElementExists(submodelElement.getIdShort()); - - List smElements = submodel.getSubmodelElements(); - smElements.add(submodelElement); - submodel.setSubmodelElements(smElements); + synchronized (submodelLock) { + List smElements = submodel.getSubmodelElements(); + throwIfSubmodelElementExists(submodelElement.getIdShort()); + smElements.add(submodelElement); + } } private void throwIfSubmodelElementExists(String submodelElementId) { @@ -142,46 +146,51 @@ private void throwIfSubmodelElementExists(String submodelElementId) { @Override public void createSubmodelElement(String idShortPath, SubmodelElement submodelElement) throws ElementDoesNotExistException, CollidingIdentifierException { - throwIfSubmodelElementExists(getFullIdShortPath(idShortPath, submodelElement.getIdShort())); - - SubmodelElement parentSme = parser.getSubmodelElementFromIdShortPath(idShortPath); - if (parentSme instanceof SubmodelElementList) { - 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); - return; + 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); + } } } @Override public void updateSubmodelElement(String idShortPath, SubmodelElement submodelElement) { - deleteSubmodelElement(idShortPath); + synchronized (submodelLock) { + deleteSubmodelElement(idShortPath); - String idShortPathParentSME = parser.getIdShortPathOfParentElement(idShortPath); - if (idShortPath.equals(idShortPathParentSME)) { - createSubmodelElement(submodelElement); - return; + String idShortPathParentSME = parser.getIdShortPathOfParentElement(idShortPath); + if (idShortPath.equals(idShortPathParentSME)) { + createSubmodelElement(submodelElement); + return; + } + createSubmodelElement(idShortPathParentSME, submodelElement); } - createSubmodelElement(idShortPathParentSME, submodelElement); } @Override public void deleteSubmodelElement(String idShortPath) throws ElementDoesNotExistException { - deleteAssociatedFileIfAny(idShortPath); + synchronized (submodelLock) { + deleteAssociatedFileIfAny(idShortPath); - if (!helper.isNestedIdShortPath(idShortPath)) { - deleteFlatSubmodelElement(idShortPath); - return; + if (!helper.isNestedIdShortPath(idShortPath)) { + deleteFlatSubmodelElement(idShortPath); + return; + } + deleteNestedSubmodelElement(idShortPath); } - deleteNestedSubmodelElement(idShortPath); } private void deleteNestedSubmodelElement(String idShortPath) { @@ -259,44 +268,48 @@ public java.io.File getFileByPath(String idShortPath) throws ElementDoesNotExist @Override public void setFileValue(String idShortPath, String fileName, InputStream inputStream) throws ElementDoesNotExistException, ElementNotAFileException { - SubmodelElement submodelElement = getSubmodelElement(idShortPath); + synchronized (submodelLock) { + 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); - - if(fileRepository.exists(fileMetadata.getFileName())) - fileRepository.delete(fileMetadata.getFileName()); + FileMetadata fileMetadata = new FileMetadata(uniqueFileName, fileSmElement.getContentType(), inputStream); - String filePath = fileRepository.save(fileMetadata); + if (fileRepository.exists(fileMetadata.getFileName())) + fileRepository.delete(fileMetadata.getFileName()); - FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + String filePath = fileRepository.save(fileMetadata); - setSubmodelElementValue(idShortPath, fileValue); + FileBlobValue fileValue = new FileBlobValue(fileSmElement.getContentType(), filePath); + + setSubmodelElementValue(idShortPath, fileValue); + } } @Override public void deleteFileValue(String idShortPath) throws ElementDoesNotExistException, ElementNotAFileException, FileDoesNotExistException { - SubmodelElement submodelElement = getSubmodelElement(idShortPath); + synchronized (submodelLock) { + 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