diff --git a/.github/workflows/maven-publish-snapshot.yml b/.github/workflows/maven-publish-snapshot.yml index 77e4f548..ded98ec9 100644 --- a/.github/workflows/maven-publish-snapshot.yml +++ b/.github/workflows/maven-publish-snapshot.yml @@ -37,7 +37,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/maven-run-tests.yml b/.github/workflows/maven-run-tests.yml index b297cda0..19f93c8d 100644 --- a/.github/workflows/maven-run-tests.yml +++ b/.github/workflows/maven-run-tests.yml @@ -49,7 +49,7 @@ jobs: - 27017:27017 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 11 uses: actions/setup-java@v3 with: diff --git a/.gitignore b/.gitignore index 42416d97..aef5e14f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ .lck *.class +/basyx.components/basyx.components.docker/basyx.components.AASServer/fileSmeIdShort.xml +/basyx.components/basyx.components.docker/basyx.components.AASServer/mySubmodelId-fileSmeIdShort.xml +/basyx.components/basyx.components.docker/basyx.components.registry/.moquette_uuid + /components/basys.components/WebContent/WEB-INF/lib/jdbc/postgresql-42.2.2.jar /components/basys.components/WebContent/WEB-INF/lib/jersey/ext/aopalliance-repackaged-2.5.0-b42.jar /components/basys.components/WebContent/WEB-INF/lib/jersey/ext/cdi-api-1.1.jar diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/pom.xml b/basyx.components/basyx.components.docker/basyx.components.AASServer/pom.xml index 36777c42..5d457d3a 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/pom.xml +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/pom.xml @@ -7,7 +7,7 @@ org.eclipse.basyx basyx.components.docker - 1.4.0 + 1.5.0 basyx.components.AASServer @@ -80,14 +80,14 @@ org.mongodb mongodb-driver-sync - 4.9.0 + 4.10.2 org.springframework.data spring-data-mongodb - 3.4.10 + 3.4.15 @@ -108,7 +108,7 @@ io.moquette moquette-broker - 0.16 + 0.17 test @@ -121,14 +121,14 @@ org.eclipse.basyx basyx.components.registry - 1.4.0 + 1.5.0 test com.tngtech.keycloakmock mock test - 0.13.0 + 0.15.2 diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java index 9ef7a4c7..7c8d78c4 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/AASServerComponent.java @@ -24,20 +24,24 @@ ******************************************************************************/ package org.eclipse.basyx.components.aas; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import javax.servlet.http.HttpServlet; import javax.xml.parsers.ParserConfigurationException; +import org.apache.catalina.Context; +import org.apache.catalina.core.StandardContext; import org.apache.catalina.servlets.DefaultServlet; +import org.apache.catalina.startup.Tomcat; import org.apache.commons.io.IOUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.basyx.aas.aggregator.AASAggregatorAPIHelper; @@ -61,7 +65,6 @@ import org.eclipse.basyx.components.aas.aascomponent.IAASServerFeature; import org.eclipse.basyx.components.aas.aascomponent.InMemoryAASServerComponentFactory; import org.eclipse.basyx.components.aas.aascomponent.MongoDBAASServerComponentFactory; -import org.eclipse.basyx.components.aas.aasx.AASXPackageManager; import org.eclipse.basyx.components.aas.authorization.AuthorizedAASServerFeature; import org.eclipse.basyx.components.aas.authorization.internal.AuthorizedAASServerFeatureFactory; import org.eclipse.basyx.components.aas.authorization.internal.AuthorizedDefaultServlet; @@ -93,6 +96,7 @@ import org.eclipse.basyx.vab.exception.provider.ProviderException; import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; import org.eclipse.basyx.vab.modelprovider.VABPathTools; +import org.eclipse.basyx.vab.protocol.http.server.BaSyxChildContext; import org.eclipse.basyx.vab.protocol.http.server.BaSyxContext; import org.eclipse.basyx.vab.protocol.http.server.BaSyxHTTPServer; import org.eclipse.basyx.vab.protocol.http.server.VABHTTPInterface; @@ -107,7 +111,6 @@ * * @author schnicke, espen, fried, fischer, danish, wege */ -@SuppressWarnings("deprecation") public class AASServerComponent implements IComponent { private static Logger logger = LoggerFactory.getLogger(AASServerComponent.class); @@ -121,16 +124,17 @@ public class AASServerComponent implements IComponent { private BaSyxMongoDBConfiguration mongoDBConfig; private BaSyxSecurityConfiguration securityConfig; - private List aasServerFeatureList = new ArrayList(); - - // Initial AASBundle - protected Collection aasBundles; + private List aasServerFeatureList = new ArrayList<>(); + protected List> aasBundles = new ArrayList<>(); private IAASAggregator aggregator; // Watcher for AAS Aggregator functionality private boolean isAASXUploadEnabled = false; private static final String PREFIX_SUBMODEL_PATH = "/aas/submodels/"; + private static final String AASX_RES_FILE_CONTEXT_PATH = AASXToMetamodelConverter.TEMP_DIRECTORY; + private static final String AASX_RES_FILE_DOCBASE_PATH = VABPathTools.append(System.getProperty("java.io.tmpdir"), AASX_RES_FILE_CONTEXT_PATH); + private static final String AASX_RES_FILE_SERVLET_MAPPING_PATTERN = "/*"; /** * Constructs an empty AAS server using the passed context @@ -210,7 +214,8 @@ public void setRegistry(IAASRegistry registry) { * The bundles that will be loaded during startup */ public void setAASBundles(Collection aasBundles) { - this.aasBundles = aasBundles; + this.aasBundles = new ArrayList<>(); + this.aasBundles.add(aasBundles); } /** @@ -220,7 +225,9 @@ public void setAASBundles(Collection aasBundles) { * The bundle that will be loaded during startup */ public void setAASBundle(AASBundle aasBundle) { - this.aasBundles = Collections.singleton(aasBundle); + this.aasBundles = new ArrayList<>(); + Collection firstBundleSet = Collections.singleton(aasBundle); + this.aasBundles.add(firstBundleSet); } /** @@ -251,11 +258,12 @@ public void startComponent() { // An initial AAS has been loaded from the drive? if (aasBundles != null) { - // 1. Also provide the files - context.addServletMapping("/files/*", createDefaultServlet()); + createBasyxResourceDirectoryIfNotExists(); + + addAasxFilesResourceServlet(context); // 2. Fix the file paths according to the servlet configuration - modifyFilePaths(contextConfig.getHostname(), contextConfig.getPort(), contextConfig.getContextPath()); + modifyFilePaths(contextConfig.getHostname(), contextConfig.getPort(), getRootFilePathWithContext(contextConfig.getContextPath())); registerWhitelistedSubmodels(); } @@ -267,6 +275,10 @@ public void startComponent() { registerPreexistingAASAndSMIfPossible(); } + private String getRootFilePathWithContext(String contextPath) { + return VABPathTools.append(contextPath, AASX_RES_FILE_CONTEXT_PATH); + } + private DefaultServlet createDefaultServlet() { if (aasConfig.isAuthorizationEnabled()) { final AuthorizedDefaultServletParams params = getAuthorizedDefaultServletParams(); @@ -543,17 +555,18 @@ private Set loadBundleFromJSON(String jsonPath) throws IOException { return new JSONAASBundleFactory(jsonContent).create(); } - private static Set loadBundleFromAASX(String aasxPath) throws IOException, ParserConfigurationException, SAXException, InvalidFormatException, URISyntaxException { + private static Set loadBundleFromAASX(String aasxPath, String childFilePath) throws IOException, ParserConfigurationException, SAXException, InvalidFormatException, URISyntaxException { logger.info("Loading aas from aasx \"" + aasxPath + "\""); // Instantiate the aasx package manager - AASXToMetamodelConverter packageManager = new AASXPackageManager(aasxPath); - - // Unpack the files referenced by the aas - packageManager.unzipRelatedFiles(); + try (AASXToMetamodelConverter packageManager = new AASXToMetamodelConverter(aasxPath)) { + // Unpack the files referenced by the aas + packageManager.unzipRelatedFilesToChildPath(childFilePath); - // Retrieve the aas from the package - return packageManager.retrieveAASBundles(); + // Retrieve the aas from the package + return packageManager.retrieveAASBundles(); + } + } private void addAASServerFeaturesToContext(BaSyxContext context) { @@ -562,13 +575,21 @@ private void addAASServerFeaturesToContext(BaSyxContext context) { } } + private Collection getFlatAASBundles() { + Collection result = new ArrayList(); + for (Collection bundle : this.aasBundles) { + result.addAll(bundle); + } + return result; + } + private VABHTTPInterface createAggregatorServlet() { aggregator = createAASAggregator(); loadAASBundles(); if (aasBundles != null) { try (final var ignored = ElevatedCodeAuthentication.enterElevatedCodeAuthenticationArea()) { - AASBundleHelper.integrate(aggregator, aasBundles); + AASBundleHelper.integrate(aggregator, getFlatAASBundles()); } } @@ -614,7 +635,7 @@ private List createAASServerDecoratorList() { } private void loadAASBundles() { - if (aasBundles != null) { + if (!aasBundles.isEmpty()) { return; } @@ -622,22 +643,31 @@ private void loadAASBundles() { aasBundles = loadAASFromSource(aasSources); } - private Set loadAASFromSource(List aasSources) { + private List> loadAASFromSource(List aasSources) { if (aasSources.isEmpty()) { - return Collections.emptySet(); + return new ArrayList<>(); } - Set aasBundlesSet = new HashSet<>(); + List> aasBundlesSet = new ArrayList<>(); - aasSources.stream().map(this::loadBundleFromFile).forEach(aasBundlesSet::addAll); + for (int i = 0; i < aasSources.size(); i++) { + String aasSource = aasSources.get(i); + String subFilePath = getAASXFileSubPath(i); + Set loadedBundles = loadBundleFromFile(aasSource, subFilePath); + aasBundlesSet.add(loadedBundles); + } return aasBundlesSet; } - private Set loadBundleFromFile(String aasSource) { + private String getAASXFileSubPath(int aasxIndex) { + return "aasx" + Integer.toString(aasxIndex); + } + + private Set loadBundleFromFile(String aasSource, String childFilePath) { try { if (aasSource.endsWith(".aasx")) { - return loadBundleFromAASX(aasSource); + return loadBundleFromAASX(aasSource, childFilePath); } else if (aasSource.endsWith(".json")) { return loadBundleFromJSON(aasSource); } else if (aasSource.endsWith(".xml")) { @@ -730,7 +760,8 @@ private String getSMEndpoint(IIdentifier smId) { } private String getSMIdShortFromSMId(IIdentifier smId) { - for (AASBundle bundle : aasBundles) { + Collection flatAasBundles = getFlatAASBundles(); + for (AASBundle bundle : flatAasBundles) { for (ISubmodel sm : bundle.getSubmodels()) { if (smId.getId().equals(sm.getIdentification().getId())) { return sm.getIdShort(); @@ -741,7 +772,8 @@ private String getSMIdShortFromSMId(IIdentifier smId) { } private String getAASIdFromSMId(IIdentifier smId) { - for (AASBundle bundle : aasBundles) { + Collection flatAasBundles = getFlatAASBundles(); + for (AASBundle bundle : flatAasBundles) { for (ISubmodel sm : bundle.getSubmodels()) { if (smId.getId().equals(sm.getIdentification().getId())) { return bundle.getAAS().getIdentification().getId(); @@ -756,11 +788,18 @@ private String getAASIdFromSMId(IIdentifier smId) { * configuration */ private void modifyFilePaths(String hostName, int port, String rootPath) { - rootPath = rootPath + "/files"; - for (AASBundle bundle : aasBundles) { + for (int i = 0; i < aasBundles.size(); i++) { + Collection bundleSet = aasBundles.get(i); + String bundleFileRootPath = VABPathTools.concatenatePaths(rootPath, getAASXFileSubPath(i), "files"); + modifyFilePathsInBundleSet(bundleSet, hostName, port, bundleFileRootPath); + } + } + + private void modifyFilePathsInBundleSet(Collection bundleSet, String hostName, int port, String bundleFileRootPath) { + for (AASBundle bundle : bundleSet) { Set submodels = bundle.getSubmodels(); for (ISubmodel sm : submodels) { - SubmodelFileEndpointLoader.setRelativeFileEndpoints(sm, hostName, port, rootPath); + SubmodelFileEndpointLoader.setRelativeFileEndpoints(sm, hostName, port, bundleFileRootPath); } } } @@ -769,10 +808,41 @@ private String getMqttAASClientId() { if (aasBundles == null || aasBundles.isEmpty()) { return "defaultNoShellId"; } - return aasBundles.stream().findFirst().get().getAAS().getIdShort(); + Collection firstBundleSet = aasBundles.get(0); + if (firstBundleSet == null || firstBundleSet.isEmpty()) { + return "defaultNoShellId"; + } + return firstBundleSet.stream().findFirst().get().getAAS().getIdShort(); } private String getMqttSubmodelClientId() { return getMqttAASClientId() + "/submodelAggregator"; } + + private void addAasxFilesResourceServlet(BaSyxContext context) { + HttpServlet httpServlet = createDefaultServlet(); + + String childContextPath = VABPathTools.append(contextConfig.getContextPath(), AASX_RES_FILE_CONTEXT_PATH); + Context childContext = createChildContextForAasxResourceFiles(childContextPath, AASX_RES_FILE_DOCBASE_PATH); + + context.addChildContext(new BaSyxChildContext(childContext, httpServlet, AASX_RES_FILE_SERVLET_MAPPING_PATTERN)); + } + + private Context createChildContextForAasxResourceFiles(String childContextPath, String childDocbasePath) { + Context childContext = new StandardContext(); + childContext.setPath(childContextPath); + childContext.setDocBase(childDocbasePath); + childContext.addLifecycleListener(new Tomcat.FixContextListener()); + return childContext; + } + + private void createBasyxResourceDirectoryIfNotExists() { + File directory = new File(AASX_RES_FILE_DOCBASE_PATH); + + if (directory.exists()) + return; + + directory.mkdir(); + } + } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/authorization/internal/PathTargetInformation.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/authorization/internal/PathTargetInformation.java index 9facfed6..a6a1ac1f 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/authorization/internal/PathTargetInformation.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/authorization/internal/PathTargetInformation.java @@ -31,7 +31,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.eclipse.basyx.extensions.shared.authorization.internal.TagTargetInformation; import org.eclipse.basyx.extensions.shared.authorization.internal.TargetInformation; import com.fasterxml.jackson.annotation.JsonCreator; @@ -73,7 +72,7 @@ public boolean equals(final Object o) { return true; } - if (!(o instanceof TagTargetInformation)) { + if (!(o instanceof PathTargetInformation)) { return false; } @@ -89,6 +88,6 @@ public int hashCode() { @Override public String toString() { - return new StringBuilder("BaSyxObjectTargetInformation{").append("tag='").append(path).append('\'').append('}').toString(); + return new StringBuilder("PathTargetInformation{").append("path='").append(path).append('\'').append('}').toString(); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/configuration/BaSyxAASServerConfiguration.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/configuration/BaSyxAASServerConfiguration.java index 0402af6d..bb4b6a49 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/configuration/BaSyxAASServerConfiguration.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/configuration/BaSyxAASServerConfiguration.java @@ -27,6 +27,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,10 +71,11 @@ public class BaSyxAASServerConfiguration extends BaSyxConfiguration { public static final String DEFAULT_CLIENT_SCOPES = "[]"; public static final String DEFAULT_PROPERTY_DELEGATION = FEATURE_ENABLED; - // Configuration keys public static final String REGISTRY = "registry.path"; + @Deprecated public static final String HOSTPATH = "registry.host"; + public static final String HOSTPATH_NEW = "aas.externalurl"; public static final String SUBMODELS = "registry.submodels"; public static final String ID = "aas.id"; public static final String BACKEND = "aas.backend"; @@ -100,7 +102,7 @@ public static Map getDefaultProperties() { defaultProps.put(BACKEND, DEFAULT_BACKEND); defaultProps.put(SOURCE, DEFAULT_SOURCE); defaultProps.put(REGISTRY, DEFAULT_REGISTRY); - defaultProps.put(HOSTPATH, DEFAULT_HOSTPATH); + defaultProps.put(HOSTPATH_NEW, DEFAULT_HOSTPATH); defaultProps.put(SUBMODELS, DEFAULT_SUBMODELS); defaultProps.put(EVENTS, DEFAULT_EVENTS); defaultProps.put(AASX_UPLOAD, DEFAULT_AASX_UPLOAD); @@ -117,7 +119,7 @@ public static Map getDefaultProperties() { * Empty Constructor - use default values */ public BaSyxAASServerConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), getPropertiesExcludedFromLogging()); } /** @@ -129,7 +131,7 @@ public BaSyxAASServerConfiguration() { * The file source for the AASServer (e.g. an .aasx file) */ public BaSyxAASServerConfiguration(AASServerBackend backend, String source) { - super(getDefaultProperties()); + this(); setAASBackend(backend); setAASSourceAsList(source); } @@ -171,14 +173,11 @@ public BaSyxAASServerConfiguration(AASServerBackend backend, String source, Stri * Constructor with predefined value map */ public BaSyxAASServerConfiguration(Map values) { - super(values); + super(values, getPropertiesExcludedFromLogging()); } public void loadFromEnvironmentVariables() { - String[] properties = { - REGISTRY, BACKEND, SOURCE, EVENTS, HOSTPATH, AASX_UPLOAD, AUTHORIZATION, TOKEN_ENDPOINT, - CLIENT_ID, CLIENT_SECRET, CLIENT_SCOPES, PROPERTY_DELEGATION, ID - }; + String[] properties = { REGISTRY, BACKEND, SOURCE, EVENTS, HOSTPATH, HOSTPATH_NEW, AASX_UPLOAD, AUTHORIZATION, TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, CLIENT_SCOPES, PROPERTY_DELEGATION, ID }; loadFromEnvironmentVariables(ENV_PREFIX, properties); } @@ -186,17 +185,17 @@ public void loadFromDefaultSource() { loadFileOrDefaultResource(DEFAULT_FILE_KEY, DEFAULT_CONFIG_PATH); loadFromEnvironmentVariables(); } - + public IAuthorizationSupplier configureAndGetAuthorizationSupplier() { - if(!isAuthorizationCredentialsForSecuredRegistryConfigured()) { + if (!isAuthorizationCredentialsForSecuredRegistryConfigured()) { throw new AuthorizationConfigurationException("Authorization credentials for the secured registry is not configured"); } - + return new OAuth2ClientCredentialsBasedAuthorizationSupplier(getTokenEndpoint(), getClientId(), getClientSecret(), getClientScopes()); } - + public String getAASId() { - return getProperty(ID); + return getProperty(ID); } public AASServerBackend getAASBackend() { @@ -282,11 +281,15 @@ public void setSubmodels(List submodels) { } public String getHostpath() { - return getProperty(HOSTPATH); + if (getProperty(HOSTPATH_NEW).equals("") && getProperty(HOSTPATH) != null) { + return getProperty(HOSTPATH); + } else { + return getProperty(HOSTPATH_NEW); + } } public void setHostpath(String hostPath) { - setProperty(HOSTPATH, hostPath); + setProperty(HOSTPATH_NEW, hostPath); } @SuppressWarnings("unchecked") @@ -298,7 +301,7 @@ private List parseFromJson(String property) { return fromJson; } } - + private T parseFromJson(String property, Class classTypeT) { T fromJson = new Gson().fromJson(property, (Type) classTypeT); if (fromJson == null) { @@ -327,7 +330,7 @@ public void disableAuthorization() { public String getTokenEndpoint() { return getProperty(TOKEN_ENDPOINT); } - + public void setTokenEndpoint(String tokenEndpoint) { setProperty(TOKEN_ENDPOINT, tokenEndpoint); } @@ -335,7 +338,7 @@ public void setTokenEndpoint(String tokenEndpoint) { public String getClientId() { return getProperty(CLIENT_ID); } - + public void setClientId(String clientId) { setProperty(CLIENT_ID, clientId); } @@ -343,7 +346,7 @@ public void setClientId(String clientId) { public String getClientSecret() { return getProperty(CLIENT_SECRET); } - + public void setClientSecret(String clientSecret) { setProperty(CLIENT_SECRET, clientSecret); } @@ -352,11 +355,11 @@ public void setClientSecret(String clientSecret) { public Set getClientScopes() { return parseFromJson(getProperty(CLIENT_SCOPES), Set.class); } - + public void setClientScopes(String clientScopes) { setProperty(CLIENT_SCOPES, clientScopes); } - + public void enablePropertyDelegation() { setProperty(PROPERTY_DELEGATION, FEATURE_ENABLED); } @@ -364,11 +367,11 @@ public void enablePropertyDelegation() { public void disablePropertyDelegation() { setProperty(PROPERTY_DELEGATION, FEATURE_DISABLED); } - + public boolean isPropertyDelegationEnabled() { return getProperty(PROPERTY_DELEGATION).equals(FEATURE_ENABLED); } - + public boolean isAuthorizationCredentialsForSecuredRegistryConfigured() { return isTokenEndpointConfigured() && isClientIdConfigured() && isClientSecretConfigured() && isScopeConfigured(); } @@ -376,7 +379,7 @@ public boolean isAuthorizationCredentialsForSecuredRegistryConfigured() { private boolean isTokenEndpointConfigured() { return getProperty(TOKEN_ENDPOINT) != null && !getProperty(TOKEN_ENDPOINT).isEmpty(); } - + private boolean isClientIdConfigured() { return getProperty(CLIENT_ID) != null && !getProperty(CLIENT_ID).isEmpty(); } @@ -384,8 +387,13 @@ private boolean isClientIdConfigured() { private boolean isClientSecretConfigured() { return getProperty(CLIENT_SECRET) != null && !getProperty(CLIENT_SECRET).isEmpty(); } - + private boolean isScopeConfigured() { return getProperty(CLIENT_SCOPES) != null && !getProperty(CLIENT_SCOPES).isEmpty(); } + + private static List getPropertiesExcludedFromLogging() { + return Collections.singletonList(CLIENT_SECRET); + } + } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/internal/StorageSubmodelAPI.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/internal/StorageSubmodelAPI.java new file mode 100644 index 00000000..0df5f9f8 --- /dev/null +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/internal/StorageSubmodelAPI.java @@ -0,0 +1,212 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.basyx.components.aas.internal; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.basyx.extensions.internal.storage.BaSyxStorageAPI; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.operation.IOperation; +import org.eclipse.basyx.submodel.metamodel.facade.submodelelement.SubmodelElementFacadeFactory; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.metamodel.map.submodelelement.operation.Operation; +import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPI; +import org.eclipse.basyx.submodel.restapi.operation.DelegatedInvocationManager; +import org.eclipse.basyx.submodel.restapi.vab.VABSubmodelAPI; +import org.eclipse.basyx.vab.exception.provider.MalformedRequestException; +import org.eclipse.basyx.vab.modelprovider.VABPathTools; +import org.eclipse.basyx.vab.modelprovider.lambda.VABLambdaProvider; + +/** + * Abstract submodel api for storage backends. + * + * @author fischer + * + */ +public abstract class StorageSubmodelAPI implements ISubmodelAPI { + protected BaSyxStorageAPI storageApi; + private String identificationId; + private DelegatedInvocationManager invocationManager; + + protected StorageSubmodelAPI(BaSyxStorageAPI storageAPI, String identificationId, DelegatedInvocationManager invocatonManager) { + this.storageApi = storageAPI; + this.identificationId = identificationId; + this.invocationManager = invocatonManager; + } + + public String getSubmodelId() { + return identificationId; + } + + /** + * Sets the submodel id, so that this API points to the submodel with smId. Can + * be changed to point to a different submodel in the database. + * + * @param identificationId + */ + public void setSubmodelId(String identificationId) { + this.identificationId = identificationId; + } + + /** + * Depending on whether the model is already in the db, this method inserts or + * replaces the existing data. The new submodel id for this API is taken from + * the given submodel. + * + * @param submodel + */ + public void setSubmodel(Submodel submodel) { + String submodelId = submodel.getIdentification().getId(); + setSubmodelId(submodelId); + storageApi.update(submodel, submodelId); + } + + @Override + public Submodel getSubmodel() { + return storageApi.retrieve(identificationId); + } + + @Override + public void addSubmodelElement(ISubmodelElement elem) { + Submodel submodel = (Submodel) getSubmodel(); + submodel.addSubmodelElement(elem); + storageApi.update(submodel, identificationId); + } + + @Override + public void addSubmodelElement(String idShortPath, ISubmodelElement elem) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + api.addSubmodelElement(idShortPath, elem); + storageApi.update(api.getSubmodel(), identificationId); + } + + + @Override + public ISubmodelElement getSubmodelElement(String idShortPath) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + return api.getSubmodelElement(idShortPath); + } + + @Override + public void deleteSubmodelElement(String idShortPath) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + api.deleteSubmodelElement(idShortPath); + storageApi.update(api.getSubmodel(), identificationId); + } + + + @Override + public Collection getOperations() { + Submodel submodel = (Submodel) getSubmodel(); + return submodel.getOperations().values(); + } + + @Override + public Collection getSubmodelElements() { + Submodel submodel = (Submodel) getSubmodel(); + return submodel.getSubmodelElements().values(); + } + + @Override + public void updateSubmodelElement(String idShortPath, Object newValue) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + api.updateSubmodelElement(idShortPath, newValue); + storageApi.update(api.getSubmodel(), identificationId); + } + + + @Override + public Object getSubmodelElementValue(String idShortPath) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + return api.getSubmodelElementValue(idShortPath); + } + + @Deprecated + @SuppressWarnings("unchecked") + protected Object unwrapParameter(Object parameter) { + if (parameter instanceof Map) { + Map map = (Map) parameter; + // Parameters have a strictly defined order and may not be omitted at all. + // Enforcing the structure with valueType is ok, but we should unwrap null + // values, too. + if (map.get("valueType") != null && map.containsKey("value")) { + return map.get("value"); + } + } + return parameter; + } + + @SuppressWarnings("unchecked") + @Override + public Object invokeOperation(String idShortPath, Object... params) { + Operation operation = (Operation) SubmodelElementFacadeFactory.createSubmodelElement((Map) getSubmodelElement(idShortPath)); + if (!DelegatedInvocationManager.isDelegatingOperation(operation)) { + throw new MalformedRequestException("This backend supports only delegating operations."); + } + return invocationManager.invokeDelegatedOperation(operation, params); + } + + @Override + public Object invokeAsync(String idShortPath, Object... params) { + List idShorts = idShortsPathAsList(idShortPath); + return invokeNestedOperationAsync(idShorts, params); + } + + private List idShortsPathAsList(String idShortPath) { + String[] splittedIdShortPath = VABPathTools.splitPath(idShortPath); + return Arrays.asList(splittedIdShortPath); + } + + private Object invokeNestedOperationAsync(List idShorts, Object... params) { + throw new MalformedRequestException("Invoke not supported by this backend"); + } + + @Override + public Object getOperationResult(String idShort, String requestId) { + throw new MalformedRequestException("Invoke not supported by this backend"); + } + + @SuppressWarnings("unchecked") + @Override + public java.io.File getSubmodelElementFile(String idShortPath) { + Map submodelElement = (Map) getSubmodelElement(idShortPath); + String parentKey = getSubmodel().getIdentification().getId(); + return storageApi.getFile(idShortPath, parentKey, submodelElement); + } + + @Override + public void uploadSubmodelElementFile(String idShortPath, InputStream fileStream) { + VABSubmodelAPI api = new VABSubmodelAPI(new VABLambdaProvider(getSubmodel())); + ISubmodelElement element = api.getSubmodelElement(idShortPath); + String fileName = storageApi.writeFile(idShortPath, getSubmodel().getIdentification().getId(), fileStream, element); + updateSubmodelElement(idShortPath, fileName); + } + +} diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPI.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPI.java index 5ee5e2e4..e99ce950 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPI.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2021, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,85 +24,83 @@ ******************************************************************************/ package org.eclipse.basyx.components.aas.mongodb; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; - import java.util.Collection; -import java.util.Iterator; -import java.util.List; +import java.util.Optional; import org.eclipse.basyx.aas.metamodel.api.IAssetAdministrationShell; import org.eclipse.basyx.aas.metamodel.map.AssetAdministrationShell; import org.eclipse.basyx.aas.restapi.api.IAASAPI; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; import org.eclipse.basyx.submodel.metamodel.api.reference.IKey; import org.eclipse.basyx.submodel.metamodel.api.reference.IReference; -import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; -import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; -import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * Implements the IAASAPI for a mongoDB backend. * - * @author espen + * @author espen, jungjan, witt */ public class MongoDBAASAPI implements IAASAPI { + private Logger logger = LoggerFactory.getLogger(getClass()); private static final String DEFAULT_CONFIG_PATH = "mongodb.properties"; - private static final String AASIDPATH = Identifiable.IDENTIFICATION + "." + Identifier.ID; - protected BaSyxMongoDBConfiguration config; - protected MongoOperations mongoOps; - protected String collection; - protected String aasId; + protected String collectionName; + private MongoDBBaSyxStorageAPI storageApi; + private String shellIdentificationId; /** * Receives the path of the configuration.properties file in its constructor. * * @param config - * @deprecated Use the new constructor using a MongoClient */ - @Deprecated - public MongoDBAASAPI(BaSyxMongoDBConfiguration config, String aasId) { - this(config, aasId, MongoClients.create(config.getConnectionUrl())); + public MongoDBAASAPI(BaSyxMongoDBConfiguration config, String shellIdentificationId, MongoClient client) { + this(MongoDBBaSyxStorageAPIFactory.create(config.getAASCollection(), AssetAdministrationShell.class, config, client), shellIdentificationId); + } + + public MongoDBAASAPI(MongoDBBaSyxStorageAPI mongoDBStorageAPI, String shellIdentificationId) { + super(); + this.storageApi = mongoDBStorageAPI; + this.shellIdentificationId = shellIdentificationId; } /** - * Receives the path of the configuration.properties file in its constructor. - * - * @param config + * Receives the path of the .properties file in its constructor from a resource. */ - public MongoDBAASAPI(BaSyxMongoDBConfiguration config, String aasId, MongoClient client) { - this.setConfiguration(config, client); - this.setAASId(aasId); + public MongoDBAASAPI(String resourceConfigPath, String shellIdentificationId, MongoClient client) { + this(MongoDBBaSyxStorageAPIFactory.create(configFromResource(resourceConfigPath).getSubmodelCollection(), AssetAdministrationShell.class, configFromResource(resourceConfigPath), client), shellIdentificationId); } /** - * Receives the path of the .properties file in its constructor from a resource. + * Constructor using default MongoDB connections + */ + public MongoDBAASAPI(String shellIdentificationId, MongoClient client) { + this(DEFAULT_CONFIG_PATH, shellIdentificationId, client); + } + + /** + * Receives the path of the configuration.properties file in its constructor. * + * @param config * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAPI(String resourceConfigPath, String aasId) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.setConfiguration(config); - this.setAASId(aasId); + public MongoDBAASAPI(BaSyxMongoDBConfiguration config, String shellIdentificationId) { + this(MongoDBBaSyxStorageAPIFactory.create(config.getAASCollection(), AssetAdministrationShell.class, config), shellIdentificationId); } /** * Receives the path of the .properties file in its constructor from a resource. + * + * @deprecated Use the new constructor using a MongoClient */ - public MongoDBAASAPI(String resourceConfigPath, String aasId, MongoClient client) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.setConfiguration(config, client); - this.setAASId(aasId); + @Deprecated + public MongoDBAASAPI(String resourceConfigPath, String shellIdentificationId) { + this(MongoDBBaSyxStorageAPIFactory.create(configFromResource(resourceConfigPath).getSubmodelCollection(), AssetAdministrationShell.class, configFromResource(resourceConfigPath)), shellIdentificationId); } /** @@ -111,113 +109,94 @@ public MongoDBAASAPI(String resourceConfigPath, String aasId, MongoClient client * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAPI(String aasId) { - this(DEFAULT_CONFIG_PATH, aasId); + public MongoDBAASAPI(String shellIdentificationId) { + this(DEFAULT_CONFIG_PATH, shellIdentificationId); } - /** - * Constructor using default MongoDB connections - */ - public MongoDBAASAPI(String aasId, MongoClient client) { - this(DEFAULT_CONFIG_PATH, aasId, client); + private static BaSyxMongoDBConfiguration configFromResource(String resourceConfigPath) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + config.loadFromResource(resourceConfigPath); + return config; } - @Deprecated + /** + * This Method enables to switch the BaSyxMongoDBConfiguration at runtime + * + * @param config + */ public void setConfiguration(BaSyxMongoDBConfiguration config) { - MongoClient client = MongoClients.create(config.getConnectionUrl()); - setConfiguration(config, client); + this.collectionName = config.getAASCollection(); + MongoDBBaSyxStorageAPIFactory storageApiFactory = new MongoDBBaSyxStorageAPIFactory<>(config, AssetAdministrationShell.class, this.collectionName); + this.storageApi = storageApiFactory.create(); } + /** + * This Method enables to switch the BaSyxMongoDBConfiguration at runtime + * + * @param config + */ public void setConfiguration(BaSyxMongoDBConfiguration config, MongoClient client) { - this.config = config; - this.mongoOps = new MongoTemplate(client, config.getDatabase()); - this.collection = config.getAASCollection(); + this.collectionName = config.getAASCollection(); + MongoDBBaSyxStorageAPIFactory storageApiFactory = new MongoDBBaSyxStorageAPIFactory<>(config, AssetAdministrationShell.class, this.collectionName, client); + this.storageApi = storageApiFactory.create(); } /** - * Sets the aas id, so that this API points to the aas with aasId. Can be - * changed to point to a different aas in the database. + * Sets the shellIdentificationId, so that this API points to the shell with + * shellIdentificationId. Can be changed to point to a different shell in the + * database. * - * @param aasId + * @param shellIdentificationId */ - public void setAASId(String aasId) { - this.aasId = aasId; + public void setAASId(String shellIdentificationId) { + this.shellIdentificationId = shellIdentificationId; } /** * Depending on whether the model is already in the db, this method inserts or - * replaces the existing data. The new aas id for this API is taken from the - * given aas. + * replaces the existing data. The new shell id for this API is taken from the + * given shell. * - * @param aas + * @param shell */ - public void setAAS(AssetAdministrationShell aas) { - String id = aas.getIdentification().getId(); + public void setAAS(AssetAdministrationShell shell) { + String id = shell.getIdentification().getId(); this.setAASId(id); - - Query hasId = query(where(AASIDPATH).is(aasId)); - // Try to replace if already present - otherwise: insert it - Object replaced = mongoOps.findAndReplace(hasId, aas, collection); - if (replaced == null) { - mongoOps.insert(aas, collection); - } - // Remove mongoDB-specific map attribute from AAS. - // mongoOps modify aas on save - thus _id has to be removed here... - aas.remove("_id"); + storageApi.createOrUpdate(shell); } @Override public IAssetAdministrationShell getAAS() { - Query hasId = query(where(AASIDPATH).is(aasId)); - AssetAdministrationShell aas = mongoOps.findOne(hasId, AssetAdministrationShell.class, collection); - if (aas == null) { - throw new ResourceNotFoundException("The AAS " + aasId + " could not be found in the database."); - } - // Remove mongoDB-specific map attribute from AAS - aas.remove("_id"); - return aas; + return storageApi.retrieve(shellIdentificationId); } @Override - public void addSubmodel(IReference submodel) { - // Get AAS from db - Query hasId = query(where(AASIDPATH).is(aasId)); - AssetAdministrationShell aas = mongoOps.findOne(hasId, AssetAdministrationShell.class, collection); - if (aas == null) { - throw new ResourceNotFoundException("The AAS " + aasId + " could not be found in the database."); - } - // Add reference - aas.addSubmodelReference(submodel); - // Update db entry - mongoOps.findAndReplace(hasId, aas, collection); + public void addSubmodel(IReference submodelReference) { + AssetAdministrationShell shell = (AssetAdministrationShell) getAAS(); + shell.addSubmodelReference(submodelReference); + storageApi.update(shell, shellIdentificationId); } @Override - public void removeSubmodel(String id) { - // Get AAS from db - Query hasId = query(where(AASIDPATH).is(aasId)); - AssetAdministrationShell aas = mongoOps.findOne(hasId, AssetAdministrationShell.class, collection); - if (aas == null) { - throw new ResourceNotFoundException("The AAS " + aasId + " could not be found in the database."); - } - // Remove reference - Collection smReferences = aas.getSubmodelReferences(); - // Reference to submodel could be either by idShort (=> local) or directly via - // its identifier - for (Iterator iterator = smReferences.iterator(); iterator.hasNext();) { - IReference ref = iterator.next(); - List keys = ref.getKeys(); - IKey lastKey = keys.get(keys.size() - 1); - String idValue = lastKey.getValue(); - // remove this reference, if the last key points to the submodel - if (idValue.equals(id)) { - iterator.remove(); - break; - } + public void removeSubmodel(String submodelIdShort) { + AssetAdministrationShell shell = (AssetAdministrationShell) this.getAAS(); + Collection submodelReferences = shell.getSubmodelReferences(); + + Optional toBeRemoved = submodelReferences.stream().filter(submodelReference -> getLastSubmodelReferenceKey(submodelReference).getValue().equals(submodelIdShort)).findFirst(); + if (!toBeRemoved.isPresent() || toBeRemoved.isEmpty()) { + logger.warn("Submodel reference could not be removed. Shell with identification id '{}' does not contain submodel with idShort '{}'.", shell.getIdentification().getId(), submodelIdShort); + return; } - aas.setSubmodelReferences(smReferences); - // Update db entry - mongoOps.findAndReplace(hasId, aas, collection); + submodelReferences.remove(toBeRemoved.get()); + shell.setSubmodelReferences(submodelReferences); + storageApi.update(shell, submodelIdShort); } + private IKey getLastSubmodelReferenceKey(IReference submodelReference) { + return submodelReference.getKeys().get(getLastSubmodelReferenceReferenceIndex(submodelReference)); + } + + private int getLastSubmodelReferenceReferenceIndex(IReference submodelReference) { + return submodelReference.getKeys().size() - 1; + } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPIFactory.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPIFactory.java index 3fabc1d6..8f0f70ca 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPIFactory.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAPIFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2022 the Eclipse BaSyx Authors + * Copyright (C) 2022, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -28,37 +28,46 @@ import org.eclipse.basyx.aas.restapi.api.IAASAPI; import org.eclipse.basyx.aas.restapi.api.IAASAPIFactory; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; +import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier; import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * * Factory for creating a MongoDBAASAPI * - * @author fried + * @author fried, jungjan, witt * */ public class MongoDBAASAPIFactory implements IAASAPIFactory { - private BaSyxMongoDBConfiguration config; - private MongoClient client; + private MongoDBBaSyxStorageAPI storageAPI; @Deprecated public MongoDBAASAPIFactory(BaSyxMongoDBConfiguration config) { - this(config, MongoClients.create(config.getConnectionUrl())); + this(MongoDBBaSyxStorageAPIFactory.create(config.getAASCollection(), AssetAdministrationShell.class, config)); } public MongoDBAASAPIFactory(BaSyxMongoDBConfiguration config, MongoClient client) { - this.config = config; - this.client = client; + this(MongoDBBaSyxStorageAPIFactory.create(config.getAASCollection(), AssetAdministrationShell.class, config, client)); + } + + public MongoDBAASAPIFactory(MongoDBBaSyxStorageAPI mongoDBStorageAPI) { + this.storageAPI = mongoDBStorageAPI; } @Override - public IAASAPI getAASApi(AssetAdministrationShell aas) { - MongoDBAASAPI api = new MongoDBAASAPI(config, aas.getIdentification().getId(), client); - api.setAAS(aas); + public IAASAPI getAASApi(AssetAdministrationShell shell) { + MongoDBAASAPI api = new MongoDBAASAPI(storageAPI, shell.getIdentification().getId()); + api.setAAS(shell); return api; } + @Override + public IAASAPI create(IIdentifier aasId) { + return new MongoDBAASAPI(storageAPI, aasId.getId()); + } + } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregator.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregator.java index fbbc7322..5632cc22 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregator.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2021, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,15 +24,9 @@ ******************************************************************************/ package org.eclipse.basyx.components.aas.mongodb; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; - -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import org.eclipse.basyx.aas.aggregator.AASAggregator; @@ -46,7 +40,8 @@ import org.eclipse.basyx.aas.restapi.api.IAASAPIFactory; import org.eclipse.basyx.components.aas.aascomponent.MongoDBAASServerComponentFactory; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; -import org.eclipse.basyx.extensions.shared.authorization.internal.NotAuthorizedException; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; import org.eclipse.basyx.submodel.aggregator.SubmodelAggregatorFactory; import org.eclipse.basyx.submodel.aggregator.api.ISubmodelAggregator; import org.eclipse.basyx.submodel.aggregator.api.ISubmodelAggregatorFactory; @@ -55,9 +50,6 @@ import org.eclipse.basyx.submodel.metamodel.api.reference.IReference; import org.eclipse.basyx.submodel.metamodel.api.reference.enums.KeyType; import org.eclipse.basyx.submodel.metamodel.map.Submodel; -import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; -import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; -import org.eclipse.basyx.submodel.metamodel.map.qualifier.Referable; import org.eclipse.basyx.submodel.restapi.SubmodelProvider; import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPI; import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPIFactory; @@ -67,9 +59,6 @@ import org.eclipse.basyx.vab.protocol.http.connector.HTTPConnectorFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -79,32 +68,22 @@ * * @see AASAggregator AASAggregator for the "InMemory"-variant * - * @author espen, wege + * @author espen, wege, witt, jugnjan, zhangzai * */ public class MongoDBAASAggregator implements IAASAggregator { - private static Logger logger = LoggerFactory.getLogger(MongoDBAASAggregator.class); - - private static final String IDSHORTPATH = Referable.IDSHORT; - private static final String IDPATH = Identifiable.IDENTIFICATION + "." + Identifier.ID; - - protected Map aasProviderMap = new HashMap<>(); - protected BaSyxMongoDBConfiguration config; - protected MongoOperations mongoOps; - protected String aasCollection; - protected String smCollection; - + private Logger logger = LoggerFactory.getLogger(this.getClass()); private IAASRegistry registry; /** * Store AAS API Provider. By default, uses the MongoDB API Provider */ - protected IAASAPIFactory aasApiProvider; + protected IAASAPIFactory shellApiFactory; /** * Store Submodel API Provider. By default, uses a MongoDB Submodel Provider */ - protected ISubmodelAPIFactory smApiProvider; + protected ISubmodelAPIFactory submodelApiFactory; /** * Store SubmodelAggregator. By default, uses standard SubmodelAggregator @@ -115,21 +94,89 @@ public class MongoDBAASAggregator implements IAASAggregator { protected ISubmodelAggregator submodelAggregator; protected ISubmodelAggregatorFactory submodelAggregatorFactory; + private MongoDBBaSyxStorageAPI submodelStorageApi; + private MongoDBBaSyxStorageAPI shellStorageApi; + + public MongoDBAASAggregator(IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoDBBaSyxStorageAPI submodelStorageApi, + MongoDBBaSyxStorageAPI shellStorageApi) { + this.submodelStorageApi = submodelStorageApi; + this.shellStorageApi = shellStorageApi; + this.shellApiFactory = shellAPIFactory; + this.submodelAggregatorFactory = submodelAggregatorFactory; + this.registry = registry; + } + /** - * Receives a BaSyxMongoDBConfiguration and a registry to create a persistent - * MongoDB backend. - * + * Receives a BaSyxMongoDBConfiguration, IAASRegistry, IAASAPIFactory, + * ISubmodelAggregatorFactory and a MongoClient to create a persistent MongoDB + * backend. + * * @param config - * The MongoDB Configuration + * @param registry + * @param shellAPIFactory + * @param submodelAggregatorFactory * - * @deprecated Use new MongoDBAASAggregator with the - * {@link MongoDBAASServerComponentFactory}. */ - @Deprecated - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config) { - this.setConfiguration(config); - submodelAggregatorFactory = new SubmodelAggregatorFactory(smApiProvider); - init(); + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(registry, shellAPIFactory, submodelAggregatorFactory, submodelStorageApiFromConfig(config, client), shellStorageApiFromConfig(config, client)); + } + + /** + * Receives a BaSyxMongoDBConfiguration, + * IAASAPIFactory,ISubmodelAggregatorFactory and a MongoClient to create a + * persistent MongoDB backend. + * + * @param config + * @param shellAPIFactory + * @param submodelAggregatorFactory + * @param client + * + */ + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(config, null, shellAPIFactory, submodelAggregatorFactory, client); + } + + /** + * Receives a resourceConfigPath, IAASRegistry, IAASAPIFactory, + * ISubmodelAggregatorFactory and a MongoClient to create a persistent MongoDB + * backend. + * + * @param resourceConfigPath + * @param registry + * @param shellAPIFactory + * @param submodelAggregatorFactory + * @param client + * Use the new constructor using a MongoClient + * + */ + public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(loadConfigFromPath(resourceConfigPath), registry, shellAPIFactory, submodelAggregatorFactory, client); + } + + /** + * Receives a resourceConfigPath, IAASAPIFactory, ISubmodelAggregatorFactory and + * a MongoClient to create a persistent MongoDB backend. + * + * @param resourceConfigPath + * @param shellAPIFactory + * @param submodelAggregatorFactory + * @param client + * + */ + public MongoDBAASAggregator(String resourceConfigPath, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(loadConfigFromPath(resourceConfigPath), shellAPIFactory, submodelAggregatorFactory, client); + } + + /** + * Constructor using the default configuration, with the given + * IAASAPIFactory,ISubmodelAggregatorFactory and MongoClient. + * + * @param shellAPIFactory + * @param submodelAggregatorFactory + * @param client + */ + public MongoDBAASAggregator(IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, shellAPIFactory, submodelAggregatorFactory, client); } /** @@ -138,18 +185,13 @@ public MongoDBAASAggregator(BaSyxMongoDBConfiguration config) { * * @param config * The MongoDB Configuration - * @param registry - * The registry * * @deprecated Use new MongoDBAASAggregator with the * {@link MongoDBAASServerComponentFactory}. */ @Deprecated - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry) { - this.setConfiguration(config); - this.registry = registry; - submodelAggregatorFactory = new SubmodelAggregatorFactory(smApiProvider); - init(); + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config) { + this(config, null); } /** @@ -164,11 +206,7 @@ public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry regis */ @Deprecated public MongoDBAASAggregator(String resourceConfigPath) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.setConfiguration(config); - submodelAggregatorFactory = new SubmodelAggregatorFactory(smApiProvider); - init(); + this(loadConfigFromPath(resourceConfigPath)); } /** @@ -184,12 +222,24 @@ public MongoDBAASAggregator(String resourceConfigPath) { */ @Deprecated public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.setConfiguration(config); - submodelAggregatorFactory = new SubmodelAggregatorFactory(smApiProvider); - this.registry = registry; - init(); + this(loadConfigFromPath(resourceConfigPath), registry); + } + + /** + * Receives a BaSyxMongoDBConfiguration and a registry to create a persistent + * MongoDB backend. + * + * @param config + * The MongoDB Configuration + * @param registry + * The registry + * + * @deprecated Use new MongoDBAASAggregator with the + * {@link MongoDBAASServerComponentFactory}. + */ + @Deprecated + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry) { + this(registry, initShellApiFactory(config), initSubmodelAggregatorFactory(config), submodelStorageApiFromConfig(config, null), shellStorageApiFromConfig(config, null)); } /** @@ -222,33 +272,13 @@ public MongoDBAASAggregator(IAASRegistry registry) { * * @param config * @param registry - * @param aasAPIFactory + * @param shellAPIFactory * @param submodelAggregatorFactory * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(config, registry, aasAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); - } - - /** - * Receives a BaSyxMongoDBConfiguration, IAASRegistry, IAASAPIFactory, - * ISubmodelAggregatorFactory and a MongoClient to create a persistent MongoDB - * backend. - * - * @param config - * @param registry - * @param aasAPIFactory - * @param submodelAggregatorFactory - * - */ - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - setMongoDBConfiguration(config, client); - this.config = config; - this.registry = registry; - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(config, registry, shellAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); } /** @@ -256,32 +286,13 @@ public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASRegistry regis * ISubmodelAggregatorFactory to create a persistent MongoDB backend. * * @param config - * @param aasAPIFactory + * @param shellAPIFactory * @param submodelAggregatorFactory * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(config, aasAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); - } - - /** - * Receives a BaSyxMongoDBConfiguration, - * IAASAPIFactory,ISubmodelAggregatorFactory and a MongoClient to create a - * persistent MongoDB backend. - * - * @param config - * @param aasAPIFactory - * @param submodelAggregatorFactory - * @param client - * - */ - public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - setMongoDBConfiguration(config, client); - this.config = config; - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); + public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(config, shellAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); } /** @@ -290,42 +301,13 @@ public MongoDBAASAggregator(BaSyxMongoDBConfiguration config, IAASAPIFactory aas * * @param resourceConfigPath * @param registry - * @param aasAPIFactory + * @param shellAPIFactory * @param submodelAggregatorFactory * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - setMongoDBConfiguration(config, MongoClients.create(config.getConnectionUrl())); - this.registry = registry; - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); - } - - /** - * Receives a resourceConfigPath, IAASRegistry, IAASAPIFactory, - * ISubmodelAggregatorFactory and a MongoClient to create a persistent MongoDB - * backend. - * - * @param resourceConfigPath - * @param registry - * @param aasAPIFactory - * @param submodelAggregatorFactory - * @param client - * Use the new constructor using a MongoClient - * - */ - public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - setMongoDBConfiguration(config, client); - this.registry = registry; - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); + public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(loadConfigFromPath(resourceConfigPath), registry, shellAPIFactory, submodelAggregatorFactory); } /** @@ -333,62 +315,61 @@ public MongoDBAASAggregator(String resourceConfigPath, IAASRegistry registry, IA * ISubmodelAggregatorFactory to create a persistent MongoDB backend. * * @param resourceConfigPath - * @param aasAPIFactory + * @param shellAPIFactory * @param submodelAggregatorFactory * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAggregator(String resourceConfigPath, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - setMongoDBConfiguration(config, MongoClients.create(config.getConnectionUrl())); - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); - } - - /** - * Receives a resourceConfigPath, IAASAPIFactory, ISubmodelAggregatorFactory and - * a MongoClient to create a persistent MongoDB backend. - * - * @param resourceConfigPath - * @param aasAPIFactory - * @param submodelAggregatorFactory - * @param client - * - */ - public MongoDBAASAggregator(String resourceConfigPath, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - setMongoDBConfiguration(config, client); - this.aasApiProvider = aasAPIFactory; - this.submodelAggregatorFactory = submodelAggregatorFactory; - init(); + public MongoDBAASAggregator(String resourceConfigPath, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(loadConfigFromPath(resourceConfigPath), shellAPIFactory, submodelAggregatorFactory); } /** * Constructor using the default configuration, with the given IAASAPIFactory * and ISubmodelAggregatorFactory. * - * @param aasAPIFactory + * @param shellAPIFactory * @param submodelAggregatorFactory * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBAASAggregator(IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, aasAPIFactory, submodelAggregatorFactory); + public MongoDBAASAggregator(IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, shellAPIFactory, submodelAggregatorFactory); } - /** - * Constructor using the default configuration, with the given - * IAASAPIFactory,ISubmodelAggregatorFactory and MongoClient. - * - * @param aasAPIFactory - * @param submodelAggregatorFactory - * @param client - */ - public MongoDBAASAggregator(IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, aasAPIFactory, submodelAggregatorFactory, client); + private static ISubmodelAggregatorFactory initSubmodelAggregatorFactory(BaSyxMongoDBConfiguration config) { + ISubmodelAPIFactory submodelApiFactory = initSubmodelApiFactory(config); + return new SubmodelAggregatorFactory(submodelApiFactory); + } + + @SuppressWarnings("deprecation") + private static ISubmodelAPIFactory initSubmodelApiFactory(BaSyxMongoDBConfiguration config) { + return new MongoDBSubmodelAPIFactory(config); + } + + @SuppressWarnings("deprecation") + private static IAASAPIFactory initShellApiFactory(BaSyxMongoDBConfiguration config) { + return new MongoDBAASAPIFactory(config); + } + + private static MongoDBBaSyxStorageAPI submodelStorageApiFromConfig(BaSyxMongoDBConfiguration config, MongoClient client) { + String submodelCollectionName = config.getSubmodelCollection(); + MongoDBBaSyxStorageAPI submodelStorageApi = client == null ? MongoDBBaSyxStorageAPIFactory.create(submodelCollectionName, Submodel.class, config) + : MongoDBBaSyxStorageAPIFactory.create(submodelCollectionName, Submodel.class, config, client); + return submodelStorageApi; + } + + private static MongoDBBaSyxStorageAPI shellStorageApiFromConfig(BaSyxMongoDBConfiguration config, MongoClient client) { + String shellCollectionName = config.getAASCollection(); + MongoDBBaSyxStorageAPI shellStorageApi = client == null ? MongoDBBaSyxStorageAPIFactory.create(shellCollectionName, AssetAdministrationShell.class, config) + : MongoDBBaSyxStorageAPIFactory.create(shellCollectionName, AssetAdministrationShell.class, config, client); + return shellStorageApi; + } + + private static BaSyxMongoDBConfiguration loadConfigFromPath(String resourceConfigPath) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + config.loadFromResource(resourceConfigPath); + return config; } /** @@ -408,203 +389,146 @@ public void setRegistry(IAASRegistry registry) { this.registry = registry; } - /** - * Sets the db configuration for this Aggregator. - * - * @param config - * The MongoDB Configuration - * - * @deprecated This method is used with the old, deprecated Constructors. Use - * {@link MongoDBAASServerComponentFactory} instead - */ - @SuppressWarnings("deprecation") - @Deprecated - public void setConfiguration(BaSyxMongoDBConfiguration config) { - // set mongoDB configuration - this.config = config; - MongoClient client = MongoClients.create(config.getConnectionUrl()); - this.mongoOps = new MongoTemplate(client, config.getDatabase()); - this.aasCollection = config.getAASCollection(); - this.smCollection = config.getSubmodelCollection(); - - // Create API factories with the given configuration - this.aasApiProvider = aas -> { - MongoDBAASAPI api = new MongoDBAASAPI(config, aas.getIdentification().getId()); - api.setAAS(aas); - return api; - }; - this.smApiProvider = sm -> { - MongoDBSubmodelAPI api = new MongoDBSubmodelAPI(config, sm.getIdentification().getId()); - api.setSubmodel(sm); - return api; - }; - } - - private void setMongoDBConfiguration(BaSyxMongoDBConfiguration config, MongoClient client) { - this.config = config; - this.mongoOps = new MongoTemplate(client, config.getDatabase()); - this.aasCollection = config.getAASCollection(); - this.smCollection = config.getSubmodelCollection(); - } - /** * Removes all persistent AAS and submodels */ public void reset() { - mongoOps.dropCollection(aasCollection); - mongoOps.dropCollection(smCollection); - aasProviderMap.clear(); - } - - @SuppressWarnings("deprecation") - private void init() { - List data = mongoOps.findAll(AssetAdministrationShell.class, aasCollection); - for (AssetAdministrationShell aas : data) { - String aasId = aas.getIdentification().getId(); - logger.info("Adding AAS from DB: " + aasId); - MongoDBAASAPI aasApi = new MongoDBAASAPI(config, aasId); - MultiSubmodelProvider provider = createMultiSubmodelProvider(aasApi); - addSubmodelsFromDB(provider, aas); - aasProviderMap.put(aas.getIdentification().getId(), provider); - } + Collection shells = shellStorageApi.retrieveAll(); + Collection submodels = submodelStorageApi.retrieveAll(); + shells.forEach(shell -> shellStorageApi.delete(shell.getIdentification().getId())); + submodels.forEach(shell -> submodelStorageApi.delete(shell.getIdentification().getId())); } /** * Initializes and returns a VABMultiSubmodelProvider with only the * AssetAdministrationShell */ - private MultiSubmodelProvider createMultiSubmodelProvider(IAASAPI aasApi) { - AASModelProvider aasProvider = new AASModelProvider(aasApi); - IConnectorFactory connProvider = new HTTPConnectorFactory(); + private MultiSubmodelProvider createMultiSubmodelProvider(IAASAPI shellApi) { + AASModelProvider contentProvider = createContentProvider(shellApi); + IConnectorFactory connectorFactory = new HTTPConnectorFactory(); - ISubmodelAggregator usedAggregator = getSubmodelAggregatorInstance(); + ISubmodelAggregator submodelAggregator = getSubmodelAggregatorInstance(shellApi.getAAS().getIdentification()); - return new MultiSubmodelProvider(aasProvider, registry, connProvider, aasApiProvider, usedAggregator); + return new MultiSubmodelProvider(contentProvider, this.registry, connectorFactory, this.shellApiFactory, submodelAggregator); } - private ISubmodelAggregator getSubmodelAggregatorInstance() { + private AASModelProvider createContentProvider(IAASAPI shellApi) { + return new AASModelProvider(shellApi); + } + + private ISubmodelAggregator getSubmodelAggregatorInstance(IIdentifier shellId) { if (submodelAggregatorFactory == null) { return submodelAggregator; } - return submodelAggregatorFactory.create(); + return submodelAggregatorFactory.create(shellId); } /** * Adds submodel providers for submodels in the MongoDB */ - private void addSubmodelsFromDB(MultiSubmodelProvider provider, AssetAdministrationShell aas) { - // Get ids and idShorts from aas - Collection submodelRefs = aas.getSubmodelReferences(); - List smIds = new ArrayList<>(); - List smIdShorts = new ArrayList<>(); - for (IReference ref : submodelRefs) { - List keys = ref.getKeys(); - IKey lastKey = keys.get(keys.size() - 1); - if (lastKey.getIdType() == KeyType.IDSHORT) { - smIdShorts.add(lastKey.getValue()); - } else { - smIds.add(lastKey.getValue()); - } - } + private void addSubmodelsFromDB(MultiSubmodelProvider provider, AssetAdministrationShell shell) { + Collection submodelRefs = shell.getSubmodelReferences(); + List submodelIdentificationIds = getSubmodelIdentificationIdsFromSubmodelReferences(submodelRefs); + List submodelIdShorts = getSubmodelIdShortsFromSubmodelReferences(submodelRefs); + submodelIdentificationIds = completeSubmodelIdentificationsIdsByIdShorts(submodelIdentificationIds, submodelIdShorts); + + createProviderForSubmodels(provider, submodelIdentificationIds); + + } - // Add submodel ids by id shorts - for (String idShort : smIdShorts) { - String id = getSubmodelId(idShort); + private void createProviderForSubmodels(MultiSubmodelProvider provider, List submodelIdentificationIds) { + submodelIdentificationIds.forEach(submodelIdentificationId -> addSubmodelProvidersById(submodelIdentificationId, provider)); + } + + private List completeSubmodelIdentificationsIdsByIdShorts(List submodelIdentificationIds, List submodelIdShorts) { + submodelIdShorts.forEach(idShort -> { + String id = getSubmodelIdByIdShort(idShort); if (id != null) { - smIds.add(id); + submodelIdentificationIds.add(id); } - } + }); + return submodelIdentificationIds; + } - // Create a provider for each submodel - for (String id : smIds) { - logger.info("Adding Submodel from DB: " + id); - addSubmodelProvidersById(id, provider); - } + private List getSubmodelIdentificationIdsFromSubmodelReferences(Collection submodelRefs) { + List submodelIdentificationIds = submodelRefs.stream().map(this::getLastKeyFromReference).filter(lastKey -> lastKey.getIdType() != KeyType.IDSHORT).map(lastKey -> lastKey.getValue()).collect(Collectors.toList()); + return submodelIdentificationIds; + } + + private List getSubmodelIdShortsFromSubmodelReferences(Collection submodelRefs) { + List submodelIdShorts = submodelRefs.stream().map(this::getLastKeyFromReference).filter(lastKey -> lastKey.getIdType() == KeyType.IDSHORT).map(lastKey -> lastKey.getValue()).collect(Collectors.toList()); + return submodelIdShorts; } - private String getSubmodelId(String idShort) { - Submodel sm = mongoOps.findOne(query(where(IDSHORTPATH).is(idShort)), Submodel.class); - if (sm != null) { - return sm.getIdentification().getId(); + private IKey getLastKeyFromReference(IReference reference) { + List keys = reference.getKeys(); + IKey lastKey = keys.get(keys.size() - 1); + return lastKey; + } + + private String getSubmodelIdByIdShort(String idShort) { + Submodel submodel = submodelStorageApi.retrieve(idShort); + if (submodel != null) { + return submodel.getIdentification().getId(); } return null; } - @SuppressWarnings("deprecation") - private void addSubmodelProvidersById(String smId, MultiSubmodelProvider provider) { - ISubmodelAPI smApi = new MongoDBSubmodelAPI(config, smId); - SubmodelProvider smProvider = new SubmodelProvider(smApi); - provider.addSubmodel(smProvider); + private void addSubmodelProvidersById(String submodelIdentificationId, MultiSubmodelProvider provider) { + ISubmodelAPI submodelApi = new MongoDBSubmodelAPI(this.submodelStorageApi, submodelIdentificationId); + try { + SubmodelProvider submodelProvider = new SubmodelProvider(submodelApi); + provider.addSubmodel(submodelProvider); + } catch (ResourceNotFoundException noSubmodelsInDB) { + // ignore + logger.warn("Could not add submodel with identificationId '{}'.", submodelIdentificationId); + } + } - @SuppressWarnings("unchecked") @Override public Collection getAASList() { - return aasProviderMap.values().stream().map(p -> { - try { - return p.getValue("/aas"); - } catch (NotAuthorizedException e) { - return null; - } catch (Exception e1) { - e1.printStackTrace(); - throw new RuntimeException(); - } - }).filter(Objects::nonNull).map(m -> { - AssetAdministrationShell aas = new AssetAdministrationShell(); - aas.putAll((Map) m); - return aas; - }).collect(Collectors.toList()); + return shellStorageApi.retrieveAll().stream().map(aas -> (IAssetAdministrationShell) aas).collect(Collectors.toList()); } @SuppressWarnings("unchecked") @Override - public IAssetAdministrationShell getAAS(IIdentifier aasId) { - IModelProvider aasProvider = getAASProvider(aasId); + public IAssetAdministrationShell getAAS(IIdentifier shellIdentification) { + IModelProvider shellProvider = getAASProvider(shellIdentification); - // get all Elements from provider - Map aasMap = (Map) aasProvider.getValue("/aas"); - return AssetAdministrationShell.createAsFacade(aasMap); + Map shellMap = (Map) shellProvider.getValue("/aas"); + return AssetAdministrationShell.createAsFacade(shellMap); } @Override - public void createAAS(AssetAdministrationShell aas) { - IAASAPI aasApi = this.aasApiProvider.create(aas); - MultiSubmodelProvider provider = createMultiSubmodelProvider(aasApi); - aasProviderMap.put(aas.getIdentification().getId(), provider); + public void createAAS(AssetAdministrationShell shell) { + this.shellApiFactory.create(shell); } @Override - public void updateAAS(AssetAdministrationShell aas) { - MultiSubmodelProvider oldProvider = (MultiSubmodelProvider) getAASProvider(aas.getIdentification()); - IAASAPI aasApi = aasApiProvider.create(aas); - AASModelProvider contentProvider = new AASModelProvider(aasApi); - IConnectorFactory connectorFactory = oldProvider.getConnectorFactory(); - - MultiSubmodelProvider updatedProvider = new MultiSubmodelProvider(contentProvider, registry, connectorFactory, aasApiProvider, oldProvider.getSmAggregator()); - - aasProviderMap.put(aas.getIdentification().getId(), updatedProvider); + public void updateAAS(AssetAdministrationShell shell) { + this.shellApiFactory.create(shell); } @Override - public void deleteAAS(IIdentifier aasId) { - Query hasId = query(where(IDPATH).is(aasId.getId())); - mongoOps.remove(hasId, aasCollection); - aasProviderMap.remove(aasId.getId()); + public void deleteAAS(IIdentifier shellIdentifier) { + String shellIdentificationId = shellIdentifier.getId(); + shellStorageApi.delete(shellIdentificationId); } - public MultiSubmodelProvider getProviderForAASId(String aasId) { - return aasProviderMap.get(aasId); - } + public MultiSubmodelProvider getProviderForAASId(String shellIdentificationId) { + AssetAdministrationShell shell = shellStorageApi.retrieve(shellIdentificationId); - @Override - public IModelProvider getAASProvider(IIdentifier aasId) { - MultiSubmodelProvider provider = aasProviderMap.get(aasId.getId()); - - if (provider == null) { - throw new ResourceNotFoundException("AAS with Id " + aasId.getId() + " does not exist"); - } + IAASAPI shellApi = this.shellApiFactory.create(shell.getIdentification()); + MultiSubmodelProvider provider = createMultiSubmodelProvider(shellApi); + addSubmodelsFromDB(provider, shell); return provider; } + + @Override + public IModelProvider getAASProvider(IIdentifier shellIdentificationId) { + return getProviderForAASId(shellIdentificationId.getId()); + } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregatorFactory.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregatorFactory.java index 2128129e..29e4712e 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregatorFactory.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBAASAggregatorFactory.java @@ -45,87 +45,87 @@ public class MongoDBAASAggregatorFactory implements IAASAggregatorFactory { private BaSyxMongoDBConfiguration config; private IAASRegistry registry; - private IAASAPIFactory aasAPIFactory; + private IAASAPIFactory shellAPIFactory; private ISubmodelAggregatorFactory submodelAggregatorFactory; private String resourceConfigPath; private MongoClient client; @Deprecated - public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(config, registry, aasAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); + public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(config, registry, shellAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); } @Deprecated - public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(config, aasAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); + public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(config, shellAPIFactory, submodelAggregatorFactory, MongoClients.create(config.getConnectionUrl())); } @Deprecated - public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { this.resourceConfigPath = resourceConfigPath; this.registry = registry; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = MongoClients.create(config.getConnectionUrl()); } @Deprecated - public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { this.resourceConfigPath = resourceConfigPath; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = MongoClients.create(config.getConnectionUrl()); } @Deprecated - public MongoDBAASAggregatorFactory(IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { - this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, aasAPIFactory, submodelAggregatorFactory); + public MongoDBAASAggregatorFactory(IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory) { + this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, shellAPIFactory, submodelAggregatorFactory); } - public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { this.config = config; this.registry = registry; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = client; } - public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + public MongoDBAASAggregatorFactory(BaSyxMongoDBConfiguration config, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { this.config = config; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = client; } - public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASRegistry registry, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { this.resourceConfigPath = resourceConfigPath; this.registry = registry; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = client; } - public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + public MongoDBAASAggregatorFactory(String resourceConfigPath, IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { this.resourceConfigPath = resourceConfigPath; - this.aasAPIFactory = aasAPIFactory; + this.shellAPIFactory = shellAPIFactory; this.submodelAggregatorFactory = submodelAggregatorFactory; this.client = client; } - public MongoDBAASAggregatorFactory(IAASAPIFactory aasAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { - this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, aasAPIFactory, submodelAggregatorFactory); + public MongoDBAASAggregatorFactory(IAASAPIFactory shellAPIFactory, ISubmodelAggregatorFactory submodelAggregatorFactory, MongoClient client) { + this(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH, shellAPIFactory, submodelAggregatorFactory); } @Override public IAASAggregator create() { if (this.config != null && this.registry != null) { - return new MongoDBAASAggregator(this.config, this.registry, this.aasAPIFactory, this.submodelAggregatorFactory, this.client); + return new MongoDBAASAggregator(this.config, this.registry, this.shellAPIFactory, this.submodelAggregatorFactory, this.client); } else if (this.config != null) { - return new MongoDBAASAggregator(this.config, this.aasAPIFactory, this.submodelAggregatorFactory, this.client); + return new MongoDBAASAggregator(this.config, this.shellAPIFactory, this.submodelAggregatorFactory, this.client); } else if (this.resourceConfigPath != null && this.registry != null) { - return new MongoDBAASAggregator(this.resourceConfigPath, this.registry, this.aasAPIFactory, this.submodelAggregatorFactory, this.client); + return new MongoDBAASAggregator(this.resourceConfigPath, this.registry, this.shellAPIFactory, this.submodelAggregatorFactory, this.client); } else { - return new MongoDBAASAggregator(this.resourceConfigPath, this.aasAPIFactory, this.submodelAggregatorFactory, this.client); + return new MongoDBAASAggregator(this.resourceConfigPath, this.shellAPIFactory, this.submodelAggregatorFactory, this.client); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPI.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPI.java index c02cf109..b87e069d 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPI.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2021, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -24,564 +24,158 @@ ******************************************************************************/ package org.eclipse.basyx.components.aas.mongodb; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; - -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.apache.tika.mime.MimeType; -import org.apache.tika.mime.MimeTypeException; -import org.apache.tika.mime.MimeTypes; +import org.eclipse.basyx.components.aas.internal.StorageSubmodelAPI; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; -import org.eclipse.basyx.submodel.metamodel.api.ISubmodel; -import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; -import org.eclipse.basyx.submodel.metamodel.api.submodelelement.operation.IOperation; -import org.eclipse.basyx.submodel.metamodel.facade.submodelelement.SubmodelElementFacadeFactory; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; +import org.eclipse.basyx.extensions.internal.storage.BaSyxStorageAPI; import org.eclipse.basyx.submodel.metamodel.map.Submodel; import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; -import org.eclipse.basyx.submodel.metamodel.map.submodelelement.SubmodelElement; -import org.eclipse.basyx.submodel.metamodel.map.submodelelement.SubmodelElementCollection; -import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.File; -import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.property.Property; -import org.eclipse.basyx.submodel.metamodel.map.submodelelement.operation.Operation; -import org.eclipse.basyx.submodel.restapi.SubmodelElementProvider; -import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPI; import org.eclipse.basyx.submodel.restapi.operation.DelegatedInvocationManager; -import org.eclipse.basyx.vab.exception.provider.MalformedRequestException; -import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; -import org.eclipse.basyx.vab.modelprovider.VABPathTools; -import org.eclipse.basyx.vab.modelprovider.api.IModelProvider; -import org.eclipse.basyx.vab.modelprovider.lambda.VABLambdaProvider; -import org.eclipse.basyx.vab.modelprovider.map.VABMapProvider; import org.eclipse.basyx.vab.protocol.http.connector.HTTPConnectorFactory; -import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoDatabase; -import com.mongodb.client.gridfs.GridFSBucket; -import com.mongodb.client.gridfs.GridFSBuckets; -import com.mongodb.client.model.Filters; /** * Implements the ISubmodelAPI for a mongoDB backend. * - * @author espen + * @author fischer */ -public class MongoDBSubmodelAPI implements ISubmodelAPI { +public class MongoDBSubmodelAPI extends StorageSubmodelAPI { private static final String DEFAULT_CONFIG_PATH = "mongodb.properties"; public static final String SMIDPATH = Identifiable.IDENTIFICATION + "." + Identifier.ID; protected DelegatedInvocationManager invocationHelper; protected BaSyxMongoDBConfiguration config; - protected MongoOperations mongoOps; protected String collection; - protected String smId; - private MongoClient client; + /** * Receives the path of the configuration.properties file in its constructor. * * @param config - * @deprecated Use the new constructor using a MongoClient */ - @Deprecated - public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String smId) { - this(config, smId, new DelegatedInvocationManager(new HTTPConnectorFactory())); + public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String submdoelIdentificationId, MongoClient client) { + this(config, submdoelIdentificationId, new DelegatedInvocationManager(new HTTPConnectorFactory()), client); } /** - * Receives the path of the configuration.properties file in its constructor. - * - * @param config + * Constructor using default MongoDB connections */ - public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String smId, MongoClient client) { - this(config, smId, new DelegatedInvocationManager(new HTTPConnectorFactory()), client); + public MongoDBSubmodelAPI(String submodelIdentificationId, MongoClient client) { + this(DEFAULT_CONFIG_PATH, submodelIdentificationId, client); } - @Deprecated - public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String smId, - DelegatedInvocationManager invocationHelper) { - this(config, smId, invocationHelper, MongoClients.create(config.getConnectionUrl())); + public MongoDBSubmodelAPI(String submodelIdentificationId, DelegatedInvocationManager invocationHelper, MongoClient client) { + this(DEFAULT_CONFIG_PATH, submodelIdentificationId, invocationHelper, client); } - public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String smId, - DelegatedInvocationManager invocationHelper, MongoClient client) { - this.client = client; + // NEUER KONSTRUKTOR? + public MongoDBSubmodelAPI(BaSyxStorageAPI storageAPI, String identificationId, BaSyxMongoDBConfiguration config) { + super(storageAPI, identificationId, new DelegatedInvocationManager(new HTTPConnectorFactory())); this.setConfiguration(config); - this.setSubmodelId(smId); - this.invocationHelper = invocationHelper; } - /** - * Receives the path of the .properties file in its constructor from a resource. - * - * @deprecated Use the new constructor using a MongoClient - */ - @Deprecated - public MongoDBSubmodelAPI(String resourceConfigPath, String smId) { - this(resourceConfigPath, smId, new DelegatedInvocationManager(new HTTPConnectorFactory())); + public MongoDBSubmodelAPI(BaSyxStorageAPI storageAPI, String identificationId) { + super(storageAPI, identificationId, new DelegatedInvocationManager(new HTTPConnectorFactory())); } /** * Receives the path of the .properties file in its constructor from a resource. */ - public MongoDBSubmodelAPI(String resourceConfigPath, String smId, MongoClient client) { - this(resourceConfigPath, smId, new DelegatedInvocationManager(new HTTPConnectorFactory()), client); + public MongoDBSubmodelAPI(String resourceConfigPath, String submodelIdentificationId, MongoClient client) { + this(resourceConfigPath, submodelIdentificationId, new DelegatedInvocationManager(new HTTPConnectorFactory()), client); } - @Deprecated - public MongoDBSubmodelAPI(String resourceConfigPath, String smId, DelegatedInvocationManager invocationHelper) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.client = MongoClients.create(config.getConnectionUrl()); + public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String submodelIdentificationId, DelegatedInvocationManager invocationHelper, MongoClient client) { + super(createSubmodelStorageAPI(config, client), submodelIdentificationId, invocationHelper); this.setConfiguration(config); - this.setSubmodelId(smId); + this.setSubmodelId(submodelIdentificationId); this.invocationHelper = invocationHelper; } - public MongoDBSubmodelAPI(String resourceConfigPath, String smId, DelegatedInvocationManager invocationHelper, - MongoClient client) { - config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(resourceConfigPath); - this.client = client; + public MongoDBSubmodelAPI(String resourceConfigPath, String submodelIdentificationId, DelegatedInvocationManager invocationHelper, MongoClient client) { + super(createSubmodelStorageAPI(createConfig(resourceConfigPath), client), submodelIdentificationId, invocationHelper); + this.config = createConfig(resourceConfigPath); this.setConfiguration(config); - this.setSubmodelId(smId); + this.setSubmodelId(submodelIdentificationId); this.invocationHelper = invocationHelper; } /** - * Constructor using default MongoDB connections + * Receives the path of the configuration.properties file in its constructor. * + * @param config * @deprecated Use the new constructor using a MongoClient */ @Deprecated - public MongoDBSubmodelAPI(String smId) { - this(DEFAULT_CONFIG_PATH, smId); + public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String submodelIdentificationId) { + this(config, submodelIdentificationId, new DelegatedInvocationManager(new HTTPConnectorFactory())); } @Deprecated - public MongoDBSubmodelAPI(String smId, DelegatedInvocationManager invocationHelper) { - this(DEFAULT_CONFIG_PATH, smId, invocationHelper); - } - - /** - * Constructor using default MongoDB connections - */ - public MongoDBSubmodelAPI(String smId, MongoClient client) { - this(DEFAULT_CONFIG_PATH, smId, client); - } - - public MongoDBSubmodelAPI(String smId, DelegatedInvocationManager invocationHelper, MongoClient client) { - this(DEFAULT_CONFIG_PATH, smId, invocationHelper, client); + public MongoDBSubmodelAPI(BaSyxMongoDBConfiguration config, String submodelIdentificationId, DelegatedInvocationManager invocationHelper) { + this(config, submodelIdentificationId, invocationHelper, MongoClients.create(config.getConnectionUrl())); } /** - * Sets the db configuration for the submodel API. - * - * @param config - */ - public void setConfiguration(BaSyxMongoDBConfiguration config) { - this.config = config; - this.mongoOps = new MongoTemplate(client, config.getDatabase()); - this.collection = config.getSubmodelCollection(); - } - - /** - * Sets the submodel id, so that this API points to the submodel with smId. Can - * be changed to point to a different submodel in the database. + * Receives the path of the .properties file in its constructor from a resource. * - * @param smId + * @deprecated Use the new constructor using a MongoClient */ - public void setSubmodelId(String smId) { - this.smId = smId; + @Deprecated + public MongoDBSubmodelAPI(String resourceConfigPath, String submodelIdentificationId) { + this(resourceConfigPath, submodelIdentificationId, new DelegatedInvocationManager(new HTTPConnectorFactory())); } /** - * Depending on whether the model is already in the db, this method inserts or - * replaces the existing data. The new submodel id for this API is taken from - * the given submodel. + * Constructor using default MongoDB connections * - * @param sm + * @deprecated Use the new constructor using a MongoClient */ - public void setSubmodel(Submodel sm) { - String id = sm.getIdentification().getId(); - this.setSubmodelId(id); - - Submodel replaced = writeSubmodelInDB(sm); - if (replaced == null) { - mongoOps.insert(sm, collection); - } - // Remove mongoDB-specific map attribute from SM - // mongoOps modify sm on save - thus _id has to be removed here... - sm.remove("_id"); - } - - @SuppressWarnings("unchecked") - @Override - public ISubmodel getSubmodel() { - // Query Submodel from MongoDB - Query hasId = query(where(SMIDPATH).is(smId)); - Submodel result = mongoOps.findOne(hasId, Submodel.class, collection); - if (result == null) { - throw new ResourceNotFoundException("The submodel " + smId + " could not be found in the database."); - } - - // Remove mongoDB-specific map attribute from AASDescriptor - result.remove("_id"); - - // Cast all SubmodelElement maps to ISubmodelElements before returning the - // submodel - Map elements = new HashMap<>(); - Map> elemMaps = (Map>) result - .get(Submodel.SUBMODELELEMENT); - for (Entry> entry : elemMaps.entrySet()) { - String shortId = entry.getKey(); - Map elemMap = entry.getValue(); - ISubmodelElement element = SubmodelElementFacadeFactory.createSubmodelElement(elemMap); - elements.put(shortId, element); - } - // Replace the element map in the submodel - result.put(Submodel.SUBMODELELEMENT, elements); - // Return the "fixed" submodel - return result; - } - - @Override - public void addSubmodelElement(ISubmodelElement elem) { - // Get sm from db - Submodel sm = (Submodel) getSubmodel(); - // Add element - sm.addSubmodelElement(elem); - writeSubmodelInDB(sm); - } - - private ISubmodelElement getTopLevelSubmodelElement(String idShort) { - Submodel sm = (Submodel) getSubmodel(); - Map submodelElements = sm.getSubmodelElements(); - ISubmodelElement element = submodelElements.get(idShort); - if (element == null) { - throw new ResourceNotFoundException("The element \"" + idShort + "\" could not be found"); - } - return convertSubmodelElement(element); - } - - @SuppressWarnings("unchecked") - private ISubmodelElement convertSubmodelElement(ISubmodelElement element) { - // FIXME: Convert internal data structure of ISubmodelElement - Map elementMap = (Map) element; - IModelProvider elementProvider = new SubmodelElementProvider(new VABMapProvider(elementMap)); - Object elementVABObj = elementProvider.getValue(""); - return SubmodelElement.createAsFacade((Map) elementVABObj); - } - - private void deleteTopLevelSubmodelElement(String idShort) { - // Get sm from db - Submodel sm = (Submodel) getSubmodel(); - // Remove element - - deleteAllFilesFromGridFsIfIsFileSubmodelElement(idShort, sm); - - sm.getSubmodelElements().remove(idShort); - writeSubmodelInDB(sm); - - } - - @SuppressWarnings("unchecked") - private void deleteAllFilesFromGridFsIfIsFileSubmodelElement(String idShort, Submodel sm) { - Map submodelElement = (Map) sm.getSubmodelElement(idShort); - if (!File.isFile(submodelElement)) - return; - File file = File.createAsFacade(submodelElement); - GridFSBucket bucket = getGridFSBucket(); - bucket.find(Filters.eq("filename", file.getValue())).forEach(gridFile -> bucket.delete(gridFile.getObjectId())); - } - - @Override - public Collection getOperations() { - Submodel sm = (Submodel) getSubmodel(); - return sm.getOperations().values(); - } - - private void addNestedSubmodelElement(List idShorts, ISubmodelElement elem) { - Submodel sm = (Submodel) getSubmodel(); - // > 1 idShorts => add new sm element to an existing sm element - if (idShorts.size() > 1) { - idShorts = idShorts.subList(0, idShorts.size() - 1); - // Get parent SM element if more than 1 idShort - ISubmodelElement parentElement = getNestedSubmodelElement(sm, idShorts); - if (parentElement instanceof SubmodelElementCollection) { - ((SubmodelElementCollection) parentElement).addSubmodelElement(elem); - writeSubmodelInDB(sm); - } - } else { - // else => directly add it to the submodel - sm.addSubmodelElement(elem); - writeSubmodelInDB(sm); - } - } - - @Override - public Collection getSubmodelElements() { - Submodel sm = (Submodel) getSubmodel(); - return sm.getSubmodelElements().values(); - } - - @SuppressWarnings("unchecked") - private void updateSubmodelElementInDB(List idShorts, Object newValue) { - Submodel sm = (Submodel) getSubmodel(); - ISubmodelElement element = getNestedSubmodelElement(sm, idShorts); - - IModelProvider mapProvider = new VABLambdaProvider((Map) element); - SubmodelElementProvider smeProvider = new SubmodelElementProvider(mapProvider); - - smeProvider.setValue(Property.VALUE, newValue); - ISubmodelElement updatedElement = SubmodelElementFacadeFactory - .createSubmodelElement((Map) smeProvider.getValue("")); - - sm.addSubmodelElement(updatedElement); - - writeSubmodelInDB(sm); - } - - @Override - public void uploadSubmodelElementFile(String idShortPath, InputStream fileStream) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - Submodel sm = (Submodel) getSubmodel(); - ISubmodelElement element = getNestedSubmodelElement(sm, idShorts); - String fileName = updateFileInDB(fileStream, element, idShortPath); - updateSubmodelElementInDB(idShorts, fileName); - } - - @SuppressWarnings("unchecked") - private String updateFileInDB(InputStream newValue, ISubmodelElement element, String idShortPath) { - File file = File.createAsFacade((Map) element); - GridFSBucket bucket = getGridFSBucket(); - String fileName = constructFileName(file, idShortPath); - deleteAllDuplicateFiles(bucket, fileName); - bucket.uploadFromStream(fileName, newValue); - return fileName; - } - - private void deleteAllDuplicateFiles(GridFSBucket bucket, String fileName) { - bucket.find(Filters.eq("filename", fileName)).forEach(gridFile -> bucket.delete(gridFile.getObjectId())); - } - - private GridFSBucket getGridFSBucket() { - MongoDatabase database = client.getDatabase(config.getDatabase()); - GridFSBucket bucket = GridFSBuckets.create(database, config.getFileCollection()); - return bucket; - } - - private Object getTopLevelSubmodelElementValue(String idShort) { - Submodel sm = (Submodel) getSubmodel(); - return getElementProvider(sm, idShort).getValue("/value"); - } - - @SuppressWarnings("unchecked") - private Object getNestedSubmodelElementValue(List idShorts) { - ISubmodelElement lastElement = getNestedSubmodelElement(idShorts); - IModelProvider mapProvider = new VABLambdaProvider((Map) lastElement); - return new SubmodelElementProvider(mapProvider).getValue("/value"); - } - - @SuppressWarnings("unchecked") - protected Object unwrapParameter(Object parameter) { - if (parameter instanceof Map) { - Map map = (Map) parameter; - // Parameters have a strictly defined order and may not be omitted at all. - // Enforcing the structure with valueType is ok, but we should unwrap null - // values, too. - if (map.get("valueType") != null && map.containsKey("value")) { - return map.get("value"); - } - } - return parameter; - } - - @SuppressWarnings("unchecked") - private static SubmodelElementProvider getElementProvider(Submodel sm, String idShort) { - ISubmodelElement elem = sm.getSubmodelElement(idShort); - IModelProvider mapProvider = new VABMapProvider((Map) elem); - return new SubmodelElementProvider(mapProvider); - } - - private ISubmodelElement getNestedSubmodelElement(Submodel sm, List idShorts) { - Map elemMap = sm.getSubmodelElements(); - // Get last nested submodel element - for (int i = 0; i < idShorts.size() - 1; i++) { - String idShort = idShorts.get(i); - ISubmodelElement elem = elemMap.get(idShort); - if (elem instanceof SubmodelElementCollection) { - elemMap = ((SubmodelElementCollection) elem).getSubmodelElements(); - } else { - throw new ResourceNotFoundException( - idShort + " in the nested submodel element path could not be resolved."); - } - } - String lastIdShort = idShorts.get(idShorts.size() - 1); - if (!elemMap.containsKey(lastIdShort)) { - throw new ResourceNotFoundException( - lastIdShort + " in the nested submodel element path could not be resolved."); - } - return elemMap.get(lastIdShort); - } - - private ISubmodelElement getNestedSubmodelElement(List idShorts) { - // Get sm from db - Submodel sm = (Submodel) getSubmodel(); - // Get nested sm element from this sm - return convertSubmodelElement(getNestedSubmodelElement(sm, idShorts)); - } - - private void deleteNestedSubmodelElement(List idShorts) { - if (idShorts.size() == 1) { - deleteSubmodelElement(idShorts.get(0)); - return; - } - - // Get sm from db - Submodel sm = (Submodel) getSubmodel(); - // Get parent collection - List parentIds = idShorts.subList(0, idShorts.size() - 1); - ISubmodelElement parentElement = getNestedSubmodelElement(sm, parentIds); - // Remove element - SubmodelElementCollection coll = (SubmodelElementCollection) parentElement; - coll.deleteSubmodelElement(idShorts.get(idShorts.size() - 1)); - writeSubmodelInDB(sm); + @Deprecated + public MongoDBSubmodelAPI(String submodelIdentificationId) { + this(DEFAULT_CONFIG_PATH, submodelIdentificationId); } - private Object invokeNestedOperationAsync(List idShorts, Object... params) { - // not possible to invoke operations on a submodel that is stored in a db - throw new MalformedRequestException("Invoke not supported by this backend"); + @Deprecated + public MongoDBSubmodelAPI(String submodelIdentificationId, DelegatedInvocationManager invocationHelper) { + this(DEFAULT_CONFIG_PATH, submodelIdentificationId, invocationHelper); } - @Override - public Object getOperationResult(String idShort, String requestId) { - // not possible to invoke operations on a submodel that is stored in a db - throw new MalformedRequestException("Invoke not supported by this backend"); + @Deprecated + public MongoDBSubmodelAPI(String resourceConfigPath, String submodelIdentificationId, DelegatedInvocationManager invocationHelper) { + super(createSubmodelStorageAPI(createConfig(resourceConfigPath)), submodelIdentificationId, invocationHelper); + this.config = createConfig(resourceConfigPath); + this.setConfiguration(config); + this.setSubmodelId(submodelIdentificationId); + this.invocationHelper = invocationHelper; } - @Override - public ISubmodelElement getSubmodelElement(String idShortPath) { - if (idShortPath.contains("/")) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - return getNestedSubmodelElement(idShorts); - } else { - return getTopLevelSubmodelElement(idShortPath); - } + private static BaSyxStorageAPI createSubmodelStorageAPI(BaSyxMongoDBConfiguration config) { + MongoDBBaSyxStorageAPIFactory storageAPIFactory = new MongoDBBaSyxStorageAPIFactory<>(config, Submodel.class, config.getSubmodelCollection()); + return storageAPIFactory.create(); } - @Override - public void deleteSubmodelElement(String idShortPath) { - if (idShortPath.contains("/")) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - deleteNestedSubmodelElement(idShorts); - } else { - deleteTopLevelSubmodelElement(idShortPath); - } + private static BaSyxStorageAPI createSubmodelStorageAPI(BaSyxMongoDBConfiguration config, MongoClient client) { + MongoDBBaSyxStorageAPIFactory storageAPIFactory = new MongoDBBaSyxStorageAPIFactory<>(config, Submodel.class, config.getSubmodelCollection(), client); + return storageAPIFactory.create(); } - @Override - public void updateSubmodelElement(String idShortPath, Object newValue) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - updateSubmodelElementInDB(idShorts, newValue); + private static BaSyxMongoDBConfiguration createConfig(String resourceConfigPath) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + config.loadFromResource(resourceConfigPath); + return config; } /** - * Returns the updated Submodel or null if not found + * Sets the db configuration for the submodel API. * - * @param sm - * @return + * @param config */ - private Submodel writeSubmodelInDB(Submodel sm) { - Query hasId = query(where(SMIDPATH).is(smId)); - return mongoOps.findAndReplace(hasId, sm, collection); - } - - @Override - public Object getSubmodelElementValue(String idShortPath) { - if (idShortPath.contains("/")) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - return getNestedSubmodelElementValue(idShorts); - } else { - return getTopLevelSubmodelElementValue(idShortPath); - } - } - - @SuppressWarnings("unchecked") - @Override - public Object invokeOperation(String idShortPath, Object... params) { - Operation operation = (Operation) SubmodelElementFacadeFactory - .createSubmodelElement((Map) getSubmodelElement(idShortPath)); - if (!DelegatedInvocationManager.isDelegatingOperation(operation)) { - throw new MalformedRequestException("This backend supports only delegating operations."); - } - return invocationHelper.invokeDelegatedOperation(operation, params); - } - - @Override - public Object invokeAsync(String idShortPath, Object... params) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - return invokeNestedOperationAsync(idShorts, params); - } - - @Override - public void addSubmodelElement(String idShortPath, ISubmodelElement elem) { - String[] splitted = VABPathTools.splitPath(idShortPath); - List idShorts = Arrays.asList(splitted); - addNestedSubmodelElement(idShorts, elem); - } - - @SuppressWarnings("unchecked") - @Override - public java.io.File getSubmodelElementFile(String idShortPath) { - try { - Map submodelElement = (Map) getSubmodelElement(idShortPath); - File fileSubmodelElement = File.createAsFacade(submodelElement); - GridFSBucket bucket = getGridFSBucket(); - String fileName = constructFileName(fileSubmodelElement, idShortPath); - java.io.File file = new java.io.File(fileName); - FileOutputStream fileOutputStream; - fileOutputStream = new FileOutputStream(file); - bucket.downloadToStream(fileName, fileOutputStream); - return file; - } catch (FileNotFoundException e) { - throw new ResourceNotFoundException("The File Submodel Element does not contain a File"); - } - } - - private String constructFileName(File file, String idShortPath) { - Submodel sm = (Submodel) getSubmodel(); - return sm.getIdentification().getId() + "-" + idShortPath.replaceAll("/", "-") + getFileExtension(file); - } - - private String getFileExtension(File file) { - MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); - try { - MimeType mimeType = allTypes.forName(file.getMimeType()); - return mimeType.getExtension(); - } catch (MimeTypeException e) { - e.printStackTrace(); - return ""; - } + public void setConfiguration(BaSyxMongoDBConfiguration config) { + this.storageApi = createSubmodelStorageAPI(config); } -} +} \ No newline at end of file diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPIFactory.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPIFactory.java index 6b11b1f7..af864a80 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPIFactory.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAPIFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2022 the Eclipse BaSyx Authors + * Copyright (C) 2022, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -44,16 +44,17 @@ public class MongoDBSubmodelAPIFactory implements ISubmodelAPIFactory { private BaSyxMongoDBConfiguration config; private MongoClient client; - @Deprecated - public MongoDBSubmodelAPIFactory(BaSyxMongoDBConfiguration config) { - this(config, MongoClients.create(config.getConnectionUrl())); - } - public MongoDBSubmodelAPIFactory(BaSyxMongoDBConfiguration config, MongoClient client) { this.config = config; this.client = client; } + @Deprecated + public MongoDBSubmodelAPIFactory(BaSyxMongoDBConfiguration config) { + this(config, MongoClients.create(config.getConnectionUrl())); + } + + @Deprecated @Override public ISubmodelAPI getSubmodelAPI(Submodel submodel) { MongoDBSubmodelAPI api = new MongoDBSubmodelAPI(config, submodel.getIdentification().getId(), client); @@ -61,4 +62,8 @@ public ISubmodelAPI getSubmodelAPI(Submodel submodel) { return api; } + public ISubmodelAPI create(Submodel submodel) { + return getSubmodelAPI(submodel); + } + } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregator.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregator.java index e8d74184..d92a5913 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregator.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2022 the Eclipse BaSyx Authors + * Copyright (C) 2022, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -25,64 +25,146 @@ package org.eclipse.basyx.components.aas.mongodb; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.eclipse.basyx.aas.metamodel.map.AssetAdministrationShell; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; import org.eclipse.basyx.submodel.aggregator.SubmodelAggregator; import org.eclipse.basyx.submodel.metamodel.api.ISubmodel; import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier; +import org.eclipse.basyx.submodel.metamodel.api.reference.IKey; +import org.eclipse.basyx.submodel.metamodel.api.reference.IReference; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPI; import org.eclipse.basyx.submodel.restapi.api.ISubmodelAPIFactory; +import org.eclipse.basyx.submodel.restapi.vab.VABSubmodelAPIFactory; import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Query; import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; /** * Extends the {@link SubmodelAggregator} for the needs of MongoDB * - * @author schnicke + * @author schnicke, jungjan, witt * */ public class MongoDBSubmodelAggregator extends SubmodelAggregator { + private MongoDBBaSyxStorageAPI storageApi; + private MongoDBBaSyxStorageAPI aasStorageApi; + private IIdentifier shellId; - private String smCollection; - private MongoTemplate mongoOps; - - @Deprecated - public MongoDBSubmodelAggregator(ISubmodelAPIFactory smApiFactory, BaSyxMongoDBConfiguration config) { - this(smApiFactory, config, MongoClients.create(config.getConnectionUrl())); + public MongoDBSubmodelAggregator(ISubmodelAPIFactory submodelApiFactory, BaSyxMongoDBConfiguration config, MongoClient client) { + this(submodelApiFactory, MongoDBBaSyxStorageAPIFactory.create(config.getSubmodelCollection(), Submodel.class, config, client)); } - public MongoDBSubmodelAggregator(ISubmodelAPIFactory smApiFactory, BaSyxMongoDBConfiguration config, MongoClient client) { - super(smApiFactory); + public MongoDBSubmodelAggregator(ISubmodelAPIFactory submodelApiFactory, BaSyxMongoDBConfiguration config, MongoClient client, IIdentifier shellId) { + this(submodelApiFactory, MongoDBBaSyxStorageAPIFactory.create(config.getSubmodelCollection(), Submodel.class, config, client)); + aasStorageApi = MongoDBBaSyxStorageAPIFactory.create(config.getAASCollection(), AssetAdministrationShell.class, config, client); + this.shellId = shellId; + } - smCollection = config.getSubmodelCollection(); + public MongoDBSubmodelAggregator(ISubmodelAPIFactory submodelApiFactory, MongoDBBaSyxStorageAPI storageApi) { + super(submodelApiFactory); + this.storageApi = storageApi; + } - mongoOps = new MongoTemplate(client, config.getDatabase()); + @Deprecated + public MongoDBSubmodelAggregator(ISubmodelAPIFactory submodelApiFactory, BaSyxMongoDBConfiguration config) { + this(submodelApiFactory, MongoDBBaSyxStorageAPIFactory.create(config.getSubmodelCollection(), Submodel.class, config)); } @Override - public void deleteSubmodelByIdentifier(IIdentifier identifier) { - super.deleteSubmodelByIdentifier(identifier); - deleteSubmodelFromDB(identifier); + public void deleteSubmodelByIdentifier(IIdentifier submodelIdentifier) { + storageApi.delete(submodelIdentifier.getId()); } @Override public void deleteSubmodelByIdShort(String idShort) { - try { - ISubmodel sm = getSubmodelbyIdShort(idShort); - super.deleteSubmodelByIdShort(idShort); - deleteSubmodelFromDB(sm.getIdentification()); - } catch (ResourceNotFoundException e) { - // Nothing to do + ISubmodel submodel = getSubmodelbyIdShort(idShort); + storageApi.delete(submodel.getIdentification().getId()); + } + + @Override + public Collection getSubmodelList() { + if (shellId == null) + return returnAllSubmodels(); + + AssetAdministrationShell shell = aasStorageApi.retrieve(shellId.getId()); + Collection submodelRefs = shell.getSubmodelReferences(); + + if (submodelRefs.isEmpty()) { + return findSubmodelsWithGivenParentId(); } + List submodelIds = submodelRefs.stream().map(ref -> { + return getLastKeyFromReference(ref).getValue(); + }).collect(Collectors.toList()); + + return submodelIds.stream().map(sm -> { + return storageApi.retrieve(sm); + }).collect(Collectors.toList()); + } + + private List returnAllSubmodels() { + return storageApi.retrieveAll().stream().map(submodel -> (ISubmodel) submodel).collect(Collectors.toList()); + } + + private List findSubmodelsWithGivenParentId(){ + return storageApi.retrieveAll().stream().filter(submodel -> { + IReference parentRef = submodel.getParent(); + return (parentRef != null) && (parentRef.getKeys().get(0).getValue().equals(shellId.getId())); + }).collect(Collectors.toList()); + } + + private IKey getLastKeyFromReference(IReference reference) { + List keys = reference.getKeys(); + IKey lastKey = keys.get(keys.size() - 1); + return lastKey; + } + + @Override + public ISubmodel getSubmodel(IIdentifier identifier) throws ResourceNotFoundException { + return storageApi.retrieve(identifier.getId()); + } + + @Override + public void createSubmodel(Submodel submodel) { + storageApi.createOrUpdate(submodel); + } + + @Override + public void updateSubmodel(Submodel submodel) throws ResourceNotFoundException { + storageApi.createOrUpdate(submodel); + } + + @Override + public void createSubmodel(ISubmodelAPI submodelAPI) { + storageApi.createOrUpdate((Submodel) submodelAPI.getSubmodel()); + } + + @Override + public ISubmodel getSubmodelbyIdShort(String idShort) throws ResourceNotFoundException { + Optional submodelOptional = getSubmodelList().stream().filter(submodel -> { + return submodel.getIdShort().equals(idShort); + }).findAny(); + if (submodelOptional.isEmpty()) + throw new ResourceNotFoundException("The submodel with idShort '" + idShort + "' could not be found"); + return submodelOptional.get(); } - private void deleteSubmodelFromDB(IIdentifier identifier) { - Query hasId = query(where(MongoDBSubmodelAPI.SMIDPATH).is(identifier.getId())); - mongoOps.remove(hasId, smCollection); + @Override + public ISubmodelAPI getSubmodelAPIById(IIdentifier identifier) throws ResourceNotFoundException { + Submodel submodel = (Submodel) getSubmodel(identifier); + return new VABSubmodelAPIFactory().create(submodel); + } + + @Override + public ISubmodelAPI getSubmodelAPIByIdShort(String idShort) throws ResourceNotFoundException { + Submodel submodel = (Submodel) getSubmodelbyIdShort(idShort); + return new VABSubmodelAPIFactory().create(submodel); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregatorFactory.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregatorFactory.java index 0bf1bc2e..d5c63dfc 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregatorFactory.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/java/org/eclipse/basyx/components/aas/mongodb/MongoDBSubmodelAggregatorFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2022 the Eclipse BaSyx Authors + * Copyright (C) 2022, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -46,25 +46,25 @@ public class MongoDBSubmodelAggregatorFactory implements ISubmodelAggregatorFact private ISubmodelAPIFactory submodelAPIFactory; private MongoClient client; - @Deprecated - public MongoDBSubmodelAggregatorFactory(BaSyxMongoDBConfiguration config, ISubmodelAPIFactory submodelAPIFactory) { - this(config, submodelAPIFactory, MongoClients.create(config.getConnectionUrl())); - } - public MongoDBSubmodelAggregatorFactory(BaSyxMongoDBConfiguration config, ISubmodelAPIFactory submodelAPIFactory, MongoClient client) { this.config = config; this.client = client; this.submodelAPIFactory = submodelAPIFactory; } + @Deprecated + public MongoDBSubmodelAggregatorFactory(BaSyxMongoDBConfiguration config, ISubmodelAPIFactory submodelAPIFactory) { + this(config, submodelAPIFactory, MongoClients.create(config.getConnectionUrl())); + } + @Override public ISubmodelAggregator create() { return new MongoDBSubmodelAggregator(submodelAPIFactory, config, client); } @Override - public ISubmodelAggregator create(IIdentifier ignored) { - return create(); + public ISubmodelAggregator create(IIdentifier shellId) { + return new MongoDBSubmodelAggregator(submodelAPIFactory, config, client, shellId); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aas.properties b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aas.properties index e26cc8aa..025d0018 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aas.properties +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aas.properties @@ -59,12 +59,12 @@ aas.aasxUpload=Enabled # registry.path=http://localhost:4000/registry/ # ############################# -# Host +# External URL # ############################# -# Host specifies the endpoint of the deployed AAS component -# If host is empty, the registered AAS endpoint is derived from the context properties +# The external URL specifies the endpoint of the deployed AAS component +# If it is empty, the registered AAS endpoint is derived from the context properties -# registry.host= +# aas.externalurl= # ############################# # Submodels diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/a.aasx b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/a.aasx new file mode 100644 index 00000000..a22b8a14 Binary files /dev/null and b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/a.aasx differ diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/b.aasx b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/b.aasx new file mode 100644 index 00000000..2cb692bd Binary files /dev/null and b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/main/resources/aasx/b.aasx differ diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java index c838afe9..ef9d88c4 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/AASXSuite.java @@ -39,6 +39,7 @@ import org.eclipse.basyx.aas.manager.ConnectedAssetAdministrationShellManager; import org.eclipse.basyx.aas.metamodel.connected.ConnectedAssetAdministrationShell; import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor; +import org.eclipse.basyx.aas.metamodel.map.descriptor.CustomId; import org.eclipse.basyx.aas.metamodel.map.descriptor.ModelUrn; import org.eclipse.basyx.aas.metamodel.map.descriptor.SubmodelDescriptor; import org.eclipse.basyx.aas.registration.api.IAASRegistry; @@ -72,11 +73,25 @@ public abstract class AASXSuite { protected static final String aasShortId = "Festo_3S7PM0CP4BD"; protected static final ModelUrn aasId = new ModelUrn("smart.festo.com/demo/aas/1/1/454576463545648365874"); protected static final ModelUrn smId = new ModelUrn("www.company.com/ids/sm/4343_5072_7091_3242"); - protected static final String smShortId = "Nameplate"; + protected static final String smIdShort = "Nameplate"; + + protected static final String aasAIdShort = "aasA"; + protected static final String smAIdShort = "a"; + protected static final CustomId aasAId = new CustomId("AssetAdministrationShell---51A6D8AE"); + protected static final CustomId smAId = new CustomId("fileTestA"); + protected static final String aasBIdShort = "aasB"; + protected static final String smBIdShort = "b"; + protected static final CustomId aasBId = new CustomId("AssetAdministrationShell---51A6D8AF"); + protected static final CustomId smBId = new CustomId("fileTestB"); + protected static final String fileShortIdPath = "file"; // Has to be individualized by each test inheriting from this suite protected static String aasEndpoint; protected static String smEndpoint; + protected static String aasAEndpoint; + protected static String smAEndpoint; + protected static String aasBEndpoint; + protected static String smBEndpoint; protected static String rootEndpoint; private ConnectedAssetAdministrationShellManager manager; @@ -93,8 +108,14 @@ public void setUp() { // Create a dummy registry to test integration of XML AAS aasRegistry = new InMemoryRegistry(); AASDescriptor descriptor = new AASDescriptor(aasShortId, aasId, aasEndpoint); - descriptor.addSubmodelDescriptor(new SubmodelDescriptor(smShortId, smId, smEndpoint)); + descriptor.addSubmodelDescriptor(new SubmodelDescriptor(smIdShort, smId, smEndpoint)); aasRegistry.register(descriptor); + AASDescriptor descriptorA = new AASDescriptor(aasAIdShort, aasAId, aasAEndpoint); + descriptorA.addSubmodelDescriptor(new SubmodelDescriptor(smAIdShort, smAId, smAEndpoint)); + aasRegistry.register(descriptorA); + AASDescriptor descriptorB = new AASDescriptor(aasBIdShort, aasBId, aasBEndpoint); + descriptorB.addSubmodelDescriptor(new SubmodelDescriptor(smBIdShort, smBId, smBEndpoint)); + aasRegistry.register(descriptorB); // Create a ConnectedAssetAdministrationShell using a // ConnectedAssetAdministrationShellManager @@ -110,18 +131,18 @@ public void testGetSingleAAS() throws Exception { @Test public void testGetSingleSubmodel() throws Exception { - ISubmodel subModel = getConnectedSubmodel(); - assertEquals(smShortId, subModel.getIdShort()); + ISubmodel subModel = manager.retrieveSubmodel(aasId, smId); + assertEquals(smIdShort, subModel.getIdShort()); } @Test public void testGetSingleModule() throws Exception { - final String FILE_ENDING = "files/aasx/Nameplate/marking_rcm.jpg"; - final String FILE_PATH = rootEndpoint + "files/aasx/Nameplate/marking_rcm.jpg"; + final String FILE_ENDING = "basyx-temp/aasx0/files/aasx/Nameplate/marking_rcm.jpg"; + final String FILE_PATH = rootEndpoint + "basyx-temp/aasx0/files/aasx/Nameplate/marking_rcm.jpg"; checkFile(FILE_PATH); // Get the submdoel nameplate - ISubmodel nameplate = getConnectedSubmodel(); + ISubmodel nameplate = manager.retrieveSubmodel(aasId, smId); // Get the submodel element collection marking_rcm ConnectedSubmodelElementCollection marking_rcm = (ConnectedSubmodelElementCollection) nameplate.getSubmodelElements().get("Marking_RCM"); Collection values = marking_rcm.getValue(); @@ -139,6 +160,24 @@ public void testGetSingleModule() throws Exception { } } } + + @Test + public void testCollidingFiles() throws Exception { + final String FILE_ENDING_A = "basyx-temp/aasx1/files/aasx/files/text.txt"; + final String FILE_ENDING_B = "basyx-temp/aasx2/files/aasx/files/text.txt"; + + checkFile(rootEndpoint + FILE_ENDING_A); + checkFile(rootEndpoint + FILE_ENDING_B); + + ISubmodel smA = manager.retrieveSubmodel(aasAId, smAId); + ISubmodel smB = manager.retrieveSubmodel(aasBId, smBId); + + String fileAValue = (String) smA.getSubmodelElement("file").getValue(); + String fileBValue = (String) smB.getSubmodelElement("file").getValue(); + + assertTrue(fileAValue.endsWith(FILE_ENDING_A)); + assertTrue(fileBValue.endsWith(FILE_ENDING_B)); + } @Test public void testAllFiles() throws Exception { @@ -187,14 +226,4 @@ private void checkFile(String absolutePath) { private ConnectedAssetAdministrationShell getConnectedAssetAdministrationShell() throws Exception { return manager.retrieveAAS(aasId); } - - /** - * Gets the connected Submodel - * - * @return connected SM - * @throws Exception - */ - private ISubmodel getConnectedSubmodel() { - return manager.retrieveSubmodel(aasId, smId); - } } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXAASServer.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXAASServer.java index b8baca8d..cd8e3473 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXAASServer.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXAASServer.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.net.URLEncoder; import java.nio.file.Paths; import javax.servlet.ServletException; @@ -59,7 +60,7 @@ public static void setUpClass() throws ParserConfigurationException, SAXExceptio // Setup component's test configuration BaSyxContextConfiguration contextConfig = new BaSyxContextConfiguration(); contextConfig.loadFromResource(BaSyxContextConfiguration.DEFAULT_CONFIG_PATH); - BaSyxAASServerConfiguration aasConfig = new BaSyxAASServerConfiguration(AASServerBackend.INMEMORY, "aasx/01_Festo.aasx"); + BaSyxAASServerConfiguration aasConfig = new BaSyxAASServerConfiguration(AASServerBackend.INMEMORY, "[\"aasx/01_Festo.aasx\", \"aasx/a.aasx\", \"aasx/b.aasx\"]"); String docBasepath = Paths.get(FileUtils.getTempDirectory().getAbsolutePath(), AASXToMetamodelConverter.TEMP_DIRECTORY).toAbsolutePath().toString(); contextConfig.setDocBasePath(docBasepath); @@ -70,7 +71,13 @@ public static void setUpClass() throws ParserConfigurationException, SAXExceptio rootEndpoint = contextConfig.getUrl() + "/"; aasEndpoint = rootEndpoint + "/" + AASAggregatorProvider.PREFIX + "/" + aasId.getEncodedURN() + "/aas"; - smEndpoint = aasEndpoint + "/submodels/" + smShortId + "/submodel"; + smEndpoint = aasEndpoint + "/submodels/" + smIdShort + "/submodel"; + String encodedAasAId = URLEncoder.encode(aasAId.getId(), "UTF-8"); + aasAEndpoint = rootEndpoint + "/" + AASAggregatorProvider.PREFIX + "/" + encodedAasAId + "/aas"; + smAEndpoint = aasAEndpoint + "/submodels/" + smAIdShort + "/submodel"; + String encodedAasBId = URLEncoder.encode(aasBId.getId(), "UTF-8"); + aasBEndpoint = rootEndpoint + "/" + AASAggregatorProvider.PREFIX + "/" + encodedAasBId + "/aas"; + smBEndpoint = aasBEndpoint + "/submodels/" + smBIdShort + "/submodel"; logger.info("AAS URL for servlet test: " + aasEndpoint); } diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXPackageManager.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXPackageManager.java index 1efce60e..79d10494 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXPackageManager.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXPackageManager.java @@ -145,6 +145,7 @@ public void testCheckAasxConverter() { /** * Creates a new .aasx using the AASXFactory and tries to parse it */ + @SuppressWarnings("resource") @Test public void testLoadGeneratedAASX() throws InvalidFormatException, IOException, ParserConfigurationException, SAXException, TransformerException, URISyntaxException { diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAAASAPI.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAAASAPI.java new file mode 100644 index 00000000..c7770652 --- /dev/null +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAAASAPI.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.regression.AASServer.mongodb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.eclipse.basyx.aas.metamodel.api.IAssetAdministrationShell; +import org.eclipse.basyx.aas.metamodel.map.AssetAdministrationShell; +import org.eclipse.basyx.aas.metamodel.map.descriptor.CustomId; +import org.eclipse.basyx.components.aas.mongodb.MongoDBAASAPI; +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier; +import org.eclipse.basyx.submodel.metamodel.api.reference.IReference; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * + * @author jungjan, witt + * + */ +public class TestMongoDBAAASAPI { + private static MongoDBAASAPI shellAPI; + private final static String COLLECTION_NAME = "testCollection"; + private final static BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + private static MongoDBBaSyxStorageAPI mongoDBStorageAPI; + private final static String SHELL_IDENTIFICATION_ID = "testIdentificationId"; + + @SuppressWarnings("deprecation") + @BeforeClass + public static void setUpClass() { + mongoDBStorageAPI = new MongoDBBaSyxStorageAPI<>(COLLECTION_NAME, AssetAdministrationShell.class, config); + shellAPI = new MongoDBAASAPI(mongoDBStorageAPI, SHELL_IDENTIFICATION_ID); + } + + @Before + public void before() { + Collection shells = mongoDBStorageAPI.retrieveAll(); + shells.forEach(shell -> mongoDBStorageAPI.delete(shell.getIdentification().getId())); + } + + @Test + public void setAndGetAAS() { + String idShort = "testIdShort"; + + IIdentifier identification = new CustomId(SHELL_IDENTIFICATION_ID); + AssetAdministrationShell expectedShell = new AssetAdministrationShell(idShort, identification, null); + + shellAPI.setAAS(expectedShell); + AssetAdministrationShell resultShell = (AssetAdministrationShell) shellAPI.getAAS(); + + assertEquals(expectedShell, resultShell); + } + + @Test + public void addSubmodel() { + String idShortShell = "testIdShortShell"; + String idShortSubmodel = "testIdShortSubmodel"; + + IIdentifier identification = new CustomId(SHELL_IDENTIFICATION_ID); + + AssetAdministrationShell expectedShell = new AssetAdministrationShell(idShortShell, identification, null); + shellAPI.setAAS(expectedShell); + + Submodel expectedSubmodel = new Submodel(idShortSubmodel, identification); + IReference testReference = expectedSubmodel.getReference(); + + expectedShell.addSubmodelReference(testReference); + shellAPI.addSubmodel(testReference); + + Object resultShell = shellAPI.getAAS(); + + assertEquals(expectedShell, resultShell); + + } + + @Test + public void removeSubmodel() { + String idShortShell = "testIdShortShell"; + String idShortSubmodel = "testIdShortSubmodel"; + + IIdentifier identification = new CustomId(SHELL_IDENTIFICATION_ID); + + AssetAdministrationShell testShell = new AssetAdministrationShell(idShortShell, identification, null); + + Submodel expectedSubmodel = new Submodel(idShortSubmodel, identification); + IReference testReference = expectedSubmodel.getReference(); + + shellAPI.setAAS(testShell); + shellAPI.addSubmodel(testReference); + shellAPI.removeSubmodel(expectedSubmodel.getIdentification().getId()); + + IAssetAdministrationShell resultShell = shellAPI.getAAS(); + Collection submodelReferences = resultShell.getSubmodelReferences(); + assertTrue(submodelReferences.isEmpty()); + } +} diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAggregator.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAggregator.java index f655c944..380a0db0 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAggregator.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBAggregator.java @@ -109,7 +109,11 @@ public static void setUpClass() throws ParserConfigurationException, SAXExceptio component.setRegistry(registry); component.startComponent(); + } + @Override + public void setup() { + super.setup(); createAssetAdministrationShell(AAS_ID); createSubmodel(SM_IDSHORT, SM_IDENTIFICATION, AAS_ID); } @@ -152,14 +156,11 @@ private static void createSubmodel(String smIdShort, Identifier smIdentifier, St @Test public void testDeleteReachesDatabase() { - final BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); - config.loadFromResource(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH); - - final MongoClient client = MongoClients.create(config.getConnectionUrl()); + final MongoClient client = MongoClients.create(mongoDBConfig.getConnectionUrl()); - final MongoTemplate mongoOps = new MongoTemplate(client, config.getDatabase()); + final MongoTemplate mongoOps = new MongoTemplate(client, mongoDBConfig.getDatabase()); - final String aasCollection = config.getAASCollection(); + final String aasCollection = mongoDBConfig.getAASCollection(); final IAASAggregator aggregator = getAggregator(); @@ -198,16 +199,16 @@ public void testDeleteReachesDatabase() { @SuppressWarnings("deprecation") @Override protected IAASAggregator getAggregator() { - MongoDBAASAggregator aggregator = new MongoDBAASAggregator(BaSyxMongoDBConfiguration.DEFAULT_CONFIG_PATH); + MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig, registry); aggregator.reset(); - return aggregator; } @SuppressWarnings("deprecation") @Test public void checkInitialSetupAfterCreatingAndRegisteringAasAndSubmodel() { - MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig); + MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig, registry); + ISubmodel persistentSubmodel = getSubmodelFromAggregator(aggregator, AAS_ID, SM_IDSHORT); assertEquals(SM_IDSHORT, persistentSubmodel.getIdShort()); @@ -248,9 +249,6 @@ private void assertSubmodelsAreResolvedCorrectly(ISubmodel persistentSubmodel, I } private void createAASWithSubmodelWithCollidingIdShort() { - createAssetAdministrationShell(AAS_ID); - createSubmodel(SM_IDSHORT, SM_IDENTIFICATION, AAS_ID); - createAssetAdministrationShell(AAS_ID_2); createSubmodel(SM_IDSHORT, SM_IDENTIFICATION_2, AAS_ID_2); } @@ -273,6 +271,7 @@ public void checkNoExceptionIsObservedAfterPassingRegistry() { restartAasServer(); MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig, registry); + MultiSubmodelProvider aasProvider = (MultiSubmodelProvider) getAssetAdministrationShellProviderFromMongoDBAggregator(aggregator, AAS_ID, SM_IDSHORT); Map submodelObject = (Map) aasProvider.getValue(PREFIX_SUBMODEL_PATH + SM_IDSHORT + SUFFIX_SUBMODEL_PATH); diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBServer.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBServer.java index 2fb38045..d6fc380c 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBServer.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBServer.java @@ -130,7 +130,7 @@ public void testAggregatorPersistency() throws Exception { createAssetAdministrationShell(); createSubmodel(); - MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig); + MongoDBAASAggregator aggregator = new MongoDBAASAggregator(mongoDBConfig, aasRegistry); ISubmodel persistentSM = getSubmodelFromAggregator(aggregator); assertEquals(SM_IDSHORT, persistentSM.getIdShort()); diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBSubmodelAPI.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBSubmodelAPI.java index c631ad49..3068c067 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBSubmodelAPI.java +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/mongodb/TestMongoDBSubmodelAPI.java @@ -31,14 +31,21 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.OutputStream; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; import org.eclipse.basyx.aas.metamodel.map.descriptor.CustomId; import org.eclipse.basyx.components.aas.mongodb.MongoDBSubmodelAPI; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; import org.eclipse.basyx.submodel.metamodel.map.Submodel; import org.eclipse.basyx.submodel.metamodel.map.qualifier.LangStrings; +import org.eclipse.basyx.submodel.metamodel.map.submodelelement.SubmodelElementCollection; import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.File; import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.MultiLanguageProperty; +import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.property.Property; +import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; import org.junit.Test; import com.mongodb.MongoGridFSException; @@ -85,7 +92,7 @@ public void fileSubmodelElementFileUpload() throws FileNotFoundException { java.io.File value = submodelAPI.getSubmodelElementFile("fileSmeIdShort"); - assertEquals("mySubmodelId-fileSmeIdShort.xml", value.getName()); + assertEquals("#"+ Objects.hashCode(submodelAPI.getSubmodelId())+"#mySubmodelId-fileSmeIdShort.xml", value.getName()); assertEquals(expected.length(), value.length()); } @@ -104,6 +111,43 @@ public void fileSubmodelElementFileIsAutomaticallyDeleted() throws FileNotFoundE bucket.downloadToStream("fileSmeIdShort.xml", os); } + @SuppressWarnings("unchecked") + @Test + public void submodelElementInCollection() { + MongoDBSubmodelAPI submodelAPI = createAPIWithPreconfiguredSubmodel(); + SubmodelElementCollection collection = new SubmodelElementCollection("collection"); + MultiLanguageProperty mlprop = new MultiLanguageProperty("myMLP"); + collection.addSubmodelElement(mlprop); + submodelAPI.addSubmodelElement(collection); + + LangStrings expected = new LangStrings("de", "Hallo!"); + submodelAPI.updateSubmodelElement(collection.getIdShort() + "/" + mlprop.getIdShort(), expected); + + Object value = submodelAPI.getSubmodelElementValue(collection.getIdShort() + "/" + mlprop.getIdShort()); + + Collection updatedSubmodelElements = submodelAPI.getSubmodelElements(); + Map updatedCollectionMap = (Map) submodelAPI.getSubmodelElement("collection"); + Collection collectionValue = (Collection) updatedCollectionMap.get(Property.VALUE); + + assertEquals(1, updatedSubmodelElements.size()); + assertEquals(1, collectionValue.size()); + assertEquals(expected, value); + } + + @Test(expected = ResourceNotFoundException.class) + public void submodelElementInCollectionNotExistingAsHighLevelElement() { + MongoDBSubmodelAPI submodelAPI = createAPIWithPreconfiguredSubmodel(); + SubmodelElementCollection collection = new SubmodelElementCollection("collection"); + MultiLanguageProperty mlprop = new MultiLanguageProperty("myMLP"); + collection.addSubmodelElement(mlprop); + submodelAPI.addSubmodelElement(collection); + + LangStrings expected = new LangStrings("de", "Hallo!"); + submodelAPI.updateSubmodelElement(collection.getIdShort() + "/" + mlprop.getIdShort(), expected); + + submodelAPI.getSubmodelElementValue(mlprop.getIdShort()); + } + private void uploadDummyFile(MongoDBSubmodelAPI submodelAPI, String idShort) throws FileNotFoundException { File file = new File("application/xml"); file.setValue(""); diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/.env b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/.env index 6a62d5eb..425f95c4 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/.env +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/.env @@ -28,7 +28,7 @@ BASYX_IMAGE_NAME=eclipsebasyx/aas-server # ################## # The image tag of the image that is build for this component -BASYX_IMAGE_TAG=1.4.0 +BASYX_IMAGE_TAG=1.5.0 # ################## # Container Name diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aasServerConfig.properties b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aasServerConfig.properties index c52a96cf..3119afa2 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aasServerConfig.properties +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aasServerConfig.properties @@ -59,7 +59,7 @@ registry.path=http://localhost:4000/registry # Host specifies the endpoint of the deployed AAS component # If host is empty, the registered AAS endpoint is derived from the context properties -#registry.host=localhost +# aas.externalurl= # ############################# # Submodels diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_multiple_different_source.properties b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_multiple_different_source.properties index 5f4f3676..3efd83aa 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_multiple_different_source.properties +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_multiple_different_source.properties @@ -58,7 +58,7 @@ aas.aasxUpload=Enabled # Host specifies the endpoint of the deployed AAS component # If host is empty, the registered AAS endpoint is derived from the context properties -# registry.host= +# aas.externalurl= # ############################# # Submodels diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_json_source.properties b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_json_source.properties index c59c459a..37ea65cb 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_json_source.properties +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_json_source.properties @@ -58,7 +58,7 @@ aas.aasxUpload=Enabled # Host specifies the endpoint of the deployed AAS component # If host is empty, the registered AAS endpoint is derived from the context properties -# registry.host= +# aas.externalurl= # ############################# # Submodels diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_source.properties b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_source.properties index 2283d0fa..6988052a 100644 --- a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_source.properties +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/aas_single_source.properties @@ -58,7 +58,7 @@ aas.aasxUpload=Enabled # Host specifies the endpoint of the deployed AAS component # If host is empty, the registered AAS endpoint is derived from the context properties -# registry.host= +# aas.externalurl= # ############################# # Submodels diff --git a/basyx.components/basyx.components.docker/basyx.components.registry/pom.xml b/basyx.components/basyx.components.docker/basyx.components.registry/pom.xml index a5dadd74..c55aaf23 100644 --- a/basyx.components/basyx.components.docker/basyx.components.registry/pom.xml +++ b/basyx.components/basyx.components.docker/basyx.components.registry/pom.xml @@ -6,7 +6,7 @@ org.eclipse.basyx basyx.components.docker - 1.4.0 + 1.5.0 basyx.components.registry @@ -77,21 +77,21 @@ org.mongodb mongodb-driver-sync - 4.9.0 + 4.10.2 org.springframework.data spring-data-mongodb - 3.4.10 + 3.4.15 io.moquette moquette-broker - 0.16 + 0.17 test @@ -112,14 +112,14 @@ com.fasterxml.jackson.core jackson-core - 2.14.2 + 2.15.2 com.fasterxml.jackson.core jackson-annotations - 2.14.2 + 2.15.2 diff --git a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/configuration/BaSyxRegistryConfiguration.java b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/configuration/BaSyxRegistryConfiguration.java index 3e999e90..b5269b7c 100644 --- a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/configuration/BaSyxRegistryConfiguration.java +++ b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/configuration/BaSyxRegistryConfiguration.java @@ -24,6 +24,7 @@ ******************************************************************************/ package org.eclipse.basyx.components.registry.configuration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -73,16 +74,16 @@ public static Map getDefaultProperties() { } public BaSyxRegistryConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), Collections.emptyList()); } public BaSyxRegistryConfiguration(RegistryBackend backend) { - super(getDefaultProperties()); + super(getDefaultProperties(), Collections.emptyList()); setRegistryBackend(backend); } public BaSyxRegistryConfiguration(Map values) { - super(values); + super(values, Collections.emptyList()); } public void loadFromEnvironmentVariables() { diff --git a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBRegistryHandler.java b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBRegistryHandler.java index e5fa19f7..5f4bfeae 100644 --- a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBRegistryHandler.java +++ b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBRegistryHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * Copyright (C) 2021, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -32,30 +32,26 @@ import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor; import org.eclipse.basyx.aas.registration.memory.IRegistryHandler; import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; import org.eclipse.basyx.submodel.metamodel.api.identifier.IIdentifier; import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; - /** * A registry handler based on MongoDB * - * @author espen + * @author espen, jungjan, witt */ public class MongoDBRegistryHandler implements IRegistryHandler { private static final String DEFAULT_CONFIG_PATH = "mongodb.properties"; - protected BaSyxMongoDBConfiguration config; - protected MongoOperations mongoOps; - protected String collection; + private MongoDBBaSyxStorageAPI storageApi; - private static final String AASID = Identifiable.IDENTIFICATION + "." + Identifier.ID; - private static final String ASSETID = AASDescriptor.ASSET + "." + Identifiable.IDENTIFICATION + "." + Identifier.ID; + private static final String SHELL_IDENTIFICATION_ID = Identifiable.IDENTIFICATION + "." + Identifier.ID; + private static final String ASSET_IDENTIFICATION_ID = AASDescriptor.ASSET + "." + Identifiable.IDENTIFICATION + "." + Identifier.ID; /** * Receives the path of the configuration.properties file in it's constructor. @@ -63,7 +59,7 @@ public class MongoDBRegistryHandler implements IRegistryHandler { * @param config */ public MongoDBRegistryHandler(BaSyxMongoDBConfiguration config) { - this.setConfiguration(config); + this.initStorageApi(config); } /** @@ -71,9 +67,13 @@ public MongoDBRegistryHandler(BaSyxMongoDBConfiguration config) { * resource. */ public MongoDBRegistryHandler(String resourceConfigPath) { - config = new BaSyxMongoDBConfiguration(); + this(configFromResource(resourceConfigPath)); + } + + private static BaSyxMongoDBConfiguration configFromResource(String resourceConfigPath) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); config.loadFromResource(resourceConfigPath); - this.setConfiguration(config); + return config; } /** @@ -84,67 +84,58 @@ public MongoDBRegistryHandler() { } public void setConfiguration(BaSyxMongoDBConfiguration config) { - this.config = config; - MongoClient client = MongoClients.create(config.getConnectionUrl()); - this.mongoOps = new MongoTemplate(client, config.getDatabase()); - this.collection = config.getRegistryCollection(); + this.initStorageApi(config); + } + + private void initStorageApi(BaSyxMongoDBConfiguration config) { + String collectionName = config.getRegistryCollection(); + MongoDBBaSyxStorageAPIFactory storageApiFactory = new MongoDBBaSyxStorageAPIFactory<>(config, AASDescriptor.class, collectionName); + this.storageApi = storageApiFactory.create(); } @Override public boolean contains(IIdentifier identifier) { - String id = identifier.getId(); + String identificationId = identifier.getId(); Criteria hasId = new Criteria(); - hasId.orOperator(where(AASID).is(id), where(ASSETID).is(id)); - return mongoOps.exists(query(hasId), collection); + hasId.orOperator(where(SHELL_IDENTIFICATION_ID).is(identificationId), where(ASSET_IDENTIFICATION_ID).is(identificationId)); + + return getStorageConnection().exists(query(hasId), this.storageApi.getCollectionName()); + } + + private MongoOperations getStorageConnection() { + return (MongoOperations) this.storageApi.getStorageConnection(); } @Override public void remove(IIdentifier identifier) { - String id = identifier.getId(); + String indentificationId = identifier.getId(); Criteria hasId = new Criteria(); - hasId.orOperator(where(AASID).is(id), where(ASSETID).is(id)); - mongoOps.remove(query(hasId), collection); + hasId.orOperator(where(SHELL_IDENTIFICATION_ID).is(indentificationId), where(ASSET_IDENTIFICATION_ID).is(indentificationId)); + getStorageConnection().remove(query(hasId), this.storageApi.getCollectionName()); } @Override public void insert(AASDescriptor descriptor) { - mongoOps.insert(descriptor, collection); - // mongoOps added "_id" to descriptor after insert - removeMongoDBSpecificId(descriptor); + this.update(descriptor); } @Override public void update(AASDescriptor descriptor) { - String aasId = descriptor.getIdentifier().getId(); - Object result = mongoOps.findAndReplace(query(where(AASID).is(aasId)), descriptor, collection); - if (result == null) { - insert(descriptor); - } + this.storageApi.createOrUpdate(descriptor); } @Override public AASDescriptor get(IIdentifier identifier) { - String id = identifier.getId(); + String indentificationId = identifier.getId(); Criteria hasId = new Criteria(); - hasId.orOperator(where(AASID).is(id), where(ASSETID).is(id)); - AASDescriptor result = mongoOps.findOne(query(hasId), AASDescriptor.class, collection); - removeMongoDBSpecificId(result); - - return result; - } + hasId.orOperator(where(SHELL_IDENTIFICATION_ID).is(indentificationId), where(ASSET_IDENTIFICATION_ID).is(indentificationId)); - private void removeMongoDBSpecificId(AASDescriptor result) { - if (result != null) { - // Remove mongoDB-specific map attribute from AASDescriptor - result.remove("_id"); - } + AASDescriptor result = getStorageConnection().findOne(query(hasId), AASDescriptor.class, this.storageApi.getCollectionName()); + return this.storageApi.handleMongoDbIdAttribute(result); } @Override public List getAll() { - List result = mongoOps.findAll(AASDescriptor.class, collection); - // Remove mongoDB-specific map attribute from AASDescriptor - result.forEach(desc -> desc.remove("_id")); - return result; + return (List) this.storageApi.retrieveAll(); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBTaggedDirectory.java b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBTaggedDirectory.java index 68676631..5db11836 100644 --- a/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBTaggedDirectory.java +++ b/basyx.components/basyx.components.docker/basyx.components.registry/src/main/java/org/eclipse/basyx/components/registry/mongodb/MongoDBTaggedDirectory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021-2022 the Eclipse BaSyx Authors + * Copyright (C) 2021, 2022, 2023 the Eclipse BaSyx Authors * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -64,18 +64,18 @@ private boolean isTaggedDescriptor(ModelDescriptor descriptor) { } @Override - public void registerSubmodel(IIdentifier aas, TaggedSubmodelDescriptor descriptor) { - super.register(aas, descriptor); + public void registerSubmodel(IIdentifier shellIdentifier, TaggedSubmodelDescriptor descriptor) { + super.register(shellIdentifier, descriptor); addSubmodelTags(descriptor); - updateTagMap(aas, descriptor); + updateTagMap(shellIdentifier, descriptor); } - private void updateTagMap(IIdentifier aas, TaggedSubmodelDescriptor descriptor) { + private void updateTagMap(IIdentifier shellIdentifier, TaggedSubmodelDescriptor descriptor) { tagMap.values().forEach(tagSet -> { - tagSet.forEach(tagDesc -> { - if (descriptorEqualsToGivenAASId(aas, tagDesc)) { - if (!containsSubmodelDescriptor(descriptor, tagDesc)) - tagDesc.addSubmodelDescriptor(descriptor); + tagSet.forEach(taggedDescriptor -> { + if (descriptorEqualsToGivenAASId(shellIdentifier, taggedDescriptor)) { + if (!containsSubmodelDescriptor(descriptor, taggedDescriptor)) + taggedDescriptor.addSubmodelDescriptor(descriptor); } }); }); @@ -85,7 +85,7 @@ private boolean containsSubmodelDescriptor(TaggedSubmodelDescriptor descriptor, return tagDesc.getSubmodelDescriptorFromIdShort(descriptor.getIdShort()) != null; } - private boolean descriptorEqualsToGivenAASId(IIdentifier aas, TaggedAASDescriptor tagDesc) { - return tagDesc.getIdentifier().getId().equals(aas.getId()); + private boolean descriptorEqualsToGivenAASId(IIdentifier shellIdentifier, TaggedAASDescriptor taggedDescriptor) { + return taggedDescriptor.getIdentifier().getId().equals(shellIdentifier.getId()); } } diff --git a/basyx.components/basyx.components.docker/basyx.components.registry/src/test/resources/.env b/basyx.components/basyx.components.docker/basyx.components.registry/src/test/resources/.env index 733ae6fa..8fdd96e3 100644 --- a/basyx.components/basyx.components.docker/basyx.components.registry/src/test/resources/.env +++ b/basyx.components/basyx.components.docker/basyx.components.registry/src/test/resources/.env @@ -28,7 +28,7 @@ BASYX_IMAGE_NAME=eclipsebasyx/aas-registry # ################## # The image tag of the image that is build for this component -BASYX_IMAGE_TAG=1.4.0 +BASYX_IMAGE_TAG=1.5.0 # ################## # Container Name diff --git a/basyx.components/basyx.components.docker/pom.xml b/basyx.components/basyx.components.docker/pom.xml index 1962072a..0eb8d0ec 100644 --- a/basyx.components/basyx.components.docker/pom.xml +++ b/basyx.components/basyx.components.docker/pom.xml @@ -7,7 +7,7 @@ org.eclipse.basyx basyx.components - 1.4.0 + 1.5.0 basyx.components.docker @@ -28,7 +28,7 @@ org.codehaus.mojo properties-maven-plugin - 1.1.0 + 1.2.0 initialize @@ -95,7 +95,7 @@ io.fabric8 docker-maven-plugin - 0.42.0 + 0.43.4 @@ -170,7 +170,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.0.0 + 3.1.2 @@ -223,7 +223,7 @@ org.eclipse.basyx basyx.components.lib - 1.4.0 + 1.5.0 diff --git a/basyx.components/basyx.components.lib/pom.xml b/basyx.components/basyx.components.lib/pom.xml index 1042b9f4..dab3f087 100644 --- a/basyx.components/basyx.components.lib/pom.xml +++ b/basyx.components/basyx.components.lib/pom.xml @@ -6,7 +6,7 @@ org.eclipse.basyx basyx.components - 1.4.0 + 1.5.0 basyx.components.lib @@ -80,22 +80,36 @@ 42.6.0 + + + org.mongodb + mongodb-driver-sync + 4.10.2 + + + + + org.springframework.data + spring-data-mongodb + 3.4.15 + + org.camunda.bpm camunda-engine - 7.18.0 + 7.19.0 org.camunda.bpm.model camunda-bpmn-model - 7.18.0 + 7.19.0 com.h2database h2 - 2.2.220 + 2.2.224 diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxConfiguration.java index baae5470..b0d1e27b 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxConfiguration.java @@ -43,16 +43,33 @@ public class BaSyxConfiguration { // Properties in this configuration private Map values; + private List propertiesExcludedFromLogging; + + @Deprecated /** * Constructor that takes the configuration's default values. All the keys in * the map are the name of the properties that are stored and loaded in this * configuration. + * + * @param defaultValues + * @deprecated Use {@link #BaSyxConfiguration(Map, List)} to ensure proper + * exclusion is configured */ public BaSyxConfiguration(Map defaultValues) { this.values = defaultValues; } + /** + * + * @param defaultValues + * @param propertiesExcludedFromLogging + */ + public BaSyxConfiguration(Map defaultValues, List propertiesExcludedFromLogging) { + this(defaultValues); + this.propertiesExcludedFromLogging = propertiesExcludedFromLogging; + } + public static InputStream getResourceStream(String relativeResourcePath) { ClassLoader classLoader = BaSyxConfiguration.class.getClassLoader(); return classLoader.getResourceAsStream(relativeResourcePath); @@ -150,15 +167,14 @@ public void loadFromProperties(Properties properties) { for (Object property : properties.keySet()) { String propertyName = (String) property; String loaded = properties.getProperty(propertyName); - if (values.containsKey(propertyName)) { - logger.info(propertyName + ": '" + loaded + "'"); - } else { - logger.debug(propertyName + ": '" + loaded + "'"); - } + + logPropertyVariable(propertyName, loaded); + values.put(propertyName, loaded); } } + /** * Method for subclasses to read specific environment variables * @@ -182,7 +198,7 @@ protected void loadFromEnvironmentVariables(String prefix, String... properties) for (String propName : properties) { String result = getEnvironmentVariable(prefix, propName, usesDeprecatedNamingConvention); if (result != null) { - logger.info("Environment - " + propName + ": " + result); + logEnvironmentVariable(propName, result); setProperty(propName, result); } } @@ -191,6 +207,26 @@ protected void loadFromEnvironmentVariables(String prefix, String... properties) } } + private void logPropertyVariable(String propertyName, String result) { + if (isPropertyExcludedFromLogging(propertyName)) { + result = "*****"; + } + + logger.info("Property File - " + propertyName + ": '" + result + "'"); + } + + private void logEnvironmentVariable(String propertyName, String result) { + if (isPropertyExcludedFromLogging(propertyName)) { + result = "*****"; + } + + logger.info("Environment - " + propertyName + ": " + result); + } + + private boolean isPropertyExcludedFromLogging(String propertyName) { + return propertiesExcludedFromLogging.contains(propertyName); + } + private String getEnvironmentVariable(String prefix, String propName, boolean usesDeprecatedNamingConvention) { if (usesDeprecatedNamingConvention) { return getEnvironmentVariableDeprecatedNamingConvention(prefix, propName); diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxContextConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxContextConfiguration.java index cfe157dc..59799622 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxContextConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxContextConfiguration.java @@ -24,7 +24,9 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.basyx.vab.modelprovider.VABPathTools; @@ -85,14 +87,14 @@ public static Map getDefaultProperties() { * Empty Constructor - use default values */ public BaSyxContextConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), getPropertiesExcludedFromLogging()); } /** * Constructor with predefined value map */ public BaSyxContextConfiguration(Map values) { - super(values); + super(values, getPropertiesExcludedFromLogging()); } /** @@ -287,4 +289,8 @@ private String getProtocol() { } return "http://"; } + + private static List getPropertiesExcludedFromLogging() { + return Collections.singletonList(SSL_KEY_PASSWORD); + } } diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxDockerConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxDockerConfiguration.java index 12ea074e..6971281e 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxDockerConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxDockerConfiguration.java @@ -24,6 +24,7 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -68,14 +69,14 @@ public static Map getDefaultProperties() { * Empty Constructor - use default values */ public BaSyxDockerConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), Collections.emptyList()); } /** * Constructor with predefined value map */ public BaSyxDockerConfiguration(Map values) { - super(values); + super(values, Collections.emptyList()); } /** diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMongoDBConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMongoDBConfiguration.java index e0e04788..7cac1e79 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMongoDBConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMongoDBConfiguration.java @@ -24,7 +24,9 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -74,14 +76,14 @@ public static Map getDefaultProperties() { * Constructor with predefined value map */ public BaSyxMongoDBConfiguration(Map values) { - super(values); + super(values, getPropertiesExcludedFromLogging()); } /** * Empty Constructor - use default values */ public BaSyxMongoDBConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), getPropertiesExcludedFromLogging()); } /** @@ -202,4 +204,8 @@ public String getFileCollection() { public void setFileCollection(String fileCollection) { setProperty(FILE_COLLECTION, fileCollection); } + + private static List getPropertiesExcludedFromLogging() { + return Collections.singletonList(CONNECTIONURL); + } } diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMqttConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMqttConfiguration.java index d53ddf9d..43fb22c6 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMqttConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxMqttConfiguration.java @@ -24,6 +24,7 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -81,14 +82,14 @@ public static Map getDefaultProperties() { * Constructor with predefined value map */ public BaSyxMqttConfiguration(Map values) { - super(values); + super(values, getPropertiesExcludedFromLogging()); } /** * Empty Constructor - use default values */ public BaSyxMqttConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), getPropertiesExcludedFromLogging()); } /** @@ -209,6 +210,10 @@ public void setWhitelist(String submodelId, List elementIds) { } public String getClientId() { - return getProperty(CLIENT_ID); + return getProperty(CLIENT_ID); + } + + private static List getPropertiesExcludedFromLogging() { + return Collections.singletonList(PASS); } } diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSQLConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSQLConfiguration.java index 936ec09f..dd77a62f 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSQLConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSQLConfiguration.java @@ -24,7 +24,9 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -71,14 +73,14 @@ public static Map getDefaultProperties() { * Constructor with predefined value map */ public BaSyxSQLConfiguration(Map values) { - super(values); + super(values, getPropertiesExcludedFromLogging()); } /** * Empty Constructor - use default values */ public BaSyxSQLConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), getPropertiesExcludedFromLogging()); } /** @@ -153,4 +155,8 @@ public String getPrefix() { public void setPrefix(String prefix) { setProperty(PREFIX, prefix); } + + private static List getPropertiesExcludedFromLogging() { + return Collections.singletonList(PASS); + } } diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSecurityConfiguration.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSecurityConfiguration.java index aab653cc..ebe8fff5 100644 --- a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSecurityConfiguration.java +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/configuration/BaSyxSecurityConfiguration.java @@ -24,6 +24,7 @@ ******************************************************************************/ package org.eclipse.basyx.components.configuration; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -78,14 +79,14 @@ public static Map getDefaultProperties() { * Constructor with predefined value map */ public BaSyxSecurityConfiguration(Map values) { - super(values); + super(values, Collections.emptyList()); } /** * Empty Constructor - use default values */ public BaSyxSecurityConfiguration() { - super(getDefaultProperties()); + super(getDefaultProperties(), Collections.emptyList()); } /** diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPI.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPI.java new file mode 100644 index 00000000..5df97a9a --- /dev/null +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPI.java @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.basyx.components.internal.mongodb; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.extensions.internal.storage.BaSyxStorageAPI; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.metamodel.map.identifier.Identifier; +import org.eclipse.basyx.submodel.metamodel.map.qualifier.Identifiable; +import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.File; +import org.eclipse.basyx.vab.exception.provider.ResourceNotFoundException; +import org.springframework.data.mongodb.core.FindAndReplaceOptions; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.client.result.DeleteResult; + +/** + * Provides BaSyxStorageAPI implementation for MongoDB + * + * @author fischer, jungjan, witt + * + * @param + */ +public class MongoDBBaSyxStorageAPI extends BaSyxStorageAPI { + private final String INDEX_KEY = Identifiable.IDENTIFICATION + "." + Identifier.ID; + + protected BaSyxMongoDBConfiguration config; + protected MongoClient client; + protected MongoOperations mongoOps; + + /** + * @deprecated Please use the other constructor with MongoClient client. + * Using this constructor may lead to inefficient resource utilization. + */ + @Deprecated + public MongoDBBaSyxStorageAPI(String collectionName, Class type, BaSyxMongoDBConfiguration config) { + this(collectionName, type, config, MongoClients.create(config.getConnectionUrl())); + } + + public MongoDBBaSyxStorageAPI(String collectionName, Class type, BaSyxMongoDBConfiguration config, MongoClient client) { + super(collectionName, type); + this.config = config; + this.client = client; + this.mongoOps = new MongoTemplate(client, config.getDatabase()); + } + + @Override + public T createOrUpdate(T obj) { + String key = getKey(obj); + if (alreadyExists(key)) { + return update(obj, key); + } + + T created = mongoOps.insert(obj, getCollectionName()); + return handleMongoDbIdAttribute(created); + } + + private boolean alreadyExists(String key) { + Query hasId = query(where(INDEX_KEY).is(key)); + return mongoOps.exists(hasId, getCollectionName()); + } + + @Override + public T update(T obj, String key) { + T replaced = findAndReplaceIfExists(obj, key); + if (replaced == null) { + logger.warn("Could not execute update for key {} as it does not exist in the database; Creating new entry...", key); + return createOrUpdate(obj); + } + replaced = handleMongoDbIdAttribute(replaced); + return replaced; + } + + private T findAndReplaceIfExists(T obj, String key) { + Query hasId = query(where(INDEX_KEY).is(key)); + FindAndReplaceOptions replacementOptions = setupReplacemantOptionsToReturnNew(); + T replaced = mongoOps.findAndReplace(hasId, obj, replacementOptions.returnNew(), getCollectionName()); + return replaced; + } + + private FindAndReplaceOptions setupReplacemantOptionsToReturnNew() { + FindAndReplaceOptions replacementOptions = FindAndReplaceOptions.empty(); + replacementOptions.returnNew(); + return replacementOptions; + } + + @SuppressWarnings("unchecked") + public T handleMongoDbIdAttribute(T data) { + if (data instanceof Map) + ((Map) data).remove("_id"); + return data; + } + + @Override + public boolean delete(String key) { + Query hasId = query(where(INDEX_KEY).is(key)); + DeleteResult result = mongoOps.remove(hasId, getCollectionName()); + return result.getDeletedCount() == 1L; + } + + @Override + public void createCollectionIfNotExists(String collectionName) { + // MongoOperations implicitly creates Collections. + } + + @Override + public void deleteCollection() { + mongoOps.dropCollection(getCollectionName()); + } + + @Override + public T rawRetrieve(String key) { + Query hasId = query(where(INDEX_KEY).is(key)); + var result = mongoOps.findOne(hasId, TYPE, getCollectionName()); + if (result == null) { + throw new ResourceNotFoundException("No Object for key '" + key + "' found in the database."); + } + result = handleMongoDbIdAttribute(result); + return result; + } + + @Override + public java.io.File getFile(String idShortPath, String parentKey, Map objMap) { + try { + File fileSubmodelElement = File.createAsFacade(objMap); + GridFSBucket bucket = MongoDBFileHelper.getGridFSBucket(client, config); + String fileName = MongoDBFileHelper.constructFileName(parentKey, fileSubmodelElement, idShortPath); + java.io.File file = new java.io.File(fileName); + // check if file with this filename exist in MongoDB + // there might be older files constructed with old (=legacy) filenames, use this one instead + // the real file in file system still uses the new filename pattern! + if (!MongoDBFileHelper.fileExists(bucket, fileName)) { + fileName = MongoDBFileHelper.legacyFileName(parentKey, fileSubmodelElement, idShortPath); + } + try (FileOutputStream fileOutputStream = new FileOutputStream(file)) { + bucket.downloadToStream(fileName, fileOutputStream); + } + return file; + } catch (IOException e) { + throw new ResourceNotFoundException("The File Submodel Element does not contain a File"); + } + } + + @Override + public String writeFile(String idShortPath, String parentKey, InputStream inputStream, ISubmodelElement element) { + return MongoDBFileHelper.updateFileInDB(client, config, parentKey, inputStream, element, idShortPath); + } + + @Override + public void deleteFile(Submodel submodel, String idShort) { + MongoDBFileHelper.deleteAllFilesFromGridFsIfIsFileSubmodelElement(client, config, submodel, idShort); + } + + @Override + public Collection rawRetrieveAll() { + Collection data = mongoOps.findAll(TYPE, getCollectionName()); + data = data.stream() + .map(this::handleMongoDbIdAttribute) + .collect(Collectors.toList()); + return data; + } + + @Override + public Object getStorageConnection() { + return mongoOps; + } + + public MongoClient getClient() { + return client; + } +} diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPIFactory.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPIFactory.java new file mode 100644 index 00000000..ed20484c --- /dev/null +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBBaSyxStorageAPIFactory.java @@ -0,0 +1,150 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.basyx.components.internal.mongodb; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +/** + * + * @author fischer, jung + * + * @param + * Generic type of the objects to be managed by the produced API + */ +public class MongoDBBaSyxStorageAPIFactory { + private static Map knownClients = new HashMap<>(); + + private final BaSyxMongoDBConfiguration config; + private final Class type; + private final String collectionName; + private final MongoClient client; + + /** + * Constructor for a generic BaSyxMongoDBAPIFactory + * + * @param config + * BaSyx MongoDB Configuration + * @param type + * Must be the exact same type as the type of the generic parameter + * {@code } + * @param collectionName + * The name of the collection, managed by the produced API + */ + public MongoDBBaSyxStorageAPIFactory(BaSyxMongoDBConfiguration config, Class type, String collectionName) { + this.config = config; + this.type = type; + this.collectionName = collectionName; + this.client = null; + } + + /** + * Constructor for a generic BaSyxMongoDBAPIFactory + * + * @param config + * BaSyx MongoDB Configuration + * @param type + * Must be the exact same type as the type of the generic parameter + * {@code } + * @param collectionName + * The name of the collection, managed by the produced API + * @param client + * The client of the MongoDB connection + */ + public MongoDBBaSyxStorageAPIFactory(BaSyxMongoDBConfiguration config, Class type, String collectionName, MongoClient client) { + this.config = config; + this.type = type; + this.collectionName = collectionName; + this.client = client; + } + + /** + * Creates a generic MongoDBBaSyxStorageAPI. This method has been designed to + * ensure efficient resource utilization by reusing existing storage clients if + * they already exist. + * + * @param + * @param config + * BaSyx MongoDB Configuration + * @param type + * Must be the exact same type as the type of the generic parameter + * {@code } + * @param collectionName + * The name of the collection, managed by the produced API + * @param client + * The client of the MongoDB connection + * @return + */ + public static synchronized MongoDBBaSyxStorageAPI create(String collectionName, Class type, BaSyxMongoDBConfiguration config, MongoClient client) { + String connectionUrl = config.getConnectionUrl(); + if (!knownClients.containsKey(connectionUrl)) { + knownClients.put(connectionUrl, client); + } + return new MongoDBBaSyxStorageAPI(collectionName, type, config, knownClients.get(connectionUrl)); + } + + /** + * Creates a generic MongoDBBaSyxStorageAPI. If an appropriate MongoClient + * already exists, it will be reused. + * + * @param + * @param config + * BaSyx MongoDB Configuration + * @param type + * Must be the exact same type as the type of the generic parameter + * {@code } + * @param collectionName + * The name of the collection, managed by the produced API + * @return + */ + public static synchronized MongoDBBaSyxStorageAPI create(String collectionName, Class type, BaSyxMongoDBConfiguration config) { + String connectionUrl = config.getConnectionUrl(); + return knownClients.containsKey(connectionUrl) + ? create(collectionName, type, config, knownClients.get(connectionUrl)) + : create(collectionName, type, config, createNewClient(config)); + } + + private static MongoClient createNewClient(BaSyxMongoDBConfiguration config) { + return MongoClients.create(config.getConnectionUrl()); + } + + /** + * Creates a generic MongoDBBaSyxStorageAPI. If an appropriate MongoClient + * already exists, it will be reused + * + * @return + */ + public MongoDBBaSyxStorageAPI create() { + return this.client == null + ? MongoDBBaSyxStorageAPIFactory.create(collectionName, type, config) + : MongoDBBaSyxStorageAPIFactory.create(collectionName, type, config, client); + } +} diff --git a/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBFileHelper.java b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBFileHelper.java new file mode 100644 index 00000000..18f602d8 --- /dev/null +++ b/basyx.components/basyx.components.lib/src/main/java/org/eclipse/basyx/components/internal/mongodb/MongoDBFileHelper.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.basyx.components.internal.mongodb; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.tika.mime.MimeType; +import org.apache.tika.mime.MimeTypeException; +import org.apache.tika.mime.MimeTypes; +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.submodel.metamodel.map.submodelelement.dataelement.File; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.gridfs.GridFSBucket; +import com.mongodb.client.gridfs.GridFSBuckets; +import com.mongodb.client.model.Filters; + +/** + * Supports MongoDB file handling + * + * @author fischer + * + */ +public class MongoDBFileHelper { + private MongoDBFileHelper() { + } + + @SuppressWarnings("unchecked") + public static String updateFileInDB(MongoClient client, BaSyxMongoDBConfiguration config, String submodelId, InputStream newValue, ISubmodelElement element, String idShortPath) { + File file = File.createAsFacade((Map) element); + GridFSBucket bucket = getGridFSBucket(client, config); + String fileName = constructFileName(submodelId, file, idShortPath); + deleteAllDuplicateFiles(bucket, fileName, legacyFileName(submodelId, file, idShortPath)); + bucket.uploadFromStream(fileName, newValue); + return fileName; + } + + @SuppressWarnings("unchecked") + public static void deleteAllFilesFromGridFsIfIsFileSubmodelElement(MongoClient client, BaSyxMongoDBConfiguration config, Submodel sm, String idShort) { + Map submodelElement = (Map) sm.getSubmodelElement(idShort); + if (!File.isFile(submodelElement)) + return; + File file = File.createAsFacade(submodelElement); + GridFSBucket bucket = MongoDBFileHelper.getGridFSBucket(client, config); + deleteAllDuplicateFiles(bucket, constructFileName(sm.getIdentification().getId(), file, idShort), legacyFileName(sm.getIdentification().getId(), file, idShort)); + } + + protected static boolean fileExists(GridFSBucket bucket, String fileName) { + return bucket.find(Filters.eq("filename", fileName)).first() != null; + } + + public static GridFSBucket getGridFSBucket(MongoClient client, BaSyxMongoDBConfiguration config) { + MongoDatabase database = client.getDatabase(config.getDatabase()); + return GridFSBuckets.create(database, config.getFileCollection()); + } + + private static void deleteAllDuplicateFiles(GridFSBucket bucket, String... fileNames) { + bucket.find(Filters.or( + Arrays.stream(fileNames) + .map(fileName -> Filters.eq("filename", fileName)) + .collect(Collectors.toList()))) + .forEach(gridFile -> bucket.delete(gridFile.getObjectId())); + } + + public static String constructFileName(String submodelId, File file, String idShortPath) { + String fileName = submodelId + "-" + idShortPath.replaceAll("/", "-") + getFileExtension(file); + // replace those chars that are not permitted on filesystems + fileName = fileName.replaceAll("[^a-zA-Z0-9-_\\.]", "_"); + // ensure the filename is still unique to be used as key in MongoDB storage layer + return String.format("#%s#", Objects.hashCode(submodelId)).concat(fileName); + } + /** + * This method is kept for backwards compatibility to still find files from DB which where stored with old scheme. + * @param file the filename of this file is requested. + * @param idShortPath the shortId of the given file. + * @return the filename as it was constructed in older versions. + */ + public static String legacyFileName(String submodelId, File file, String idShortPath) { + return submodelId + "-" + idShortPath.replaceAll("/", "-") + getFileExtension(file); + } + + + private static String getFileExtension(File file) { + MimeTypes allTypes = MimeTypes.getDefaultMimeTypes(); + try { + MimeType mimeType = allTypes.forName(file.getMimeType()); + return mimeType.getExtension(); + } catch (MimeTypeException e) { + e.printStackTrace(); + return ""; + } + } + +} diff --git a/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPI.java b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPI.java new file mode 100644 index 00000000..a9e373e3 --- /dev/null +++ b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPI.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.regression.components.internal.mongodb; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; +import org.eclipse.basyx.extensions.internal.storage.BaSyxStorageAPI; +import org.eclipse.basyx.submodel.metamodel.map.Submodel; +import org.eclipse.basyx.testsuite.regression.extensions.storage.BaSyxStorageAPISuite; +import org.junit.After; +import org.springframework.data.mongodb.core.MongoOperations; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +public class TestMongoDBBaSyxStorageAPI extends BaSyxStorageAPISuite { + private final static String connectionString = "mongodb://localhost:27017"; + private final static String testSubmodelCollectioName = "testsubnmodels"; + + private static BaSyxMongoDBConfiguration config = createTestConfig(connectionString, testSubmodelCollectioName); + + private static BaSyxMongoDBConfiguration createTestConfig(String connectionstring, String submodelcollectioname) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + config.setConnectionUrl(connectionstring); + config.setSubmodelCollection(submodelcollectioname); + return config; + } + + @After + public void cleanUp() { + this.storageAPI.deleteCollection(); + } + + @Override + protected BaSyxStorageAPI getStorageAPI() { + MongoDBBaSyxStorageAPIFactory storageAPIFactory = new MongoDBBaSyxStorageAPIFactory(config, Submodel.class, config.getSubmodelCollection()); + return storageAPIFactory.create(); + } + + @Override + protected BaSyxStorageAPI getSecondStorageAPI() { + // Please use the MongoDBBaSyxStorageAPIFactory in production code. + MongoClient client = MongoClients.create(connectionString); + return new MongoDBBaSyxStorageAPI(testSubmodelCollectioName, Submodel.class, config, client); + } + + @Override + public void createCollectionIfNotExists() { + // Not Implemented for MongoDBBaSyxStorageAPI as Collections are created + // dynamically. + } + + @Override + public void deleteCollection() { + triggerCollectionCreation(); + MongoOperations mongoOps = (MongoOperations) storageAPI.getStorageConnection(); + assertTrue(mongoOps.collectionExists(testSubmodelCollectioName)); + + this.storageAPI.deleteCollection(); + assertFalse(mongoOps.collectionExists(testSubmodelCollectioName)); + } + + private void triggerCollectionCreation() { + this.storageAPI.createOrUpdate(testSubmodel); + } +} diff --git a/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPIFactory.java b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPIFactory.java new file mode 100644 index 00000000..099e6cc1 --- /dev/null +++ b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/components/internal/mongodb/TestMongoDBBaSyxStorageAPIFactory.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ +package org.eclipse.basyx.regression.components.internal.mongodb; + +import static org.junit.Assert.assertSame; + +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPI; +import org.eclipse.basyx.components.internal.mongodb.MongoDBBaSyxStorageAPIFactory; +import org.eclipse.basyx.testsuite.regression.extensions.storage.VABTestType; +import org.junit.Test; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; + +public class TestMongoDBBaSyxStorageAPIFactory { + static BaSyxMongoDBConfiguration config = createTestConfig("mongodb://localhost:27017"); + static MongoClient client = MongoClients.create(config.getConnectionUrl()); + + private static BaSyxMongoDBConfiguration createTestConfig(String connectionUrl) { + BaSyxMongoDBConfiguration config = new BaSyxMongoDBConfiguration(); + config.setConnectionUrl(connectionUrl); + return config; + } + + @Test + public void assureFactoryReusesMongoClient() throws NoSuchFieldException, SecurityException { + MongoDBBaSyxStorageAPI storageAPI0 = new MongoDBBaSyxStorageAPIFactory(config, VABTestType.class, "c0", client).create(); + MongoDBBaSyxStorageAPI storageAPI1 = new MongoDBBaSyxStorageAPIFactory(config, VABTestType.class, "c1", client).create(); + MongoDBBaSyxStorageAPI storageAPI2 = new MongoDBBaSyxStorageAPIFactory(config, VABTestType.class, "c2").create(); + MongoDBBaSyxStorageAPI storageAPI3 = MongoDBBaSyxStorageAPIFactory.create("c3", VABTestType.class, config, client); + MongoDBBaSyxStorageAPI storageAPI4 = MongoDBBaSyxStorageAPIFactory.create("c4", VABTestType.class, config); + + assertSame(storageAPI0.getClient(), storageAPI1.getClient()); + assertSame(storageAPI0.getClient(), storageAPI2.getClient()); + assertSame(storageAPI0.getClient(), storageAPI3.getClient()); + assertSame(storageAPI0.getClient(), storageAPI4.getClient()); + } + + @Test + public void dynamicCreatedFirst() { + MongoDBBaSyxStorageAPI storageAPI0 = new MongoDBBaSyxStorageAPIFactory(config, VABTestType.class, "c0").create(); + MongoDBBaSyxStorageAPI storageAPI1 = new MongoDBBaSyxStorageAPIFactory(config, VABTestType.class, "c1", client).create(); + + assertSame(storageAPI0.getClient(), storageAPI1.getClient()); + } + +} diff --git a/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/support/configuration/DummyBaSyxConfiguration.java b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/support/configuration/DummyBaSyxConfiguration.java index 3621ff3c..6030ac7b 100644 --- a/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/support/configuration/DummyBaSyxConfiguration.java +++ b/basyx.components/basyx.components.lib/src/test/java/org/eclipse/basyx/regression/support/configuration/DummyBaSyxConfiguration.java @@ -1,5 +1,6 @@ package org.eclipse.basyx.regression.support.configuration; +import java.util.Collections; import java.util.HashMap; import org.eclipse.basyx.components.configuration.BaSyxConfiguration; @@ -19,7 +20,7 @@ public class DummyBaSyxConfiguration extends BaSyxConfiguration { public static final String AASX_UPLOAD = "aas.aasxUpload"; public DummyBaSyxConfiguration() { - super(new HashMap()); + super(new HashMap(), Collections.emptyList()); } public void loadFromEnvironmentVariables() { diff --git a/basyx.components/pom.xml b/basyx.components/pom.xml index c1eb7622..059ab2c7 100644 --- a/basyx.components/pom.xml +++ b/basyx.components/pom.xml @@ -5,7 +5,7 @@ org.eclipse.basyx basyx.components - 1.4.0 + 1.5.0 BaSyx Components BaSyx Off-the-Shelf Components https://www.eclipse.org/basyx/ @@ -33,6 +33,10 @@ false + + Eclipse BaSyx SDK GitHub Packages + https://maven.pkg.github.com/eclipse-basyx/basyx-java-sdk + @@ -88,7 +92,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.0 attach-sources @@ -124,7 +128,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0 + 3.1.2 **/*HTTP* @@ -137,7 +141,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.0.0 + 3.1.2 @@ -151,7 +155,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.0 8 @@ -170,7 +174,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.0.1 + 3.1.0 sign-artifacts @@ -269,7 +273,7 @@ ch.qos.logback logback-classic - 1.4.6 + 1.4.11 @@ -277,14 +281,14 @@ org.eclipse.basyx basyx.sdk - 1.4.0 + 1.5.0 org.eclipse.basyx basyx.sdk - 1.4.0 + 1.5.0 tests test