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 7c8d78c4..188f300b 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 @@ -251,22 +251,12 @@ public void startComponent() { loadAASServerFeaturesFromConfig(); initializeAASServerFeatures(); - BaSyxContext context = contextConfig.createBaSyxContext(); - context.addServletMapping("/*", createAggregatorServlet()); - addAASServerFeaturesToContext(context); - - // An initial AAS has been loaded from the drive? - if (aasBundles != null) { - createBasyxResourceDirectoryIfNotExists(); - - addAasxFilesResourceServlet(context); - // 2. Fix the file paths according to the servlet configuration - modifyFilePaths(contextConfig.getHostname(), contextConfig.getPort(), getRootFilePathWithContext(contextConfig.getContextPath())); + initializeAasBundles(context); - registerWhitelistedSubmodels(); - } + context.addServletMapping("/*", createAggregatorServlet()); + addAASServerFeaturesToContext(context); logger.info("Start the server"); server = new BaSyxHTTPServer(context); @@ -523,6 +513,24 @@ private void initializeAASServerFeatures() { } } + private void initializeAasBundles(BaSyxContext context) { + loadAASBundles(); + + if (aasBundles == null) + return; + + logger.info("Initializing AAS Bundles"); + + createBasyxResourceDirectoryIfNotExists(); + + addAasxFilesResourceServlet(context); + + // 2. Fix the file paths according to the servlet configuration + modifyFilePaths(contextConfig.getHostname(), contextConfig.getPort(), getRootFilePathWithContext(contextConfig.getContextPath())); + + registerWhitelistedSubmodels(); + } + private void cleanUpAASServerFeatures() { for (IAASServerFeature aasServerFeature : aasServerFeatureList) { aasServerFeature.cleanUp(); @@ -585,7 +593,6 @@ private Collection getFlatAASBundles() { private VABHTTPInterface createAggregatorServlet() { aggregator = createAASAggregator(); - loadAASBundles(); if (aasBundles != null) { try (final var ignored = ElevatedCodeAuthentication.enterElevatedCodeAuthenticationArea()) { @@ -844,5 +851,6 @@ private void createBasyxResourceDirectoryIfNotExists() { directory.mkdir(); } + } 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 index 0df5f9f8..2df887c1 100644 --- 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 @@ -205,8 +205,7 @@ public java.io.File getSubmodelElementFile(String idShortPath) { 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); + storageApi.writeFile(idShortPath, getSubmodel().getIdentification().getId(), fileStream, element); } } 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 d92a5913..140c8d06 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 @@ -42,7 +42,6 @@ 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 com.mongodb.client.MongoClient; @@ -159,12 +158,12 @@ public ISubmodel getSubmodelbyIdShort(String idShort) throws ResourceNotFoundExc @Override public ISubmodelAPI getSubmodelAPIById(IIdentifier identifier) throws ResourceNotFoundException { Submodel submodel = (Submodel) getSubmodel(identifier); - return new VABSubmodelAPIFactory().create(submodel); + return submodelApiFactory.create(submodel); } @Override public ISubmodelAPI getSubmodelAPIByIdShort(String idShort) throws ResourceNotFoundException { Submodel submodel = (Submodel) getSubmodelbyIdShort(idShort); - return new VABSubmodelAPIFactory().create(submodel); + return submodelApiFactory.create(submodel); } } 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 ef9d88c4..0b797664 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 @@ -25,8 +25,13 @@ package org.eclipse.basyx.regression.AASServer; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.Collection; import java.util.Iterator; import java.util.Map; @@ -36,6 +41,14 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Response; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.eclipse.basyx.aas.aggregator.restapi.AASAggregatorProvider; import org.eclipse.basyx.aas.manager.ConnectedAssetAdministrationShellManager; import org.eclipse.basyx.aas.metamodel.connected.ConnectedAssetAdministrationShell; import org.eclipse.basyx.aas.metamodel.map.descriptor.AASDescriptor; @@ -44,12 +57,14 @@ import org.eclipse.basyx.aas.metamodel.map.descriptor.SubmodelDescriptor; import org.eclipse.basyx.aas.registration.api.IAASRegistry; import org.eclipse.basyx.aas.registration.memory.InMemoryRegistry; +import org.eclipse.basyx.components.configuration.BaSyxContextConfiguration; 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.ISubmodelElementCollection; import org.eclipse.basyx.submodel.metamodel.api.submodelelement.dataelement.IFile; import org.eclipse.basyx.submodel.metamodel.connected.submodelelement.ConnectedSubmodelElementCollection; import org.eclipse.basyx.submodel.metamodel.connected.submodelelement.dataelement.ConnectedFile; +import org.eclipse.basyx.vab.modelprovider.VABPathTools; import org.eclipse.basyx.vab.protocol.api.IConnectorFactory; import org.eclipse.basyx.vab.protocol.http.connector.HTTPConnectorFactory; import org.glassfish.jersey.client.JerseyClientBuilder; @@ -57,12 +72,13 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; /** * Suite for testing that the XMLAAS servlet is set up correctly. The tests here * can be used by the servlet test itself and the integration test * - * @author schnicke, espen + * @author schnicke, espen, mateusmolina * */ public abstract class AASXSuite { @@ -86,6 +102,7 @@ public abstract class AASXSuite { protected static final String fileShortIdPath = "file"; // Has to be individualized by each test inheriting from this suite + // Default configuration is provided by buildEnpoints method protected static String aasEndpoint; protected static String smEndpoint; protected static String aasAEndpoint; @@ -137,37 +154,26 @@ public void testGetSingleSubmodel() throws Exception { @Test public void testGetSingleModule() throws Exception { - 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); + final String FILE_ENDING = VABPathTools.buildPath(new String[] { "basyx-temp", "aasx0", "files", "aasx", "Nameplate", "marking_rcm.jpg" }, 0); + checkFile(VABPathTools.concatenatePaths(rootEndpoint, FILE_ENDING)); // Get the submdoel nameplate 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(); - // navigate to the File element - Iterator iter = values.iterator(); - while (iter.hasNext()) { - ISubmodelElement element = iter.next(); - if (element instanceof ConnectedFile) { - ConnectedFile connectedFile = (ConnectedFile) element; - // get value of the file element + ConnectedFile fileSE = retrieveFileSEFromCollection(marking_rcm); - String fileurl = connectedFile.getValue(); - assertTrue(fileurl.endsWith(FILE_ENDING)); - } - } + assertTrue(fileSE.getValue().endsWith(FILE_ENDING)); } - + @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"; + final String FILE_ENDING_A = VABPathTools.buildPath(new String[] { "basyx-temp", "aasx1", "files", "aasx", "files", "text.txt" }, 0); + final String FILE_ENDING_B = VABPathTools.buildPath(new String[] { "basyx-temp", "aasx2", "files", "aasx", "files", "text.txt" }, 0); - checkFile(rootEndpoint + FILE_ENDING_A); - checkFile(rootEndpoint + FILE_ENDING_B); + checkFile(VABPathTools.concatenatePaths(rootEndpoint, FILE_ENDING_A)); + checkFile(VABPathTools.concatenatePaths(rootEndpoint, FILE_ENDING_B)); ISubmodel smA = manager.retrieveSubmodel(aasAId, smAId); ISubmodel smB = manager.retrieveSubmodel(aasBId, smBId); @@ -194,6 +200,63 @@ public void testAllFiles() throws Exception { } + @Test + public void fileValueIsCorrectlyUpdated_whenFileIsUpdated() throws Exception { + final String UPLOAD_ENDPOINT = VABPathTools.concatenatePaths(smEndpoint, "submodelElements", "Marking_CRUUS", "File", "upload"); + + ISubmodel nameplate = manager.retrieveSubmodel(aasId, smId); + ConnectedSubmodelElementCollection marking_cruus = (ConnectedSubmodelElementCollection) nameplate.getSubmodelElements().get("Marking_CRUUS"); + ConnectedFile fileSE = retrieveFileSEFromCollection(marking_cruus); + + String fileEndpointBefore = fileSE.getValue(); + checkFile(fileEndpointBefore); + + CloseableHttpResponse response = uploadDummyFileToSubmodelElement(UPLOAD_ENDPOINT, getFileFromResources("BaSyx.png"), ContentType.IMAGE_PNG); + try { + int statusCode = response.getStatusLine().getStatusCode(); + + assertEquals(HttpStatus.CREATED.value(), statusCode); + + String fileEndpointAfter = fileSE.getValue(); + + assertNotEquals(fileEndpointBefore, fileEndpointAfter); + checkFile(fileEndpointAfter); + + } finally { + response.close(); + } + } + + private ConnectedFile retrieveFileSEFromCollection(ConnectedSubmodelElementCollection marking_rcm) throws Exception { + Collection values = marking_rcm.getValue(); + + Iterator iter = values.iterator(); + while (iter.hasNext()) { + ISubmodelElement element = iter.next(); + if (element instanceof ConnectedFile) { + return (ConnectedFile) element; + } + } + throw new RuntimeException("No File SubmodelElement found in " + marking_rcm.getIdShort()); + } + + protected static void buildEndpoints(BaSyxContextConfiguration contextConfig) { + rootEndpoint = VABPathTools.stripSlashes(contextConfig.getUrl()); + + aasEndpoint = VABPathTools.concatenatePaths(rootEndpoint, AASAggregatorProvider.PREFIX, aasId.getEncodedURN(), "aas"); + smEndpoint = VABPathTools.concatenatePaths(aasEndpoint, "submodels", smIdShort, "submodel"); + + String encodedAasAId = VABPathTools.encodePathElement(aasAId.getId()); + aasAEndpoint = VABPathTools.concatenatePaths(rootEndpoint, AASAggregatorProvider.PREFIX, encodedAasAId, "aas"); + smAEndpoint = VABPathTools.concatenatePaths(aasAEndpoint, "submodels", smAIdShort, "submodel"); + + String encodedAasBId = VABPathTools.encodePathElement(aasBId.getId()); + aasBEndpoint = VABPathTools.concatenatePaths(rootEndpoint, AASAggregatorProvider.PREFIX, encodedAasBId, "aas"); + smBEndpoint = VABPathTools.concatenatePaths(aasBEndpoint, "submodels", smBIdShort, "submodel"); + + logger.info("AAS URL for servlet test: " + aasEndpoint); + } + private void checkElementCollectionFiles(Collection elements) { for (ISubmodelElement element : elements) { if (element instanceof IFile) { @@ -226,4 +289,23 @@ private void checkFile(String absolutePath) { private ConnectedAssetAdministrationShell getConnectedAssetAdministrationShell() throws Exception { return manager.retrieveAAS(aasId); } + + private File getFileFromResources(String filename) throws IOException, URISyntaxException { + URL resource = getClass().getClassLoader().getResource(filename); + if (resource == null) + throw new IllegalArgumentException("File not found!"); + + return new File(resource.toURI()); + + } + + private CloseableHttpResponse uploadDummyFileToSubmodelElement(String endpoint, File file, ContentType contentType) throws IOException { + CloseableHttpClient client = HttpClients.createDefault(); + + HttpEntity fileEntity = MultipartEntityBuilder.create().addBinaryBody("file", file, contentType, file.getName()).build(); + HttpPost postRequest = new HttpPost(endpoint); + postRequest.setEntity(fileEntity); + + return client.execute(postRequest); + } } 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 cd8e3473..477e8403 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,14 +26,12 @@ import java.io.IOException; import java.net.URISyntaxException; -import java.net.URLEncoder; import java.nio.file.Paths; import javax.servlet.ServletException; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.FileUtils; -import org.eclipse.basyx.aas.aggregator.restapi.AASAggregatorProvider; import org.eclipse.basyx.aas.factory.aasx.AASXToMetamodelConverter; import org.eclipse.basyx.components.aas.AASServerComponent; import org.eclipse.basyx.components.aas.configuration.AASServerBackend; @@ -41,8 +39,6 @@ import org.eclipse.basyx.components.configuration.BaSyxContextConfiguration; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; /** @@ -52,7 +48,6 @@ * */ public class TestAASXAASServer extends AASXSuite { - private static Logger logger = LoggerFactory.getLogger(TestAASXAASServer.class); private static AASServerComponent component; @BeforeClass @@ -69,16 +64,7 @@ public static void setUpClass() throws ParserConfigurationException, SAXExceptio component = new AASServerComponent(contextConfig, aasConfig); component.startComponent(); - rootEndpoint = contextConfig.getUrl() + "/"; - aasEndpoint = rootEndpoint + "/" + AASAggregatorProvider.PREFIX + "/" + aasId.getEncodedURN() + "/aas"; - 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); + buildEndpoints(contextConfig); } @AfterClass diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXMongoDBAASServer.java b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXMongoDBAASServer.java new file mode 100644 index 00000000..588e788c --- /dev/null +++ b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/java/org/eclipse/basyx/regression/AASServer/TestAASXMongoDBAASServer.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * 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; + +import java.nio.file.Paths; + +import org.apache.commons.io.FileUtils; +import org.eclipse.basyx.aas.factory.aasx.AASXToMetamodelConverter; +import org.eclipse.basyx.components.aas.AASServerComponent; +import org.eclipse.basyx.components.aas.configuration.AASServerBackend; +import org.eclipse.basyx.components.aas.configuration.BaSyxAASServerConfiguration; +import org.eclipse.basyx.components.aas.mongodb.MongoDBAASAggregator; +import org.eclipse.basyx.components.configuration.BaSyxContextConfiguration; +import org.eclipse.basyx.components.configuration.BaSyxMongoDBConfiguration; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Test accessing to AAS using basys aas SDK + * + * @author mateusmolina + * + */ +public class TestAASXMongoDBAASServer extends AASXSuite { + private static AASServerComponent component; + private static BaSyxMongoDBConfiguration basyxMongoDBConfig = buildBaSyxMongoDBConfiguration(); + + @BeforeClass + public static void setUpClass() throws Exception { + BaSyxContextConfiguration contextConfig = new BaSyxContextConfiguration(); + contextConfig.loadFromResource(BaSyxContextConfiguration.DEFAULT_CONFIG_PATH); + BaSyxAASServerConfiguration aasConfig = new BaSyxAASServerConfiguration(AASServerBackend.MONGODB, "[\"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); + + resetMongoDBTestData(); + + component = new AASServerComponent(contextConfig, aasConfig, basyxMongoDBConfig); + component.startComponent(); + + buildEndpoints(contextConfig); + } + + @AfterClass + public static void tearDownClass() { + component.stopComponent(); + } + + // TODO Investigation needed on why it's failing for TestAASXMongoDBAASServer + @Override + public void testCollidingFiles() throws Exception { + } + + @SuppressWarnings("deprecation") + private static void resetMongoDBTestData() { + new MongoDBAASAggregator(basyxMongoDBConfig).reset(); + } + + private static BaSyxMongoDBConfiguration buildBaSyxMongoDBConfiguration() { + BaSyxMongoDBConfiguration mongoDBConfig = new BaSyxMongoDBConfiguration(); + mongoDBConfig.setAASCollection("TestAASXMongoDBAASServer_AAS"); + mongoDBConfig.setSubmodelCollection("TestAASXMongoDBAASServer_SM"); + return mongoDBConfig; + } +} diff --git a/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/BaSyx.png b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/BaSyx.png new file mode 100644 index 00000000..819b6818 Binary files /dev/null and b/basyx.components/basyx.components.docker/basyx.components.AASServer/src/test/resources/BaSyx.png differ