diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4d5bb4d7fc..ba2ac1ccd4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ jsonSchemaValidator = "1.5.2" junit-jupiter = "5.11.2" junit = "4.13.2" logback = "1.5.11" -milo = "0.6.14" +milo = "0.6.15" mockito = "5.14.2" modbus = "1.2.2" mqtt-sn-codec = "838f51d691" diff --git a/hivemq-edge/src/main/java/com/hivemq/api/model/mappings/southbound/SouthboundMappingModel.java b/hivemq-edge/src/main/java/com/hivemq/api/model/mappings/southbound/SouthboundMappingModel.java index d310ea527d..253c691ab3 100644 --- a/hivemq-edge/src/main/java/com/hivemq/api/model/mappings/southbound/SouthboundMappingModel.java +++ b/hivemq-edge/src/main/java/com/hivemq/api/model/mappings/southbound/SouthboundMappingModel.java @@ -61,10 +61,13 @@ public SouthboundMappingModel( return fieldMapping; } - public @NotNull SouthboundMapping toToEdgeMapping() { + public @NotNull SouthboundMapping toToEdgeMapping(final @NotNull String schema) { return new SouthboundMapping(this.tagName, this.topicFilter, - this.fieldMapping != null ? FieldMapping.fromModel(this.fieldMapping) : null); + this.fieldMapping != null ? + FieldMapping.fromModel(this.fieldMapping) : + FieldMapping.DEFAULT_FIELD_MAPPING, + schema); } public static SouthboundMappingModel from(final @NotNull SouthboundMapping southboundMapping) { diff --git a/hivemq-edge/src/main/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImpl.java b/hivemq-edge/src/main/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImpl.java index bdf314b071..b492434107 100644 --- a/hivemq-edge/src/main/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImpl.java +++ b/hivemq-edge/src/main/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImpl.java @@ -64,6 +64,8 @@ import com.hivemq.persistence.domain.DomainTagUpdateResult; import com.hivemq.persistence.mappings.NorthboundMapping; import com.hivemq.persistence.mappings.SouthboundMapping; +import com.hivemq.persistence.topicfilter.TopicFilter; +import com.hivemq.persistence.topicfilter.TopicFilterPersistence; import com.hivemq.protocols.InternalProtocolAdapterWritingService; import com.hivemq.protocols.ProtocolAdapterConfig; import com.hivemq.protocols.ProtocolAdapterConfigConverter; @@ -86,6 +88,7 @@ import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -106,6 +109,7 @@ public class ProtocolAdaptersResourceImpl extends AbstractApi implements Protoco private final @NotNull ProtocolAdapterManager protocolAdapterManager; private final @NotNull InternalProtocolAdapterWritingService protocolAdapterWritingService; private final @NotNull ProtocolAdapterConfigConverter configConverter; + private final @NotNull TopicFilterPersistence topicFilterPersistence; private final @NotNull ObjectMapper objectMapper; private final @NotNull VersionProvider versionProvider; private final @NotNull CustomConfigSchemaGenerator customConfigSchemaGenerator = new CustomConfigSchemaGenerator(); @@ -118,7 +122,8 @@ public ProtocolAdaptersResourceImpl( final @NotNull InternalProtocolAdapterWritingService protocolAdapterWritingService, final @NotNull ObjectMapper objectMapper, final @NotNull VersionProvider versionProvider, - final @NotNull ProtocolAdapterConfigConverter configConverter) { + final @NotNull ProtocolAdapterConfigConverter configConverter, + final @NotNull TopicFilterPersistence topicFilterPersistence) { this.remoteService = remoteService; this.configurationService = configurationService; this.protocolAdapterManager = protocolAdapterManager; @@ -126,6 +131,7 @@ public ProtocolAdaptersResourceImpl( this.versionProvider = versionProvider; this.protocolAdapterWritingService = protocolAdapterWritingService; this.configConverter = configConverter; + this.topicFilterPersistence = topicFilterPersistence; } @Override @@ -645,9 +651,10 @@ protected void validateAdapterSchema( .map(NorthboundMappingModel::to) .collect(Collectors.toList()); + final List southboundMappings = adapter.getSouthboundMappingModels() .stream() - .map(SouthboundMappingModel::toToEdgeMapping) + .map(this::parseAndEnrichWithSchema) .collect(Collectors.toList()); protocolAdapterManager.addAdapter(new ProtocolAdapterConfig(adapterId, @@ -672,6 +679,7 @@ protected void validateAdapterSchema( return Response.ok().build(); } + @Override public Response getNorthboundMappingsForAdapter(final @NotNull String adapterId) { return protocolAdapterManager.getAdapterById(adapterId) @@ -758,7 +766,7 @@ public Response updateSouthboundMappingsForAdapter( final Set requiredTags = new HashSet<>(); final List converted = southboundMappingListModel.getItems().stream().map(mapping -> { requiredTags.add(mapping.getTagName()); - return mapping.toToEdgeMapping(); + return parseAndEnrichWithSchema(mapping); }).collect(Collectors.toList()); adapter.getTags().forEach(tag -> requiredTags.remove(tag.getName())); @@ -778,4 +786,24 @@ public Response updateSouthboundMappingsForAdapter( } }).orElseGet(() -> ApiErrorUtils.notFound("Adapter not found")); } + + + private @NotNull SouthboundMapping parseAndEnrichWithSchema(final @NotNull SouthboundMappingModel model) { + final TopicFilter topicFilter = topicFilterPersistence.getTopicFilter(model.getTopicFilter()); + if (topicFilter == null) { + throw new IllegalStateException("Southbound mapping contained a topic filter '" + + model.getTopicFilter() + + "', which is unknown to Edge. Southbound mapping can not be created."); + } + + final DataUrl schemaAsDataUrl = topicFilter.getSchema(); + if (schemaAsDataUrl == null) { + throw new IllegalStateException("Southbound mapping contained a topic filter '" + + model.getTopicFilter() + + "', which has no schema attached. Southbound mapping can not be created."); + } + + final String schema = new String(Base64.getDecoder().decode(schemaAsDataUrl.getData())); + return model.toToEdgeMapping(schema); + } } diff --git a/hivemq-edge/src/main/java/com/hivemq/configuration/entity/adapter/SouthboundMappingEntity.java b/hivemq-edge/src/main/java/com/hivemq/configuration/entity/adapter/SouthboundMappingEntity.java index 1e6a03fa89..70dd865124 100644 --- a/hivemq-edge/src/main/java/com/hivemq/configuration/entity/adapter/SouthboundMappingEntity.java +++ b/hivemq-edge/src/main/java/com/hivemq/configuration/entity/adapter/SouthboundMappingEntity.java @@ -17,9 +17,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.hivemq.configuration.entity.adapter.fieldmapping.FieldMappingEntity; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import com.hivemq.persistence.mappings.SouthboundMapping; +import org.jetbrains.annotations.NotNull; import javax.xml.bind.ValidationEvent; import javax.xml.bind.annotation.XmlElement; @@ -35,23 +34,29 @@ public class SouthboundMappingEntity { @XmlElement(name = "tagName", required = true) private final @NotNull String tagName; - @XmlElement(name = "fieldMapping") - private final @Nullable FieldMappingEntity fieldMapping; + @XmlElement(name = "fieldMapping", required = true) + private final @NotNull FieldMappingEntity fieldMapping; + + @XmlElement(name = "fromNorthSchema", required = true) + private final @NotNull String fromNorthSchema; // no-arg constructor for JaxB public SouthboundMappingEntity() { topicFilter = ""; tagName = ""; fieldMapping = null; + fromNorthSchema = ""; } public SouthboundMappingEntity( final @NotNull String tagName, final @NotNull String topicFilter, - final @Nullable FieldMappingEntity fieldMapping) { + final @NotNull FieldMappingEntity fieldMapping, + final @NotNull String fromNorthSchema) { this.tagName = tagName; this.topicFilter = topicFilter; this.fieldMapping = fieldMapping; + this.fromNorthSchema = fromNorthSchema; } public @NotNull String getTagName() { @@ -69,22 +74,24 @@ public void validate(final @NotNull List validationEvents) { if (tagName == null || tagName.isEmpty()) { validationEvents.add(new ValidationEventImpl(ValidationEvent.FATAL_ERROR, "tagName is missing", null)); } + if (fromNorthSchema == null || fromNorthSchema.isEmpty()) { + validationEvents.add(new ValidationEventImpl(ValidationEvent.FATAL_ERROR, "fromNorthSchema is missing", null)); + } } public @NotNull SouthboundMapping to(final @NotNull ObjectMapper mapper) { - return new SouthboundMapping( - this.getTagName(), + return new SouthboundMapping(this.getTagName(), this.getTopicFilter(), - this.fieldMapping != null ? this.fieldMapping.to(mapper) : null); + this.fieldMapping != null ? this.fieldMapping.to(mapper) : null, + this.fromNorthSchema); } public static @NotNull SouthboundMappingEntity from(final @NotNull SouthboundMapping southboundMapping) { - return new SouthboundMappingEntity( - southboundMapping.getTagName(), + return new SouthboundMappingEntity(southboundMapping.getTagName(), southboundMapping.getTopicFilter(), - FieldMappingEntity.from(southboundMapping.getFieldMapping()) - ); + FieldMappingEntity.from(southboundMapping.getFieldMapping()), + southboundMapping.getSchema()); } } diff --git a/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/SouthboundMapping.java b/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/SouthboundMapping.java index 7c31d0990f..262a9db817 100644 --- a/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/SouthboundMapping.java +++ b/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/SouthboundMapping.java @@ -21,19 +21,22 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class SouthboundMapping implements WritingContext { +public class SouthboundMapping implements InternalWritingContext { private final @NotNull String topicFilter; private final @NotNull String tagName; - private final @Nullable FieldMapping fieldMapping; + private final @NotNull FieldMapping fieldMapping; + private final @NotNull String schema; public SouthboundMapping( final @NotNull String tagName, final @NotNull String topicFilter, - final @Nullable FieldMapping fieldMapping) { + final @NotNull FieldMapping fieldMapping, + final @NotNull String schema) { this.tagName = tagName; this.topicFilter = topicFilter; this.fieldMapping = fieldMapping; + this.schema = schema; } public @NotNull String getTopicFilter() { @@ -44,16 +47,13 @@ public SouthboundMapping( return tagName; } - public @Nullable FieldMapping getFieldMapping() { + public @NotNull FieldMapping getFieldMapping() { return fieldMapping; } - public static @NotNull SouthboundMapping from( - final @NotNull InternalWritingContext writingContext) { - return new SouthboundMapping( - writingContext.getTagName(), - writingContext.getTopicFilter(), - writingContext.getFieldMapping()); + @Override + public @NotNull String getSchema() { + return schema; } @Override diff --git a/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/fieldmapping/FieldMapping.java b/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/fieldmapping/FieldMapping.java index 8bdbd0ddaa..3c38da7950 100644 --- a/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/fieldmapping/FieldMapping.java +++ b/hivemq-edge/src/main/java/com/hivemq/persistence/mappings/fieldmapping/FieldMapping.java @@ -23,6 +23,9 @@ public class FieldMapping { + public static @NotNull FieldMapping DEFAULT_FIELD_MAPPING = + new FieldMapping(List.of(new Instruction("value", "value"))); + private final @NotNull List instructions; public FieldMapping(final @NotNull List instructions) { diff --git a/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContext.java b/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContext.java index 52a006edb7..7741ce09ab 100644 --- a/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContext.java +++ b/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContext.java @@ -26,4 +26,6 @@ public interface InternalWritingContext extends WritingContext { @NotNull FieldMapping getFieldMapping(); + @NotNull + String getSchema(); } diff --git a/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContextImpl.java b/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContextImpl.java index 934a4ef34e..cf13bf70f3 100644 --- a/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContextImpl.java +++ b/hivemq-edge/src/main/java/com/hivemq/protocols/InternalWritingContextImpl.java @@ -33,6 +33,11 @@ public FieldMapping getFieldMapping() { return southboundMapping.getFieldMapping(); } + @Override + public @NotNull String getSchema() { + return southboundMapping.getSchema(); + } + @Override public @NotNull String getTagName() { return southboundMapping.getTagName(); diff --git a/hivemq-edge/src/test/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImplTest.java b/hivemq-edge/src/test/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImplTest.java index 82833696f8..ad4d243115 100644 --- a/hivemq-edge/src/test/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImplTest.java +++ b/hivemq-edge/src/test/java/com/hivemq/api/resources/impl/ProtocolAdaptersResourceImplTest.java @@ -21,14 +21,15 @@ import com.hivemq.configuration.service.ConfigurationService; import com.hivemq.edge.HiveMQEdgeRemoteService; import com.hivemq.edge.VersionProvider; -import org.jetbrains.annotations.NotNull; import com.hivemq.persistence.domain.DomainTag; import com.hivemq.persistence.domain.DomainTagAddResult; import com.hivemq.persistence.domain.DomainTagDeleteResult; import com.hivemq.persistence.domain.DomainTagUpdateResult; +import com.hivemq.persistence.topicfilter.TopicFilterPersistence; import com.hivemq.protocols.InternalProtocolAdapterWritingService; import com.hivemq.protocols.ProtocolAdapterConfigConverter; import com.hivemq.protocols.ProtocolAdapterManager; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import javax.ws.rs.core.Response; @@ -55,6 +56,8 @@ class ProtocolAdaptersResourceImplTest { private final @NotNull ObjectMapper objectMapper = new ObjectMapper(); private final @NotNull VersionProvider versionProvider = mock(); private final @NotNull ProtocolAdapterConfigConverter configConverter = mock(); + private final @NotNull TopicFilterPersistence topicFilterPersistence = mock(); + private final ProtocolAdaptersResourceImpl protocolAdaptersResource = new ProtocolAdaptersResourceImpl(remoteService, @@ -63,7 +66,8 @@ class ProtocolAdaptersResourceImplTest { protocolAdapterWritingService, objectMapper, versionProvider, - configConverter); + configConverter, + topicFilterPersistence); @Test void getDomainTagsForAdapter() { @@ -71,7 +75,10 @@ void getDomainTagsForAdapter() { final ArrayList domainTags = new ArrayList<>(); for (int i = 0; i < 10; i++) { - domainTags.add(new DomainTag("tag"+i, "1", "description", objectMapper.valueToTree(Map.of("address", "addressy")))); + domainTags.add(new DomainTag("tag" + i, + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy")))); } when(protocolAdapterManager.getTagsForAdapter("adapter")).thenReturn(Optional.of(domainTags)); @@ -95,7 +102,10 @@ void addAdapterDomainTag_whenAddingSucceeds_thenReturn200() { when(protocolAdapterManager.addDomainTag(eq("adapter"), any())).thenReturn(DomainTagAddResult.success()); final Response response = protocolAdaptersResource.addAdapterDomainTag("adapter", - DomainTagModel.fromDomainTag(new DomainTag("tag", "1", "description", objectMapper.valueToTree(Map.of("address", "addressy"))))); + DomainTagModel.fromDomainTag(new DomainTag("tag", + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy"))))); assertEquals(200, response.getStatus()); } @@ -103,10 +113,15 @@ void addAdapterDomainTag_whenAddingSucceeds_thenReturn200() { @Test void addAdapterDomainTag_whenAlreadyExists_thenReturn403() { when(protocolAdapterWritingService.writingEnabled()).thenReturn(false); - when(protocolAdapterManager.addDomainTag(eq("adapter"), any())).thenReturn(DomainTagAddResult.failed(DomainTagAddResult.DomainTagPutStatus.ALREADY_EXISTS, "it exists")); + when(protocolAdapterManager.addDomainTag(eq("adapter"), any())).thenReturn(DomainTagAddResult.failed( + DomainTagAddResult.DomainTagPutStatus.ALREADY_EXISTS, + "it exists")); final Response response = protocolAdaptersResource.addAdapterDomainTag("adapter", - DomainTagModel.fromDomainTag(new DomainTag("tag", "1", "description", objectMapper.valueToTree(Map.of("address", "addressy"))))); + DomainTagModel.fromDomainTag(new DomainTag("tag", + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy"))))); assertEquals(403, response.getStatus()); } @@ -114,9 +129,11 @@ void addAdapterDomainTag_whenAlreadyExists_thenReturn403() { @Test void deleteDomainTag_whenTagExists_thenReturn200() { when(protocolAdapterWritingService.writingEnabled()).thenReturn(false); - when(protocolAdapterManager.deleteDomainTag(eq("adapter"), eq("tag"))).thenReturn(DomainTagDeleteResult.success()); + when(protocolAdapterManager.deleteDomainTag(eq("adapter"), + eq("tag"))).thenReturn(DomainTagDeleteResult.success()); - final Response response = protocolAdaptersResource.deleteDomainTag("adapter", URLEncoder.encode("tag", StandardCharsets.UTF_8)); + final Response response = + protocolAdaptersResource.deleteDomainTag("adapter", URLEncoder.encode("tag", StandardCharsets.UTF_8)); assertEquals(200, response.getStatus()); @@ -125,9 +142,11 @@ void deleteDomainTag_whenTagExists_thenReturn200() { @Test void deleteDomainTag_whenTagDoesNotExists_thenReturn403() { when(protocolAdapterWritingService.writingEnabled()).thenReturn(false); - when(protocolAdapterManager.deleteDomainTag("adapter", "tag")).thenReturn(DomainTagDeleteResult.failed(DomainTagDeleteResult.DomainTagDeleteStatus.NOT_FOUND)); + when(protocolAdapterManager.deleteDomainTag("adapter", "tag")).thenReturn(DomainTagDeleteResult.failed( + DomainTagDeleteResult.DomainTagDeleteStatus.NOT_FOUND)); - final Response response = protocolAdaptersResource.deleteDomainTag("adapter", URLEncoder.encode("tag", StandardCharsets.UTF_8)); + final Response response = + protocolAdaptersResource.deleteDomainTag("adapter", URLEncoder.encode("tag", StandardCharsets.UTF_8)); assertEquals(404, response.getStatus()); @@ -140,7 +159,10 @@ void updateDomainTag_whenTagExists_thenReturn200() { final Response response = protocolAdaptersResource.updateDomainTag("adapter", Base64.getEncoder().encodeToString("tag".getBytes(StandardCharsets.UTF_8)), - DomainTagModel.fromDomainTag(new DomainTag("tag", "1", "description", objectMapper.valueToTree(Map.of("address", "addressy"))))); + DomainTagModel.fromDomainTag(new DomainTag("tag", + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy"))))); assertEquals(200, response.getStatus()); } @@ -153,7 +175,10 @@ void updateDomainTag_whenTagDoesNotExists_thenReturn403() { final Response response = protocolAdaptersResource.updateDomainTag("adapter", Base64.getEncoder().encodeToString("tag".getBytes(StandardCharsets.UTF_8)), - DomainTagModel.fromDomainTag(new DomainTag("tag", "1", "description", objectMapper.valueToTree(Map.of("address", "addressy"))))); + DomainTagModel.fromDomainTag(new DomainTag("tag", + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy"))))); assertEquals(403, response.getStatus()); } @@ -163,7 +188,10 @@ void getDomainTags() { when(protocolAdapterWritingService.writingEnabled()).thenReturn(false); final ArrayList domainTags = new ArrayList<>(); for (int i = 0; i < 10; i++) { - domainTags.add(new DomainTag("tag"+i, "1", "description", objectMapper.valueToTree(Map.of("address", "addressy")))); + domainTags.add(new DomainTag("tag" + i, + "1", + "description", + objectMapper.valueToTree(Map.of("address", "addressy")))); } when(protocolAdapterManager.getDomainTags()).thenReturn(domainTags); diff --git a/modules/hivemq-edge-module-http/src/test/resources/http-config-defaults.xml b/modules/hivemq-edge-module-http/src/test/resources/http-config-defaults.xml index 45ee3874c7..541edc8661 100644 --- a/modules/hivemq-edge-module-http/src/test/resources/http-config-defaults.xml +++ b/modules/hivemq-edge-module-http/src/test/resources/http-config-defaults.xml @@ -27,6 +27,7 @@ tag2 my/# + {} diff --git a/modules/hivemq-edge-module-http/src/test/resources/http-config-with-headers.xml b/modules/hivemq-edge-module-http/src/test/resources/http-config-with-headers.xml index a04a1fed66..0fb01091a2 100644 --- a/modules/hivemq-edge-module-http/src/test/resources/http-config-with-headers.xml +++ b/modules/hivemq-edge-module-http/src/test/resources/http-config-with-headers.xml @@ -35,10 +35,12 @@ tag3 my/# + {} tag4 my/# + {} diff --git a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-full-config.xml b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-full-config.xml index eada4ae966..034cf6ea4d 100644 --- a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-full-config.xml +++ b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-full-config.xml @@ -70,10 +70,12 @@ ns=1;i=1004 test/blubb/# + {} ns=2;i=1004 test/blubbb/# + {} diff --git a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config-missing-tag.xml b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config-missing-tag.xml index 67af1532f0..c8f2c5689c 100644 --- a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config-missing-tag.xml +++ b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config-missing-tag.xml @@ -35,6 +35,7 @@ ns=1;i=1004 test/blubb/# + {} diff --git a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config.xml b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config.xml index 6b199ea566..b318dff47e 100644 --- a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config.xml +++ b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-minimal-config.xml @@ -35,6 +35,7 @@ ns=1;i=1004 test/blubb/# + {} diff --git a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-missing-uri.xml b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-missing-uri.xml index 741fb97f79..a236c615f9 100644 --- a/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-missing-uri.xml +++ b/modules/hivemq-edge-module-opcua/src/test/resources/opcua-adapter-missing-uri.xml @@ -34,6 +34,7 @@ ns=1;i=1004 test/blubb/# + {}