diff --git a/basyx.common/basyx.mqttcore/src/main/java/org/eclipse/digitaltwin/basyx/common/mqttcore/serializer/SubmodelElementSerializer.java b/basyx.common/basyx.mqttcore/src/main/java/org/eclipse/digitaltwin/basyx/common/mqttcore/serializer/SubmodelElementSerializer.java index f2395f0f5..771fd9c43 100644 --- a/basyx.common/basyx.mqttcore/src/main/java/org/eclipse/digitaltwin/basyx/common/mqttcore/serializer/SubmodelElementSerializer.java +++ b/basyx.common/basyx.mqttcore/src/main/java/org/eclipse/digitaltwin/basyx/common/mqttcore/serializer/SubmodelElementSerializer.java @@ -25,6 +25,8 @@ package org.eclipse.digitaltwin.basyx.common.mqttcore.serializer; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; @@ -72,6 +74,34 @@ public static String serializeSubmodelElement(SubmodelElement submodelElement) { throw new RuntimeException(e); } } + + /** + * Serializer to create a JSON String for the given submodel elements. + * + * @param submodelElement + * @return serialized list of submodelElements as JSON String + */ + public static String serializeSubmodelElements(List submodelElements) { + try { + List updatedSubmodelElements = new ArrayList<>(); + + for(int i = 0; i < submodelElements.size(); i++) { + SubmodelElement elem = submodelElements.get(i); + SubmodelElement localElement; + if (shouldSendEmptyValueEvent(elem)) { + localElement = getSubmodelElementWithoutValue(elem); + } else { + localElement = elem; + } + + updatedSubmodelElements.add(localElement); + } + + return new JsonSerializer().writeList(updatedSubmodelElements); + } catch (SerializationException | DeserializationException e) { + throw new RuntimeException(e); + } + } /** * Generator to create a copy of a submodelElement without its value. diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/Readme.md b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/Readme.md index 628ebfdf5..fd810ae9c 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/Readme.md +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/Readme.md @@ -9,5 +9,8 @@ This feature provides hierarchical MQTT eventing for a multitude of events: | SubmodelElement Created | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/$idShortPath/created | Created SubmodelElement JSON | | SubmodelElement Updated | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/$idShortPath/updated | Updated SubmodelElement JSON | | SubmodelElement Deleted | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/$idShortPath/deleted | Deleted SubmodelElement JSON | +| SubmodelElements Patched | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/patched | Patched SubmodelElements JSON | +| FileValue Updated | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/$idShortPath/attachment/updated | Updated SubmodelElement JSON | +| FileValue Deleted | sm-repository/$repoId/submodels/$submodelIdBase64URLEncoded/submodelElements/$idShortPath/attachment/deleted | Deleted SubmodelElement JSON | Per default, the SubmodelElement topic payloads include the SubmodelElement's value. If this is not desired, the SubmodelElement can be annotated with a Qualifier of type *emptyValueUpdateEvent* and value *true* diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java index 86d11bc29..9b4e661be 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepository.java @@ -1,5 +1,31 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + package org.eclipse.digitaltwin.basyx.submodelrepository.feature.mqtt; +import java.io.File; import java.io.InputStream; import java.util.List; @@ -126,6 +152,12 @@ public void deleteSubmodelElement(String submodelId, String idShortPath) throws submodelElementDeleted(submodelElement, getName(), submodelId, idShortPath); } + @Override + public void patchSubmodelElements(String submodelId, List submodelElementList) { + decorated.patchSubmodelElements(submodelId, submodelElementList); + submodelElementsPatched(submodelElementList, getName(), submodelId); + } + @Override public String getName() { return decorated.getName(); @@ -141,6 +173,35 @@ public Submodel getSubmodelByIdMetadata(String submodelId) { return decorated.getSubmodelByIdMetadata(submodelId); } + @Override + public OperationVariable[] invokeOperation(String submodelId, String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { + return decorated.invokeOperation(submodelId, idShortPath, input); + } + + @Override + public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { + return decorated.getFileByPathSubmodel(submodelId, idShortPath); + } + + @Override + public void deleteFileValue(String identifier, String idShortPath) { + SubmodelElement submodelElement = decorated.getSubmodelElement(identifier, idShortPath); + decorated.deleteFileValue(identifier, idShortPath); + fileValueDeleted(submodelElement, getName(), identifier, idShortPath); + } + + @Override + public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream){ + decorated.setFileValue(submodelId, idShortPath, fileName, inputStream); + SubmodelElement submodelElement = decorated.getSubmodelElement(submodelId, idShortPath); + fileValueUpdated(submodelElement, getName(), submodelId, idShortPath); + } + + @Override + public InputStream getFileByFilePath(String submodelId, String filePath) { + return decorated.getFileByFilePath(submodelId, filePath); + } + private void submodelCreated(Submodel submodel, String repoId) { sendMqttMessage(topicFactory.createCreateSubmodelTopic(repoId), SubmodelSerializer.serializeSubmodel(submodel)); } @@ -164,6 +225,18 @@ private void submodelElementUpdated(SubmodelElement submodelElement, String repo private void submodelElementDeleted(SubmodelElement submodelElement, String repoId, String submodelId, String submodelElementId) { sendMqttMessage(topicFactory.createDeleteSubmodelElementTopic(repoId, submodelId, submodelElementId), SubmodelElementSerializer.serializeSubmodelElement(submodelElement)); } + + private void submodelElementsPatched(List submodelElements, String repoId, String submodelId) { + sendMqttMessage(topicFactory.createPatchSubmodelElementsTopic(repoId, submodelId), SubmodelElementSerializer.serializeSubmodelElements(submodelElements)); + } + + private void fileValueDeleted(SubmodelElement submodelElement, String repoId, String submodelId, String submodelElementId) { + sendMqttMessage(topicFactory.createDeleteFileValueTopic(repoId, submodelId, submodelElementId), SubmodelElementSerializer.serializeSubmodelElement(submodelElement)); + } + + private void fileValueUpdated(SubmodelElement submodelElement, String repoId, String submodelId, String submodelElementId) { + sendMqttMessage(topicFactory.createUpdateFileValueTopic(repoId, submodelId, submodelElementId), SubmodelElementSerializer.serializeSubmodelElement(submodelElement)); + } /** * Sends MQTT message to connected broker @@ -194,37 +267,4 @@ private MqttMessage createMqttMessage(String payload) { } } - @Override - public OperationVariable[] invokeOperation(String submodelId, String idShortPath, OperationVariable[] input) throws ElementDoesNotExistException { - return decorated.invokeOperation(submodelId, idShortPath, input); - } - - @Override - public java.io.File getFileByPathSubmodel(String submodelId, String idShortPath) { - return decorated.getFileByPathSubmodel(submodelId, idShortPath); - } - - @Override - public void deleteFileValue(String identifier, String idShortPath) { - // TODO: Eventing - decorated.deleteFileValue(identifier, idShortPath); - } - - @Override - public void setFileValue(String submodelId, String idShortPath, String fileName, InputStream inputStream){ - // TODO: Eventing - decorated.setFileValue(submodelId, idShortPath, fileName, inputStream); - } - - @Override - public void patchSubmodelElements(String submodelId, List submodelElementList) { - // TODO: Eventing - decorated.patchSubmodelElements(submodelId, submodelElementList); - } - - @Override - public InputStream getFileByFilePath(String submodelId, String filePath) { - return decorated.getFileByFilePath(submodelId, filePath); - } - } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepositoryTopicFactory.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepositoryTopicFactory.java index ea4d29263..14ab1f0e5 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepositoryTopicFactory.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/MqttSubmodelRepositoryTopicFactory.java @@ -41,7 +41,9 @@ public class MqttSubmodelRepositoryTopicFactory extends AbstractMqttTopicFactory private static final String CREATED = "created"; private static final String UPDATED = "updated"; private static final String DELETED = "deleted"; + private static final String PATCHED = "patched"; private static final String SUBMODELELEMENTS = "submodelElements"; + private static final String ATTACHMENT = "attachment"; /** * @param encoder @@ -104,4 +106,34 @@ public String createUpdateSubmodelElementTopic(String repoId, String submodelId, public String createDeleteSubmodelElementTopic(String repoId, String submodelId, String submodelElementId) { return new StringJoiner("/", "", "").add(SUBMODELREPOSITORY).add(repoId).add(SUBMODELS).add(encodeId(submodelId)).add(SUBMODELELEMENTS).add(submodelElementId).add(DELETED).toString(); } + + /** + * Creates the hierarchical topic for the patch event of submodelElements + * + * @param repoId + * @param submodelId + */ + public String createPatchSubmodelElementsTopic(String repoId, String submodelId) { + return new StringJoiner("/", "", "").add(SUBMODELREPOSITORY).add(repoId).add(SUBMODELS).add(encodeId(submodelId)).add(SUBMODELELEMENTS).add(PATCHED).toString(); + } + + /** + * Creates the hierarchical topic for the delete event of a file of a file element + * + * @param repoId + * + */ + public String createDeleteFileValueTopic(String repoId, String submodelId, String submodelElementId) { + return new StringJoiner("/", "", "").add(SUBMODELREPOSITORY).add(repoId).add(SUBMODELS).add(encodeId(submodelId)).add(SUBMODELELEMENTS).add(submodelElementId).add(ATTACHMENT).add(DELETED).toString(); + } + + /** + * Creates the hierarchical topic for the update event of a file of a file element + * + * @param repoId + * + */ + public String createUpdateFileValueTopic(String repoId, String submodelId, String submodelElementId) { + return new StringJoiner("/", "", "").add(SUBMODELREPOSITORY).add(repoId).add(SUBMODELS).add(encodeId(submodelId)).add(SUBMODELELEMENTS).add(submodelElementId).add(ATTACHMENT).add(UPDATED).toString(); + } } diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java index 1198e9ab1..2afc466d0 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-mqtt/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/mqtt/TestMqttSubmodelObserver.java @@ -28,21 +28,30 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonDeserializer; +import org.eclipse.digitaltwin.aas4j.v3.model.File; import org.eclipse.digitaltwin.aas4j.v3.model.Property; import org.eclipse.digitaltwin.aas4j.v3.model.Qualifier; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultFile; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultQualifier; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; import org.eclipse.digitaltwin.basyx.common.mqttcore.encoding.Base64URLEncoder; import org.eclipse.digitaltwin.basyx.common.mqttcore.serializer.SubmodelElementSerializer; +import org.eclipse.digitaltwin.basyx.core.exceptions.FileHandlingException; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileMetadata; +import org.eclipse.digitaltwin.basyx.core.filerepository.FileRepository; import org.eclipse.digitaltwin.basyx.core.filerepository.InMemoryFileRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelInMemoryBackendProvider; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -58,6 +67,9 @@ import org.junit.BeforeClass; import org.junit.Test; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; + import io.moquette.broker.Server; import io.moquette.broker.config.ClasspathResourceLoader; import io.moquette.broker.config.IConfig; @@ -74,7 +86,13 @@ public class TestMqttSubmodelObserver { private static MqttSubmodelRepositoryTopicFactory topicFactory = new MqttSubmodelRepositoryTopicFactory(new Base64URLEncoder()); private static SubmodelRepository submodelRepository; + + private static JsonDeserializer deserializer = new JsonDeserializer(); + private static final String FILE_SUBMODEL_ELEMENT_NAME = "testFile.txt"; + private static final String FILE_SUBMODEL_ELEMENT_CONTENT = "This is a text file."; + private static String SAVED_FILE_PATH = ""; + @BeforeClass public static void setUpClass() throws MqttException, IOException { mqttBroker = startBroker(); @@ -135,10 +153,10 @@ public void createSubmodelElementEvent() throws DeserializationException { @Test public void updateSubmodelElementEvent() throws DeserializationException { - Submodel submodel = createSubmodelDummy("updateSubmodelForElementEventId"); + Submodel submodel = createSubmodelDummyWithSubmodelElement("updateSubmodelForElementEventId", "updateSubmodelElementEventId"); submodelRepository.createSubmodel(submodel); - SubmodelElement submodelElement = createSubmodelElementDummy("updateSubmodelElementEventId"); - submodelRepository.createSubmodelElement(submodel.getId(), submodelElement); + SubmodelElement submodelElement = submodel.getSubmodelElements().get(0); + SubmodelElementValue value = new PropertyValue("updatedValue"); submodelRepository.setSubmodelElementValue(submodel.getId(), submodelElement.getIdShort(), value); @@ -148,10 +166,11 @@ public void updateSubmodelElementEvent() throws DeserializationException { @Test public void deleteSubmodelElementEvent() throws DeserializationException { - Submodel submodel = createSubmodelDummy("deleteSubmodelForElementEventId"); + Submodel submodel = createSubmodelDummyWithSubmodelElement("deleteSubmodelForElementEventId", "deleteSubmodelElementEventId"); submodelRepository.createSubmodel(submodel); - SubmodelElement submodelElement = createSubmodelElementDummy("deleteSubmodelElementEventId"); - submodelRepository.createSubmodelElement(submodel.getId(), submodelElement); + + SubmodelElement submodelElement = submodel.getSubmodelElements().get(0); + submodelRepository.deleteSubmodelElement(submodel.getId(), submodelElement.getIdShort()); assertEquals(topicFactory.createDeleteSubmodelElementTopic(submodelRepository.getName(), submodel.getId(), submodelElement.getIdShort()), listener.lastTopic); @@ -175,6 +194,50 @@ public void createSubmodelElementWithoutValueEvent() throws DeserializationExcep assertEquals(submodelElement, deserializeSubmodelElementPayload(listener.lastPayload)); } + @Test + public void patchSubmodelElementsEvent() throws DeserializationException, JsonMappingException, JsonProcessingException { + Submodel submodel = createSubmodelDummyWithSubmodelElements("patchSubmodelForElementEventId"); + submodelRepository.createSubmodel(submodel); + + List submodelElements = submodel.getSubmodelElements(); + + for (int i = 0; i < submodelElements.size(); i++) { + SubmodelElement submodelElement = submodelElements.get(i); + submodelElement.setIdShort("patchedSubmodelElementId_" + i); + } + + submodelRepository.patchSubmodelElements(submodel.getId(), submodelElements); + + assertEquals(topicFactory.createPatchSubmodelElementsTopic(submodelRepository.getName(), submodel.getId()), listener.lastTopic); + assertEquals(submodelElements, deserializeSubmodelElementsListPayload(listener.lastPayload)); + } + + @Test + public void setFileValueEvent() throws DeserializationException, IOException { + Submodel submodel = createSubmodelDummyWithFileSubmodelElement("setSubmodelFileValueEventId", "setFileValueSubmodelElementEventId"); + submodelRepository.createSubmodel(submodel); + + File submodelElement = (File) submodel.getSubmodelElements().get(0); + + submodelRepository.setFileValue(submodel.getId(), submodelElement.getIdShort(), FILE_SUBMODEL_ELEMENT_NAME, getInputStreamOfDummyFile(FILE_SUBMODEL_ELEMENT_CONTENT)); + + assertEquals(topicFactory.createUpdateFileValueTopic(submodelRepository.getName(), submodel.getId(), submodelElement.getIdShort()), listener.lastTopic); + assertEquals(submodelElement, deserializeSubmodelElementPayload(listener.lastPayload)); + } + + @Test + public void deleteFileValueEvent() throws DeserializationException, IOException { + Submodel submodel = createSubmodelDummyWithFileSubmodelElement("deleteSubmodelFileValueEventId", "deleteFileValueSubmodelElementEventId"); + submodelRepository.createSubmodel(submodel); + + File submodelElement = (File) submodel.getSubmodelElements().get(0); + + submodelRepository.deleteFileValue(submodel.getId(), submodelElement.getIdShort()); + + assertEquals(topicFactory.createDeleteFileValueTopic(submodelRepository.getName(), submodel.getId(), submodelElement.getIdShort()), listener.lastTopic); + assertEquals(submodelElement, deserializeSubmodelElementPayload(listener.lastPayload)); + } + private List createNoValueQualifierList() { Qualifier emptyValueQualifier = new DefaultQualifier.Builder().type(SubmodelElementSerializer.EMPTYVALUEUPDATE_TYPE).value("true").build(); return Arrays.asList(emptyValueQualifier); @@ -187,17 +250,67 @@ private Submodel deserializeSubmodelPayload(String payload) throws Deserializati private SubmodelElement deserializeSubmodelElementPayload(String payload) throws DeserializationException { return new JsonDeserializer().read(payload, SubmodelElement.class); } + + private List deserializeSubmodelElementsListPayload(String payload) throws DeserializationException, JsonMappingException, JsonProcessingException { + return deserializer.readList(payload, SubmodelElement.class); + } private Submodel createSubmodelDummy(String submodelId) { return new DefaultSubmodel.Builder().id(submodelId).build(); } + + private Submodel createSubmodelDummyWithSubmodelElement(String submodelId, String submodelElementId) { + List submodelElements = new ArrayList<>(); + + submodelElements.add(createSubmodelElementDummy(submodelElementId)); + + return new DefaultSubmodel.Builder().id(submodelId).submodelElements(submodelElements).build(); + } + + private Submodel createSubmodelDummyWithFileSubmodelElement(String submodelId, String submodelElementId) { + List submodelElements = new ArrayList<>(); + + submodelElements.add(createFileSubmodelElement(submodelElementId)); + + return new DefaultSubmodel.Builder().id(submodelId).submodelElements(submodelElements).build(); + } + + private Submodel createSubmodelDummyWithSubmodelElements(String submodelId) { + List submodelElements = createSubmodelElementsListDummy(2); + + return new DefaultSubmodel.Builder().id(submodelId).submodelElements(submodelElements).build(); + } private SubmodelElement createSubmodelElementDummy(String submodelElementId) { + Property defaultProp = new DefaultProperty.Builder().idShort(submodelElementId).value("defaultValue").build(); + return new DefaultProperty.Builder().idShort(submodelElementId).value("defaultValue").build(); } + + public File createFileSubmodelElement(String submodelElementId) { + return new DefaultFile.Builder().idShort(submodelElementId).value(SAVED_FILE_PATH).contentType("text/plain").build(); + } + + private static InputStream getInputStreamOfDummyFile(String fileContent) throws FileNotFoundException, IOException { + return new ByteArrayInputStream(fileContent.getBytes()); + } + + private List createSubmodelElementsListDummy(int count) { + List submodelElements = new ArrayList(); + + for (int i = 0; i < count; i++) { + submodelElements.add(createSubmodelElementDummy("submodelElementId_" + i)); + } + + return submodelElements; + } - private static SubmodelRepository createMqttSubmodelRepository(MqttClient client) { - SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(new InMemoryFileRepository())); + private static SubmodelRepository createMqttSubmodelRepository(MqttClient client) throws FileHandlingException, FileNotFoundException, IOException { + FileRepository fileRepository = new InMemoryFileRepository(); + + SAVED_FILE_PATH = fileRepository.save(new FileMetadata(FILE_SUBMODEL_ELEMENT_NAME, "", getInputStreamOfDummyFile(FILE_SUBMODEL_ELEMENT_CONTENT))); + + SubmodelRepositoryFactory repoFactory = new SimpleSubmodelRepositoryFactory(new SubmodelInMemoryBackendProvider(), new InMemorySubmodelServiceFactory(fileRepository)); return new MqttSubmodelRepositoryFactory(repoFactory, client, new MqttSubmodelRepositoryTopicFactory(new Base64URLEncoder())).create(); }