From 8674a1ec419383d93fd55a4553bda161e6e36e4e Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish <62088117+mdanish98@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:03:27 +0200 Subject: [PATCH] Implements custom mongodb mapping converter (#81) Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../pom.xml | 14 +- .../TestMongoDBAasRepository.java | 92 ++++++++++++-- .../src/test/resources/DummyAas.json | 6 + basyx.common/basyx.mongocore/pom.xml | 4 + ...stomIdentifiableMappingMongoConverter.java | 103 +++++++++++++++ .../pom.xml | 16 +++ ...stMongoDBConceptDescriptionRepository.java | 76 +++++++++-- .../src/test/resources/DummyCD.json | 4 + .../pom.xml | 10 ++ .../TestMongoDBSubmodelRepository.java | 120 +++++++++++++++--- .../src/test/resources/DummySubmodel.json | 12 ++ .../core/SubmodelRepositorySuite.java | 2 +- 12 files changed, 418 insertions(+), 41 deletions(-) create mode 100644 basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/DummyAas.json create mode 100644 basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/CustomIdentifiableMappingMongoConverter.java create mode 100644 basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/DummyCD.json create mode 100644 basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/DummySubmodel.json diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/pom.xml b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/pom.xml index 078d11f08..8090b5d81 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/pom.xml +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/pom.xml @@ -1,5 +1,6 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 @@ -46,5 +47,16 @@ org.springframework.boot spring-boot-starter-data-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.http + test + + + + commons-io + commons-io + \ No newline at end of file diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBAasRepository.java b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBAasRepository.java index 46d7fc9bb..6e5ee34c8 100644 --- a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBAasRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (C) 2021 the Eclipse BaSyx Authors + * 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 @@ -27,30 +27,54 @@ import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; +import org.apache.commons.io.IOUtils; +import org.bson.Document; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultAssetAdministrationShell; import org.eclipse.digitaltwin.basyx.aasservice.backend.InMemoryAasServiceFactory; +import org.eclipse.digitaltwin.basyx.common.mongocore.CustomIdentifiableMappingMongoConverter; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; +import org.eclipse.digitaltwin.basyx.http.SerializationExtension; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; /** + * Tests the {@link MongoDBAasRepository} * * @author schnicke, danish, kammognie * */ public class TestMongoDBAasRepository extends AasRepositorySuite { - private final String COLLECTION = "aasTestCollection"; - + private static final String CONFIGURED_AAS_REPO_NAME = "configured-aas-repo-name"; - + private final String COLLECTION = "aasTestCollection"; + + private MongoTemplate template; + @Override protected AasRepositoryFactory getAasRepositoryFactory() { - MongoTemplate template = createMongoTemplate(); + template = createMongoTemplate(); MongoDBUtilities.clearCollection(template, COLLECTION); @@ -60,22 +84,21 @@ protected AasRepositoryFactory getAasRepositoryFactory() { @Test public void aasIsPersisted() { AasRepositoryFactory repoFactory = getAasRepositoryFactory(); - AssetAdministrationShell expectedShell = createDummyShellOnRepo(repoFactory.create()); + AssetAdministrationShell expectedShell = createDummyShellOnRepo(repoFactory.create(), "dummy"); AssetAdministrationShell retrievedShell = getAasFromNewBackendInstance(repoFactory, expectedShell.getId()); assertEquals(expectedShell, retrievedShell); - } @Test public void updatedAasIsPersisted() { AasRepositoryFactory repoFactory = getAasRepositoryFactory(); AasRepository mongoDBAasRepository = repoFactory.create(); - AssetAdministrationShell expectedShell = createDummyShellOnRepo(mongoDBAasRepository); + AssetAdministrationShell expectedShell = createDummyShellOnRepo(mongoDBAasRepository, "dummy"); addSubmodelReferenceToAas(expectedShell); mongoDBAasRepository.updateAas(expectedShell.getId(), expectedShell); AssetAdministrationShell retrievedShell = getAasFromNewBackendInstance(repoFactory, expectedShell.getId()); - + assertEquals(expectedShell, retrievedShell); } @@ -85,6 +108,26 @@ public void getConfiguredMongoDBAasRepositoryName() { assertEquals(CONFIGURED_AAS_REPO_NAME, repo.getName()); } + + @Test + public void retrieveRawAasJson() throws FileNotFoundException, IOException { + AssetAdministrationShell dummyAas = createDummyShellOnRepo(getAasRepositoryFactory().create(), "dummyAAS"); + + String expectedAASJson = getAasJSONString(); + + template.save(dummyAas, COLLECTION); + + Document aasDocument = template.findOne(new Query().addCriteria(Criteria.where("id").is("dummyAAS")), + Document.class, COLLECTION); + + assertSameJSONContent(expectedAASJson, aasDocument.toJson()); + } + + private void assertSameJSONContent(String expectedAASJson, String json) throws JsonMappingException, JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + + assertEquals(mapper.readTree(expectedAASJson), mapper.readTree(json)); + } private void addSubmodelReferenceToAas(AssetAdministrationShell expectedShell) { expectedShell.setSubmodels(Arrays.asList(AasRepositorySuite.createDummyReference("dummySubmodel"))); @@ -96,8 +139,8 @@ private AssetAdministrationShell getAasFromNewBackendInstance(AasRepositoryFacto return retrievedShell; } - private AssetAdministrationShell createDummyShellOnRepo(AasRepository aasRepository) { - AssetAdministrationShell expectedShell = new DefaultAssetAdministrationShell.Builder().id("dummy") + private AssetAdministrationShell createDummyShellOnRepo(AasRepository aasRepository, String id) { + AssetAdministrationShell expectedShell = new DefaultAssetAdministrationShell.Builder().id(id).idShort("dummyAASIdShort") .build(); aasRepository.createAas(expectedShell); @@ -105,11 +148,32 @@ private AssetAdministrationShell createDummyShellOnRepo(AasRepository aasReposit } private MongoTemplate createMongoTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; + List extensions = Arrays.asList(new Aas4JHTTPSerializationExtension()); + + ObjectMapper mapper = new BaSyxHTTPConfiguration().jackson2ObjectMapperBuilder(extensions).build(); - MongoClient client = MongoClients.create(connectionURL); + MongoDatabaseFactory databaseFactory = createDatabaseFactory(); + + return new MongoTemplate(databaseFactory, new CustomIdentifiableMappingMongoConverter(databaseFactory, new MongoMappingContext(), mapper)); + } + + private MongoDatabaseFactory createDatabaseFactory() { + String connectionString = createConnectionString(); + + MongoClient client = MongoClients.create(connectionString); + + return new SimpleMongoClientDatabaseFactory(client, "test"); + } + + private String createConnectionString() { + return String.format("mongodb://%s:%s@%s:%s", "mongoAdmin", "mongoPassword", "127.0.0.1", "27017"); + } + + private String getAasJSONString() throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource("DummyAas.json"); + InputStream in = classPathResource.getInputStream(); - return new MongoTemplate(client, "BaSyxTestDb"); + return IOUtils.toString(in, StandardCharsets.UTF_8.name()); } } diff --git a/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/DummyAas.json b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/DummyAas.json new file mode 100644 index 000000000..9c2407b93 --- /dev/null +++ b/basyx.aasrepository/basyx.aasrepository-backend-mongodb/src/test/resources/DummyAas.json @@ -0,0 +1,6 @@ +{ + "modelType": "AssetAdministrationShell", + "assetInformation": null, + "id": "dummyAAS", + "idShort": "dummyAASIdShort" +} diff --git a/basyx.common/basyx.mongocore/pom.xml b/basyx.common/basyx.mongocore/pom.xml index 4920f5580..812e783b5 100644 --- a/basyx.common/basyx.mongocore/pom.xml +++ b/basyx.common/basyx.mongocore/pom.xml @@ -20,5 +20,9 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.eclipse.digitaltwin.aas4j + dataformat-json + \ No newline at end of file diff --git a/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/CustomIdentifiableMappingMongoConverter.java b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/CustomIdentifiableMappingMongoConverter.java new file mode 100644 index 000000000..a4d0fc25d --- /dev/null +++ b/basyx.common/basyx.mongocore/src/main/java/org/eclipse/digitaltwin/basyx/common/mongocore/CustomIdentifiableMappingMongoConverter.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (C) 2023 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.common.mongocore; + +import java.io.IOException; + +import org.bson.Document; +import org.bson.conversions.Bson; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Custom abstract mongodb mapping converter for applying mixins to the + * metamodels for conformance to the specification. + * + * @author danish + * + */ +@Component +@ConditionalOnProperty(prefix = "basyx", name = "backend", havingValue = "MongoDB") +public class CustomIdentifiableMappingMongoConverter extends MappingMongoConverter { + + private static final String MONGODB_ID_KEY = "_id"; + + private ObjectMapper mapper; + + public CustomIdentifiableMappingMongoConverter(MongoDatabaseFactory databaseFactory, MongoMappingContext mappingContext, + ObjectMapper mapper) { + super(new DefaultDbRefResolver(databaseFactory), mappingContext); + this.mapper = mapper; + } + + @Override + public S read(Class clazz, Bson source) { + Document doc = (Document) source; + + doc.remove(MONGODB_ID_KEY); + + String metamodelJson = doc.toJson(); + + return deserializeMetamodel(clazz, metamodelJson); + } + + @Override + public void write(Object source, Bson target) { + Document document = (Document) target; + + String id = ((Identifiable) source).getId(); + + String json = serializeMetamodel(source); + + document.put(MONGODB_ID_KEY, id); + document.putAll(Document.parse(json)); + } + + private String serializeMetamodel(Object source) { + try { + return mapper.writeValueAsString(source); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private S deserializeMetamodel(Class clazz, String string) { + try { + return mapper.readValue(string, clazz); + } catch (IOException e) { + throw new RuntimeException(string, e); + } + } + +} diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml index ad78fb7a6..b71d4abaf 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/pom.xml @@ -36,5 +36,21 @@ org.springframework.boot spring-boot-starter-data-mongodb + + + org.eclipse.digitaltwin.basyx + basyx.conceptdescriptionrepository-http + test + + + org.eclipse.digitaltwin.basyx + basyx.mongodbcore + test + + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBConceptDescriptionRepository.java index 2f1a32229..66113427b 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBConceptDescriptionRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/TestMongoDBConceptDescriptionRepository.java @@ -28,20 +28,39 @@ import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; +import java.util.List; +import org.apache.commons.io.IOUtils; +import org.bson.Document; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultLangStringTextType; +import org.eclipse.digitaltwin.basyx.common.mongocore.CustomIdentifiableMappingMongoConverter; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.MongoDBConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.MongoDBConceptDescriptionRepositoryFactory; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.core.ConceptDescriptionRepositorySuite; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; +import org.eclipse.digitaltwin.basyx.http.SerializationExtension; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -57,7 +76,7 @@ public class TestMongoDBConceptDescriptionRepository extends ConceptDescriptionR @Override protected ConceptDescriptionRepository getConceptDescriptionRepository() { - MongoTemplate template = createTemplate(); + MongoTemplate template = createMongoTemplate(); clearDatabase(template); @@ -67,7 +86,7 @@ protected ConceptDescriptionRepository getConceptDescriptionRepository() { @Override protected ConceptDescriptionRepository getConceptDescriptionRepository( Collection conceptDescriptions) { - MongoTemplate template = createTemplate(); + MongoTemplate template = createMongoTemplate(); clearDatabase(template); @@ -80,7 +99,7 @@ protected ConceptDescriptionRepository getConceptDescriptionRepository( @Test public void testConfiguredMongoDBConceptDescriptionRepositoryName() { - MongoTemplate template = createTemplate(); + MongoTemplate template = createMongoTemplate(); clearDatabase(template); @@ -113,6 +132,24 @@ public void updatedConceptDescriptionIsPersisted() { assertEquals(expectedConceptDescription, retrievedConceptDescription); } + @Test + public void retrieveRawCDJson() throws FileNotFoundException, IOException { + createDummyConceptDescriptionOnRepo(getConceptDescriptionRepository()); + + String expectedCDJson = getDummyCDJSONString(); + + Document cdDocument = createMongoTemplate().findOne(new Query().addCriteria(Criteria.where("id").is("dummy")), + Document.class, COLLECTION); + + assertSameJSONContent(expectedCDJson, cdDocument.toJson()); + } + + private void assertSameJSONContent(String expectedCDJson, String json) throws JsonMappingException, JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + + assertEquals(mapper.readTree(expectedCDJson), mapper.readTree(json)); + } + private void addDescriptionToConceptDescription(ConceptDescription expectedConceptDescription) { expectedConceptDescription.setDescription(Arrays.asList(new DefaultLangStringTextType.Builder().text("description").language("en").build())); } @@ -131,16 +168,37 @@ private ConceptDescription createDummyConceptDescriptionOnRepo(ConceptDescriptio return expectedConceptDescription; } - private MongoTemplate createTemplate() { - String connectionURL = "mongodb://mongoAdmin:mongoPassword@localhost:27017/"; + private void clearDatabase(MongoTemplate template) { + template.remove(new Query(), COLLECTION); + } + + private MongoTemplate createMongoTemplate() { + List extensions = Arrays.asList(new Aas4JHTTPSerializationExtension()); + + ObjectMapper mapper = new BaSyxHTTPConfiguration().jackson2ObjectMapperBuilder(extensions).build(); - MongoClient client = MongoClients.create(connectionURL); + MongoDatabaseFactory databaseFactory = createDatabaseFactory(); - return new MongoTemplate(client, "BaSyxTestDb"); + return new MongoTemplate(databaseFactory, new CustomIdentifiableMappingMongoConverter(databaseFactory, new MongoMappingContext(), mapper)); } - private void clearDatabase(MongoTemplate template) { - template.remove(new Query(), COLLECTION); + private MongoDatabaseFactory createDatabaseFactory() { + String connectionString = createConnectionString(); + + MongoClient client = MongoClients.create(connectionString); + + return new SimpleMongoClientDatabaseFactory(client, "BaSyxTestDb"); + } + + private String createConnectionString() { + return String.format("mongodb://%s:%s@%s:%s", "mongoAdmin", "mongoPassword", "127.0.0.1", "27017"); + } + + private String getDummyCDJSONString() throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource("DummyCD.json"); + InputStream in = classPathResource.getInputStream(); + + return IOUtils.toString(in, StandardCharsets.UTF_8.name()); } } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/DummyCD.json b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/DummyCD.json new file mode 100644 index 000000000..a99731547 --- /dev/null +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-backend-mongodb/src/test/resources/DummyCD.json @@ -0,0 +1,4 @@ +{ + "modelType": "ConceptDescription", + "id": "dummy" +} diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml index afc17f693..9bf049f1d 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/pom.xml @@ -37,5 +37,15 @@ org.eclipse.digitaltwin.basyx basyx.submodelservice-core + + org.eclipse.digitaltwin.basyx + basyx.submodelrepository-http + test + + + commons-io + commons-io + test + \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java index 27f0d29be..c93102e57 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/TestMongoDBSubmodelRepository.java @@ -26,48 +26,77 @@ import static org.junit.Assert.assertEquals; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.io.IOUtils; +import org.bson.Document; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.InvokableOperation; +import org.eclipse.digitaltwin.basyx.common.mongocore.CustomIdentifiableMappingMongoConverter; import org.eclipse.digitaltwin.basyx.common.mongocore.MongoDBUtilities; import org.eclipse.digitaltwin.basyx.core.exceptions.FeatureNotSupportedException; +import org.eclipse.digitaltwin.basyx.http.Aas4JHTTPSerializationExtension; +import org.eclipse.digitaltwin.basyx.http.BaSyxHTTPConfiguration; +import org.eclipse.digitaltwin.basyx.http.SerializationExtension; import org.eclipse.digitaltwin.basyx.submodelrepository.core.SubmodelRepositorySuite; +import org.eclipse.digitaltwin.basyx.submodelrepository.http.SubmodelRepositoryHTTPSerializationExtension; import org.eclipse.digitaltwin.basyx.submodelservice.InMemorySubmodelServiceFactory; import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.MongoTemplate; -import org.junit.Test; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; public class TestMongoDBSubmodelRepository extends SubmodelRepositorySuite { private final String COLLECTION = "submodelTestCollection"; - private final String CONNECTION_URL = "mongodb://mongoAdmin:mongoPassword@localhost:27017"; - private final MongoClient CLIENT = MongoClients.create(CONNECTION_URL); - private final MongoTemplate TEMPLATE = new MongoTemplate(CLIENT, "BaSyxTestDb"); + + private MongoTemplate mongoTemplate; + private final InMemorySubmodelServiceFactory SUBMODEL_SERVICE_FACTORY = new InMemorySubmodelServiceFactory(); private static final String CONFIGURED_SM_REPO_NAME = "configured-sm-repo-name"; @Override protected SubmodelRepository getSubmodelRepository() { - MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); + mongoTemplate = createMongoTemplate(); + + MongoDBUtilities.clearCollection(mongoTemplate, COLLECTION); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY).create(); + return new MongoDBSubmodelRepositoryFactory(mongoTemplate, COLLECTION, SUBMODEL_SERVICE_FACTORY).create(); } @Override protected SubmodelRepository getSubmodelRepository(Collection submodels) { - MongoDBUtilities.clearCollection(TEMPLATE, COLLECTION); + mongoTemplate = createMongoTemplate(); + + MongoDBUtilities.clearCollection(mongoTemplate, COLLECTION); - // TODO: Remove this after MongoDB uses AAS4J serializer - submodels.forEach(this::removeInvokableFromInvokableOperation); + // Remove InvokableOperation from the Submodels + submodels.forEach(this::removeInvokableOperation); - return new MongoDBSubmodelRepositoryFactory(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels).create(); + return new MongoDBSubmodelRepositoryFactory(mongoTemplate, COLLECTION, SUBMODEL_SERVICE_FACTORY, submodels).create(); } @Test public void getConfiguredMongoDBSmRepositoryName() { - SubmodelRepository repo = new MongoDBSubmodelRepository(TEMPLATE, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME); + mongoTemplate = createMongoTemplate(); + + SubmodelRepository repo = new MongoDBSubmodelRepository(mongoTemplate, COLLECTION, SUBMODEL_SERVICE_FACTORY, CONFIGURED_SM_REPO_NAME); assertEquals(CONFIGURED_SM_REPO_NAME, repo.getName()); } @@ -83,12 +112,71 @@ public void invokeOperation() { public void invokeNonOperation() { super.invokeNonOperation(); } + + @Test + public void retrieveRawSMJson() throws FileNotFoundException, IOException { + String dummySMId = "dummySubmodelId"; + + createDummySubmodelOnRepository(dummySMId); + + String expectedSMJson = getSubmodelJSONString(); + + Document smDocument = createMongoTemplate().findOne(new Query().addCriteria(Criteria.where("id").is(dummySMId)), + Document.class, COLLECTION); + + assertSameJSONContent(expectedSMJson, smDocument.toJson()); + } + + private void assertSameJSONContent(String expectedSMJson, String actualSMJson) throws JsonMappingException, JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + + assertEquals(mapper.readTree(expectedSMJson), mapper.readTree(actualSMJson)); + } - private void removeInvokableFromInvokableOperation(Submodel sm) { - sm.getSubmodelElements().stream() - .filter(InvokableOperation.class::isInstance) - .map(InvokableOperation.class::cast) - .forEach(o -> o.setInvokable(null)); + private void createDummySubmodelOnRepository(String dummySMId) { + Submodel dummySubmodel = buildDummySubmodel(dummySMId); + + SubmodelRepository repository = getSubmodelRepository(); + repository.createSubmodel(dummySubmodel); + } + + private void removeInvokableOperation(Submodel sm) { + Iterator iterator = sm.getSubmodelElements().iterator(); + while (iterator.hasNext()) { + SubmodelElement element = iterator.next(); + if (element instanceof InvokableOperation) { + iterator.remove(); + } + } + } + + private MongoTemplate createMongoTemplate() { + List extensions = Arrays.asList(new Aas4JHTTPSerializationExtension(), new SubmodelRepositoryHTTPSerializationExtension()); + + ObjectMapper mapper = new BaSyxHTTPConfiguration().jackson2ObjectMapperBuilder(extensions).build(); + + MongoDatabaseFactory databaseFactory = createDatabaseFactory(); + + return new MongoTemplate(databaseFactory, new CustomIdentifiableMappingMongoConverter(databaseFactory, new MongoMappingContext(), mapper)); + } + + private MongoDatabaseFactory createDatabaseFactory() { + String connectionString = createConnectionString(); + + MongoClient client = MongoClients.create(connectionString); + + return new SimpleMongoClientDatabaseFactory(client, "BaSyxTestDb"); + } + + private String createConnectionString() { + return String.format("mongodb://%s:%s@%s:%s", "mongoAdmin", "mongoPassword", "127.0.0.1", "27017"); + } + + private String getSubmodelJSONString() throws FileNotFoundException, IOException { + ClassPathResource classPathResource = new ClassPathResource("DummySubmodel.json"); + InputStream in = classPathResource.getInputStream(); + + return IOUtils.toString(in, StandardCharsets.UTF_8.name()); } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/DummySubmodel.json b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/DummySubmodel.json new file mode 100644 index 000000000..4da7fffac --- /dev/null +++ b/basyx.submodelrepository/basyx.submodelrepository-backend-mongodb/src/test/resources/DummySubmodel.json @@ -0,0 +1,12 @@ +{ + "modelType": "Submodel", + "id": "dummySubmodelId", + "submodelElements": [ + { + "modelType": "Property", + "value": "testValue", + "valueType": "xs:string", + "idShort": "prop" + } + ] +} \ No newline at end of file diff --git a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java index 37ba5d562..9889c5457 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java +++ b/basyx.submodelrepository/basyx.submodelrepository-core/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/core/SubmodelRepositorySuite.java @@ -398,7 +398,7 @@ private SubmodelElement getExpectedSubmodelElement() { .get(); } - private Submodel buildDummySubmodel(String id) { + protected Submodel buildDummySubmodel(String id) { return new DefaultSubmodel.Builder().id(id) .submodelElements(new DefaultProperty.Builder().idShort("prop") .value("testValue")