From 2d51ad6246c5ff82b04f87b1cc597aaffc2f7075 Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Tue, 2 Jan 2024 17:20:24 +0100 Subject: [PATCH 1/7] BREAKING CHANGE: new name cleanup in DeviceFactory * Invalid characters are now replaced by an underscore (_) instead of a hyphen (-). This matches the Java identifier format used in EMF. * By default, all Java identifier characters are now allowed. This means that the name of a model, provider, service or resource can use locale characters (greek letters, accents, ...). * A new boolean mapping option is available: "names.ascii". If true, the only characters allowed in names are ASCII letters and digits, and the underscore. The names are first normalized in NFKD form to remove diacritics, then all unexpected characters are replaced by an underscore. * In both modes, ASCII or Java identifiers: if the name is a reserved Java keyword or starts with digit, then it is prefixed with an underscore. Tests have been added to check the new behaviours. --- .../device/factory/IResourceMapping.java | 4 +- .../factory/dto/DeviceMappingOptionsDTO.java | 3 + .../factory/impl/AbstractResourceMapping.java | 16 ++- .../factory/impl/FactoryParserHandler.java | 17 ++- .../device/factory/impl/NamingUtils.java | 104 ++++++++++++++++-- .../factory/impl/RecordHandlingTest.java | 68 ++++++++++++ 6 files changed, 196 insertions(+), 16 deletions(-) diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/IResourceMapping.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/IResourceMapping.java index 20faf93d7..b2bc92c8b 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/IResourceMapping.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/IResourceMapping.java @@ -64,8 +64,10 @@ IResourceMapping fillInVariables(final Map variables) * Returns a copy of this mapping with a path that meets the sensiNact naming * requirements * + * @param asciiOnly If true, the path must only contain letters, digits and + * underscores * @return This object if the key was valid or a new one with a new path * @throws InvalidResourcePathException Invalid new resource path */ - IResourceMapping ensureValidPath() throws InvalidResourcePathException; + IResourceMapping ensureValidPath(final boolean asciiOnly) throws InvalidResourcePathException; } diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java index 5a3e674bd..acf1ec0cd 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java @@ -38,4 +38,7 @@ public class DeviceMappingOptionsDTO { @JsonProperty("null.action") public NullAction nullAction = NullAction.UPDATE; + + @JsonProperty("names.ascii") + public boolean asciiNames; } diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/AbstractResourceMapping.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/AbstractResourceMapping.java index f887b2789..24d4ee8ba 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/AbstractResourceMapping.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/AbstractResourceMapping.java @@ -100,6 +100,7 @@ public AbstractResourceMapping(final String rcPath) throws InvalidResourcePathEx /** * Returns the name of the service */ + @Override public String getService() { return service; } @@ -107,6 +108,7 @@ public String getService() { /** * Returns the name of the resource */ + @Override public String getResource() { return resource; } @@ -114,6 +116,7 @@ public String getResource() { /** * Returns the name of the metadata, if any */ + @Override public String getMetadata() { return metadata; } @@ -121,6 +124,7 @@ public String getMetadata() { /** * Checks if this mapping targets a metadata */ + @Override public boolean isMetadata() { return metadata != null; } @@ -128,18 +132,24 @@ public boolean isMetadata() { /** * Returns the resource path */ + @Override public String getResourcePath() { return path; } @Override - public IResourceMapping ensureValidPath() throws InvalidResourcePathException { - if (service == null) { + public IResourceMapping ensureValidPath(final boolean asciiOnly) throws InvalidResourcePathException { + if (path == null) { // Not a path return this; } - final String cleaned = NamingUtils.sanitizeName(path, true); + final String cleaned; + if (asciiOnly) { + cleaned = NamingUtils.asciiSanitizeName(path, true); + } else { + cleaned = NamingUtils.sanitizeName(path, true); + } if (cleaned.equals(path)) { // Nothing to do return this; diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java index 46d529b0b..c3e0522a8 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java @@ -268,7 +268,12 @@ private List handleRecord(final DeviceMappingConfigurationDTO config if (rawProvider == null || rawProvider.isBlank()) { throw new ParserException("Empty provider field"); } - final String provider = NamingUtils.sanitizeName(rawProvider, false); + final String provider; + if (configuration.mappingOptions.asciiNames) { + provider = NamingUtils.asciiSanitizeName(rawProvider, false); + } else { + provider = NamingUtils.sanitizeName(rawProvider, false); + } // Extract the model final String model; @@ -276,6 +281,8 @@ private List handleRecord(final DeviceMappingConfigurationDTO config final String rawModel = getFieldString(record, recordState.placeholders.get(KEY_MODEL), options); if (rawModel == null || rawModel.isBlank()) { throw new ParserException("Empty model field for " + provider); + } else if (configuration.mappingOptions.asciiNames) { + model = NamingUtils.asciiSanitizeName(rawModel, false); } else { model = NamingUtils.sanitizeName(rawModel, false); } @@ -437,6 +444,8 @@ private RecordState computeRecordState(final DeviceMappingConfigurationDTO confi final RecordState initialState, final IDeviceMappingRecord record) throws InvalidResourcePathException, ParserException, VariableNotFoundException { + final boolean asciiPaths = configuration.mappingOptions.asciiNames; + final RecordState state = new RecordState(); // Resolve variables @@ -447,12 +456,14 @@ private RecordState computeRecordState(final DeviceMappingConfigurationDTO confi state.rcMappings = new ArrayList<>(initialState.rcMappings.size()); for (final ResourceRecordMapping rcMapping : initialState.rcMappings) { - state.rcMappings.add((ResourceRecordMapping) rcMapping.fillInVariables(state.variables).ensureValidPath()); + state.rcMappings.add( + (ResourceRecordMapping) rcMapping.fillInVariables(state.variables).ensureValidPath(asciiPaths)); } state.rcLiterals = new ArrayList<>(initialState.rcLiterals.size()); for (final ResourceLiteralMapping rcMapping : initialState.rcLiterals) { - state.rcLiterals.add((ResourceLiteralMapping) rcMapping.fillInVariables(state.variables).ensureValidPath()); + state.rcLiterals.add( + (ResourceLiteralMapping) rcMapping.fillInVariables(state.variables).ensureValidPath(asciiPaths)); } return state; } diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/NamingUtils.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/NamingUtils.java index 48d91f221..2e7395116 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/NamingUtils.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/NamingUtils.java @@ -1,5 +1,5 @@ /********************************************************************* -* Copyright (c) 2023 Contributors to the Eclipse Foundation. +* Copyright (c) 2024 Contributors to the Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 @@ -12,24 +12,110 @@ **********************************************************************/ package org.eclipse.sensinact.gateway.southbound.device.factory.impl; +import java.text.Normalizer; +import java.util.Arrays; +import java.util.stream.Collectors; + /** - * + * Handles model, provider, service and resource names from the user */ public class NamingUtils { - public static String sanitizeName(final String name, final boolean isPath) { - if (name == null) { + /** + * List of Java keywords according to its specification + */ + public static final String[] keywords = { "abstract", "continue", "for", "new", "switch", "assert", "default", "if", + "package", "synchronized", "boolean", "do", "goto", "private", "this", "break", "double", "implements", + "protected", "throw", "byte", "else", "import", "public", "throws", "case", "enum", "instanceof", "return", + "transient", "catch", "extends", "int", "short", "try", "char", "final", "interface", "static", "void", + "class", "finally", "long", "strictfp", "volatile", "const", "float", "native", "super", "while" }; + + static { + // Final array can be sorted (in-place sort) + Arrays.sort(keywords); + } + + /** + * Checks if the given name is a Java keyword + * + * @param name Name to test + * @return True if the given name is a Java keyword + */ + public static boolean isJavaKeyword(final String name) { + return Arrays.binarySearch(keywords, name) >= 0; + } + + /** + * Ensures that the given name is only based on ASCII letters, digits and the + * underscore + * + * @param name Input name + * @param isPath Flag to allow slashes (/) in the name + * @return A name that contains only ASCII letters, digits or underscore, or + * null if the input is empty or null + */ + public static String asciiSanitizeName(final String name, final boolean isPath) { + if (name == null || name.isBlank()) { return null; } - final String rejectedPattern; if (isPath) { - // Allow slash in path - rejectedPattern = "[^-A-Za-z0-9/]"; + // Treat each part separately then join everything + return Arrays.stream(name.split("/")).map(p -> asciiSanitizeName(p, false)) + .collect(Collectors.joining("/")); } else { - rejectedPattern = "[^-A-Za-z0-9]"; + // Normalize diacritics + final String normalized = Normalizer.normalize(name.strip(), Normalizer.Form.NFKD).replaceAll("\\p{M}", ""); + final String sanitized; + if (normalized.isEmpty()) { + // All characters were invalid, create a name with as many underscores as input + // characters + sanitized = name.replaceAll(".", "_"); + } else { + // Replace all non acceptable characters with an underscore + sanitized = normalized.replaceAll("[^_A-Za-z0-9]", "_"); + } + + if (sanitized.isEmpty()) { + return "_"; + } else if (!Character.isJavaIdentifierStart(sanitized.charAt(0)) || isJavaKeyword(name)) { + // Make sure we don't start with an invalid character + return "_" + sanitized; + } else { + return sanitized; + } + } + } + + /** + * Ensures the given name is accepted as a Java identifier + * + * @param name Input name + * @param isPath Flag to allow slashes (/) in the name + * @return A name that can be used as Java identifier, or null if the input is + * empty or null + */ + public static String sanitizeName(final String name, final boolean isPath) { + if (name == null || name.isBlank()) { + return null; } - return name.replaceAll(rejectedPattern, "-"); + if (isPath) { + // Treat each part separately then join everything + return Arrays.stream(name.split("/")).map(p -> sanitizeName(p, false)).collect(Collectors.joining("/")); + } else { + // Replace invalid Java identifier letters + final String sanitized = name.strip().chars().mapToObj( + c -> Character.isJavaIdentifierPart(c) || (isPath && c == '/') ? Character.toString((char) c) : "_") + .collect(Collectors.joining()); + if (sanitized.isEmpty()) { + return "_"; + } else if (!Character.isJavaIdentifierStart(sanitized.charAt(0)) || isJavaKeyword(name)) { + // Make sure we don't start with an invalid character + return "_" + sanitized; + } else { + return sanitized; + } + } } } diff --git a/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java b/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java index 35bca7016..d4bb0073b 100644 --- a/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java +++ b/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java @@ -310,4 +310,72 @@ void testVariableService() throws Exception { // Test values assertEquals(42, getResourceValue("provider", "svc", "rc").value); } + + @Test + void testNameSanitize() throws Exception { + final String model = "modèle_test"; + final String providerInput = "test-provider"; + final String provider = "test_provider"; + final String name = "Châlons"; + final int value = 42; + parser.setRecords(Map.of("m", model, "p", providerInput, "n", name, "val", value)); + + // Test w/o a model + final DeviceMappingConfigurationDTO config = prepareConfig(); + config.mapping.put("@model", "m"); + config.mapping.put("@provider", "p"); + config.mapping.put("@name", "n"); + config.mapping.put("état/Val-1", "val"); + config.mapping.put("État/föhn", "val"); + config.mapping.put("1/3.14", "val"); + config.mapping.put("-Greek/Γαλαξιας", "val"); + config.mapping.put("int/finally", "val"); + deviceMapper.handle(config, Map.of(), new byte[0]); + + GenericDto dto = getResourceValue(provider, "admin", "friendlyName"); + // No change in value + assertEquals(model, dto.model); + assertEquals(provider, dto.provider); + assertEquals(name, dto.value); + assertEquals(value, getResourceValue(provider, "état", "Val_1", Integer.class)); + assertEquals(value, getResourceValue(provider, "État", "föhn", Integer.class)); + assertEquals(value, getResourceValue(provider, "_1", "_3_14", Integer.class)); + assertEquals(value, getResourceValue(provider, "_Greek", "Γαλαξιας", Integer.class)); + assertEquals(value, getResourceValue(provider, "_int", "_finally", Integer.class)); + } + + @Test + void testNameAsciiSanitize() throws Exception { + final String modelInput = "modèle_test"; + final String model = "modele_test"; + final String providerInput = "test-provider"; + final String provider = "test_provider"; + final String name = "Châlons"; + final int value = 42; + parser.setRecords(Map.of("m", modelInput, "p", providerInput, "n", name, "val", value)); + + // Test w/o a model + final DeviceMappingConfigurationDTO config = prepareConfig(); + config.mappingOptions.asciiNames = true; + config.mapping.put("@model", "m"); + config.mapping.put("@provider", "p"); + config.mapping.put("@name", "n"); + config.mapping.put("état/Val-1", "val"); + config.mapping.put("État/föhn", "val"); + config.mapping.put("1/3.14", "val"); + config.mapping.put("-Greek/Γαλαξιας", "val"); + config.mapping.put("int/finally", "val"); + deviceMapper.handle(config, Map.of(), new byte[0]); + + GenericDto dto = getResourceValue(provider, "admin", "friendlyName"); + // No change in value + assertEquals(model, dto.model); + assertEquals(provider, dto.provider); + assertEquals(name, dto.value); + assertEquals(value, getResourceValue(provider, "etat", "Val_1", Integer.class)); + assertEquals(value, getResourceValue(provider, "Etat", "fohn", Integer.class)); + assertEquals(value, getResourceValue(provider, "_1", "_3_14", Integer.class)); + assertEquals(value, getResourceValue(provider, "_Greek", "________", Integer.class)); + assertEquals(value, getResourceValue(provider, "_int", "_finally", Integer.class)); + } } From 1351095b0f345b215edce5f0e53a2465559b4278 Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Tue, 2 Jan 2024 17:43:36 +0100 Subject: [PATCH 2/7] DeviceFactory: added a model.raw option If "model.raw" is set to true, then the model string is not sanitized. This allows to give existing URLs as models instead of simple names. --- .../factory/dto/DeviceMappingOptionsDTO.java | 5 +++- .../factory/impl/FactoryParserHandler.java | 2 ++ .../factory/impl/RecordHandlingTest.java | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java index acf1ec0cd..1f268573d 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/dto/DeviceMappingOptionsDTO.java @@ -40,5 +40,8 @@ public class DeviceMappingOptionsDTO { public NullAction nullAction = NullAction.UPDATE; @JsonProperty("names.ascii") - public boolean asciiNames; + public boolean asciiNames = false; + + @JsonProperty("model.raw") + public boolean useRawModel = false; } diff --git a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java index c3e0522a8..0a0586ce6 100644 --- a/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java +++ b/southbound/device-factory/device-factory-core/src/main/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/FactoryParserHandler.java @@ -281,6 +281,8 @@ private List handleRecord(final DeviceMappingConfigurationDTO config final String rawModel = getFieldString(record, recordState.placeholders.get(KEY_MODEL), options); if (rawModel == null || rawModel.isBlank()) { throw new ParserException("Empty model field for " + provider); + } else if (configuration.mappingOptions.useRawModel) { + model = rawModel; } else if (configuration.mappingOptions.asciiNames) { model = NamingUtils.asciiSanitizeName(rawModel, false); } else { diff --git a/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java b/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java index d4bb0073b..e1fd7f547 100644 --- a/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java +++ b/southbound/device-factory/device-factory-core/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/RecordHandlingTest.java @@ -378,4 +378,29 @@ void testNameAsciiSanitize() throws Exception { assertEquals(value, getResourceValue(provider, "_Greek", "________", Integer.class)); assertEquals(value, getResourceValue(provider, "_int", "_finally", Integer.class)); } + + @Test + void testRawModel() throws Exception { + final String rawModel = "http://user:test@some.emf.model:8080/model.xml?format=emf#"; + final String provider = "provider"; + parser.setRecords(Map.of("m", rawModel, "p", provider)); + + // Test with an escaped model + final DeviceMappingConfigurationDTO config = prepareConfig(); + config.mappingOptions.useRawModel = false; + config.mapping.put("@model", "m"); + config.mapping.put("@provider", "p"); + config.mapping.put("@name", "p"); + deviceMapper.handle(config, Map.of(), new byte[0]); + + GenericDto dto = getResourceValue(provider, "admin", "friendlyName"); + assertEquals("http___user_test_some_emf_model_8080_model_xml_format_emf_", dto.model); + + // Test with a raw model + bulks.clear(); + config.mappingOptions.useRawModel = true; + deviceMapper.handle(config, Map.of(), new byte[0]); + dto = getResourceValue(provider, "admin", "friendlyName"); + assertEquals(rawModel, dto.model); + } } From 4a95211965a0699a72a3182c5345d0b18a4f506e Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Tue, 2 Jan 2024 18:09:39 +0100 Subject: [PATCH 3/7] Updated DeviceFactory parsers tests --- .../device/factory/impl/CSVParserTest.java | 28 +++++++++---------- .../device/factory/impl/JSONParserTest.java | 16 +++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/southbound/device-factory/parser-csv/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/CSVParserTest.java b/southbound/device-factory/parser-csv/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/CSVParserTest.java index 491c04a80..56751bcc8 100644 --- a/southbound/device-factory/parser-csv/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/CSVParserTest.java +++ b/southbound/device-factory/parser-csv/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/CSVParserTest.java @@ -141,8 +141,8 @@ T getResourceValue(final String provider, final String service, final String @Test void testNoHeader() throws Exception { // Excepted providers - final String provider1 = "no-header-provider1"; - final String provider2 = "no-header-provider2"; + final String provider1 = "no_header_provider1"; + final String provider2 = "no_header_provider2"; // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/csv-no-header-mapping.json"); @@ -188,8 +188,8 @@ void testNoHeader() throws Exception { @Test void testWithHeader() throws Exception { // Excepted providers - final String provider1 = "header-provider1"; - final String provider2 = "header-provider2"; + final String provider1 = "header_provider1"; + final String provider2 = "header_provider2"; // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/csv-header-mapping.json"); @@ -235,8 +235,8 @@ void testWithHeader() throws Exception { @Test void testTyped() throws Exception { // Excepted providers - final String provider1 = "typed-provider1"; - final String provider2 = "typed-provider2"; + final String provider1 = "typed_provider1"; + final String provider2 = "typed_provider2"; // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/csv-header-typed-mapping.json"); @@ -282,9 +282,9 @@ void testTyped() throws Exception { @Test void testLiteral() throws Exception { // Excepted providers - final String provider1 = "literal-provider1"; - final String provider2 = "literal-provider2"; - final String literalProvider = "literal-provider"; + final String provider1 = "literal_provider1"; + final String provider2 = "literal_provider2"; + final String literalProvider = "literal_provider"; // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/csv-literal-typed-mapping.json"); @@ -323,7 +323,7 @@ void testLiteral() throws Exception { @Test void testIsolatedValue() throws Exception { // Excepted provider - final String provider = "isolated-value-" + String.valueOf(new Random().nextInt()); + final String provider = "isolated_value_" + String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/isolated-value-mapping.json"); @@ -345,7 +345,7 @@ void testIsolatedValue() throws Exception { @Test void testIsolatedValueTyped() throws Exception { // Excepted provider - final String provider = "isolated-value-" + String.valueOf(new Random().nextInt()); + final String provider = "isolated_value_" + String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/isolated-value-mapping-typed.json"); @@ -366,9 +366,9 @@ void testIsolatedValueTyped() throws Exception { @Test void testVariables() throws Exception { // Excepted resource - final String provider = "provider-vars-" + String.valueOf(new Random().nextInt()); - final String service = "svc-vars-" + String.valueOf(new Random().nextInt()); - final String resource = "rc-vars-" + String.valueOf(new Random().nextInt()); + final String provider = "provider_vars_" + String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); + final String service = "svc_vars_" + String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); + final String resource = "rc_vars_" + String.valueOf(new Random().nextInt(Integer.MAX_VALUE)); // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("csv/csv-no-header-vars-mapping.json"); diff --git a/southbound/device-factory/parser-json/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/JSONParserTest.java b/southbound/device-factory/parser-json/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/JSONParserTest.java index 04b1046da..efdddc615 100644 --- a/southbound/device-factory/parser-json/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/JSONParserTest.java +++ b/southbound/device-factory/parser-json/src/test/java/org/eclipse/sensinact/gateway/southbound/device/factory/impl/JSONParserTest.java @@ -220,7 +220,7 @@ void testSubArray() throws Exception { // Excepted providers final String provider1 = "JsonSubArray1"; final String provider2 = "JsonSubArray2"; - final String ignoredProvider = "JsonSubArray-Ignore"; + final String ignoredProvider = "JsonSubArray_Ignore"; // Read the configuration DeviceMappingConfigurationDTO config = readConfiguration("json/sub-array-mapping.json"); @@ -286,7 +286,7 @@ void testSingleObject() throws Exception { // Ensure value and type assertEquals(15, getResourceValue(provider, "data", "value", Integer.class)); - assertEquals(1691587953000L, getResourceValue(provider, "data", "long-value", Long.class)); + assertEquals(1691587953000L, getResourceValue(provider, "data", "long_value", Long.class)); // Ensure timestamp Instant timestamp = Instant.from(LocalDateTime.of(2022, 12, 7, 15, 17, 0).atOffset(ZoneOffset.UTC)); @@ -328,9 +328,9 @@ void testDatetime() throws Exception { void testDeepObjects() throws Exception { // Expected providers - final String provider1 = "1452"; + final String provider1 = "_1452"; final String type1 = "GOOSE"; - final String provider2 = "1851"; + final String provider2 = "_1851"; final String type2 = "DUCK"; // Read the configuration @@ -343,11 +343,11 @@ void testDeepObjects() throws Exception { deviceMapper.handle(config, Map.of(), fileContent); // Check first provider - assertEquals(0, getResourceValue(provider1, "data", type1 + "-value", Integer.class)); + assertEquals(0, getResourceValue(provider1, "data", type1 + "_value", Integer.class)); // Ensure timestamp Instant timestamp = Instant.from(LocalDateTime.of(2021, 10, 26, 15, 28, 0).atOffset(ZoneOffset.UTC)); - assertEquals(timestamp, getResourceValue(provider1, "data", type1 + "-value").timestamp); + assertEquals(timestamp, getResourceValue(provider1, "data", type1 + "_value").timestamp); // Ensure location update (and its timestamp) GenericDto location = getResourceValue(provider1, "admin", "location"); @@ -360,11 +360,11 @@ void testDeepObjects() throws Exception { assertTrue(Double.isNaN(geoPoint.coordinates.elevation)); // Check 2nd provider - assertEquals(7, getResourceValue(provider2, "data", type2 + "-value", Integer.class)); + assertEquals(7, getResourceValue(provider2, "data", type2 + "_value", Integer.class)); // Ensure timestamp timestamp = Instant.from(LocalDateTime.of(2021, 10, 26, 15, 27, 0).atOffset(ZoneOffset.UTC)); - assertEquals(timestamp, getResourceValue(provider2, "data", type2 + "-value").timestamp); + assertEquals(timestamp, getResourceValue(provider2, "data", type2 + "_value").timestamp); // Ensure location update (and its timestamp) location = getResourceValue(provider2, "admin", "location"); From 23a7009e2937ee40bdbf5d713a5168736c503726 Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Tue, 2 Jan 2024 18:16:50 +0100 Subject: [PATCH 4/7] Updated HTTP Device Factory tests --- .../http/http-device-factory/integration-test.bndrun | 1 - .../factory/integration/HttpDeviceFactoryAuthTest.java | 2 +- .../integration/HttpDeviceFactoryParallelQueries.java | 4 ++-- .../factory/integration/HttpDeviceFactorySSLTest.java | 10 +++++----- .../factory/integration/HttpDeviceFactoryTest.java | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/southbound/http/http-device-factory/integration-test.bndrun b/southbound/http/http-device-factory/integration-test.bndrun index 156dad834..0696be070 100644 --- a/southbound/http/http-device-factory/integration-test.bndrun +++ b/southbound/http/http-device-factory/integration-test.bndrun @@ -59,7 +59,6 @@ org.gecko.emf.osgi.api;version='[5.0.0,5.0.1)',\ org.gecko.emf.osgi.component;version='[5.0.0,5.0.1)',\ org.opentest4j;version='[1.2.0,1.2.1)',\ - org.osgi.service.cm;version='[1.6.1,1.6.2)',\ org.osgi.service.component;version='[1.5.0,1.5.1)',\ org.osgi.service.typedevent;version='[1.0.0,1.0.1)',\ org.osgi.test.common;version='[1.2.1,1.2.2)',\ diff --git a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryAuthTest.java b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryAuthTest.java index 9911f9030..193c85810 100644 --- a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryAuthTest.java +++ b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryAuthTest.java @@ -130,7 +130,7 @@ byte[] readFile(final String filename) throws IOException { @Test void testCombined() throws Exception { // Excepted providers - final String providerBase = "auth-station"; + final String providerBase = "auth_station"; final String provider1 = providerBase + "1"; final String provider2 = providerBase + "2"; diff --git a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryParallelQueries.java b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryParallelQueries.java index 3aa732d97..6cc737092 100644 --- a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryParallelQueries.java +++ b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryParallelQueries.java @@ -131,8 +131,8 @@ byte[] readFile(final String filename) throws IOException { @Test void testParallelQuery() throws Exception { // Setup server - final String provider1 = "http-parallel-query1"; - final String provider2 = "http-parallel-query2"; + final String provider1 = "http_parallel_query1"; + final String provider2 = "http_parallel_query2"; final int value1 = 42; final int value2 = 21; diff --git a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactorySSLTest.java b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactorySSLTest.java index 4fcc2892d..7e7363df8 100644 --- a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactorySSLTest.java +++ b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactorySSLTest.java @@ -228,7 +228,7 @@ void testRejectUntrusted() throws Exception { startServer(false); // Excepted providers - final String provider1 = "ssl-reject-provider1"; + final String provider1 = "ssl_reject_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); @@ -263,7 +263,7 @@ void testAllowUntrusted() throws Exception { startServer(false); // Excepted providers - final String provider1 = "ssl-allowed-provider1"; + final String provider1 = "ssl_allowed_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); @@ -302,7 +302,7 @@ void testTrusted() throws Exception { startServer(false); // Excepted providers - final String provider1 = "ssl-trusted-provider1"; + final String provider1 = "ssl_trusted_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); @@ -344,7 +344,7 @@ void testClientAuthFailure() throws Exception { startServer(true); // Excepted providers - final String provider1 = "ssl-client-auth-fail-provider1"; + final String provider1 = "ssl_client_auth_fail_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); @@ -382,7 +382,7 @@ void testClientAuthValid() throws Exception { startServer(true); // Excepted providers - final String provider1 = "ssl-client-auth-valid-provider1"; + final String provider1 = "ssl_client_auth_valid_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); diff --git a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryTest.java b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryTest.java index d3fb2637d..1788be65f 100644 --- a/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryTest.java +++ b/southbound/http/http-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/http/factory/integration/HttpDeviceFactoryTest.java @@ -168,8 +168,8 @@ void assertNotifications(final Object providerValue1, final Object providerValue @Test void testSimpleTask() throws Exception { // Excepted providers - final String provider1 = "typed-provider1"; - final String provider2 = "typed-provider2"; + final String provider1 = "typed_provider1"; + final String provider2 = "typed_provider2"; // Register listener setupProvidersHandling(provider1, provider2); @@ -225,8 +225,8 @@ void testSimpleTask() throws Exception { @Test void testPeriodicTask() throws Exception { // Excepted providers - final String provider1 = "dynamic-provider1"; - final String provider2 = "dynamic-provider2"; + final String provider1 = "dynamic_provider1"; + final String provider2 = "dynamic_provider2"; // Register listener setupProvidersHandling(provider1, provider2); From 9e0bd4573e463a80042cef55cd81f30ad182b7f7 Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Tue, 2 Jan 2024 18:18:22 +0100 Subject: [PATCH 5/7] Updated MQTT Device Factory tests --- southbound/mqtt/mqtt-device-factory/integration-test.bndrun | 1 - .../mqtt/factory/integration/MqttDeviceFactoryTest.java | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/southbound/mqtt/mqtt-device-factory/integration-test.bndrun b/southbound/mqtt/mqtt-device-factory/integration-test.bndrun index b079b486f..b3b95ba40 100644 --- a/southbound/mqtt/mqtt-device-factory/integration-test.bndrun +++ b/southbound/mqtt/mqtt-device-factory/integration-test.bndrun @@ -65,7 +65,6 @@ org.gecko.emf.osgi.api;version='[5.0.0,5.0.1)',\ org.gecko.emf.osgi.component;version='[5.0.0,5.0.1)',\ org.opentest4j;version='[1.2.0,1.2.1)',\ - org.osgi.service.cm;version='[1.6.1,1.6.2)',\ org.osgi.service.component;version='[1.5.0,1.5.1)',\ org.osgi.service.typedevent;version='[1.0.0,1.0.1)',\ org.osgi.test.common;version='[1.2.1,1.2.2)',\ diff --git a/southbound/mqtt/mqtt-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/mqtt/factory/integration/MqttDeviceFactoryTest.java b/southbound/mqtt/mqtt-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/mqtt/factory/integration/MqttDeviceFactoryTest.java index 38f678e3c..7415432a4 100644 --- a/southbound/mqtt/mqtt-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/mqtt/factory/integration/MqttDeviceFactoryTest.java +++ b/southbound/mqtt/mqtt-device-factory/src/test/java/org/eclipse/sensinact/gateway/southbound/mqtt/factory/integration/MqttDeviceFactoryTest.java @@ -127,8 +127,8 @@ byte[] readFile(final String filename) throws IOException { @Test void testWorking() throws Exception { // Excepted providers - final String provider1 = "typed-provider1"; - final String provider2 = "typed-provider2"; + final String provider1 = "typed_provider1"; + final String provider2 = "typed_provider2"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); @@ -192,7 +192,7 @@ void testWorking() throws Exception { @Test void testHandlerFilter() throws Exception { // Excepted providers - final String provider1 = "handler-provider1"; + final String provider1 = "handler_provider1"; // Register listener session.addListener(List.of(provider1 + "/*"), (t, e) -> queue.offer(e), null, null, null); From 9f22d4c83d77e1e08de7c769e521d69a5282dd7e Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Wed, 3 Jan 2024 13:25:41 +0100 Subject: [PATCH 6/7] Use _ instead of - in Device Factory examples --- docs/source/getting-started.md | 18 ++++----- docs/source/southbound/device-factory/core.md | 39 +++++++++++++++++-- .../southbound/device-factory/tuto-parser.md | 32 +++++++-------- .../southbound/http/http-device-factory.md | 4 +- 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/docs/source/getting-started.md b/docs/source/getting-started.md index aa4357e7e..969c186bd 100644 --- a/docs/source/getting-started.md +++ b/docs/source/getting-started.md @@ -332,8 +332,8 @@ Here is the configuration of the HTTP device factory, where we declare a periodi "utc_offset_seconds": 0 } ``` -* maps the response JSON as follows: - * the result is associated to a fixed provider name: `grenoble-weather`. `@Provider` is a special placeholder to name the provider that holds the data we received, this is the only mandatory field in a mapping definition. +* will map the response JSON as follows: + * the result is associated to a fixed provider name: `grenoble_weather`. `@Provider` is a special placeholder to name the provider that holds the data we received, this is the only mandatory field in a mapping definition. Note that model, provider, service and resource names are normalized before being transmitted to the Eclipse sensiNact model. See [here](./southbound/device-factory/core.md#names-handling) for more information. * the provider friendly (human readable) name will be: `Grenoble Weather`. It is stored using the `@Name` placeholder. * the exact location of the weather data point is given by its latitude, longitude and elevation. * the weather was measured/computed at the time given in the `time` entry of the `current_weather` object. The timestamp is given as an [ISO 8601 date time](https://en.wikipedia.org/wiki/ISO_8601) (in UTC timezone), that can be given to the device factory using `@datetime` placeholder. Other time-related placeholders are: `@Date`, `@Time` and `@Timestamp` (for Unix timestamps). @@ -353,7 +353,7 @@ Here is the configuration of the HTTP device factory, where we declare a periodi "parser": "json", "mapping": { "@provider": { - "literal": "grenoble-weather" + "literal": "grenoble_weather" }, "@name": { "literal": "Grenoble Weather" @@ -366,15 +366,15 @@ Here is the configuration of the HTTP device factory, where we declare a periodi "path": "current_weather/temperature", "type": "float" }, - "weather/wind-speed": { + "weather/wind_speed": { "path": "current_weather/windspeed", "type": "float" }, - "weather/wind-direction": { + "weather/wind_direction": { "path": "current_weather/winddirection", "type": "int" }, - "weather/weather-code": "current_weather/weathercode" + "weather/weather_code": "current_weather/weathercode" } } } @@ -391,7 +391,6 @@ You can (re)start the configured sensiNact instance, using `./start.sh` :::{note} On Windows, that would be: ```powershell - & java -D"sensinact.config.dir=configuration" -jar launch\launcher.jar ``` ::: @@ -399,13 +398,12 @@ On Windows, that would be: The current temperature at Grenoble should now be accessible using: ```bash -curl http://localhost:8082/sensinact/providers/grenoble-weather/services/weather/resources/temperature/GET +curl http://localhost:8082/sensinact/providers/grenoble_weather/services/weather/resources/temperature/GET ``` And the result will have the following format: ```json - { "response": { "name": "temperature", @@ -415,6 +413,6 @@ And the result will have the following format: }, "statusCode": 200, "type": "GET_RESPONSE", - "uri": "/grenoble-weather/weather/temperature" + "uri": "/grenoble_weather/weather/temperature" } ``` diff --git a/docs/source/southbound/device-factory/core.md b/docs/source/southbound/device-factory/core.md index 7e4088a40..201f35044 100644 --- a/docs/source/southbound/device-factory/core.md +++ b/docs/source/southbound/device-factory/core.md @@ -79,6 +79,35 @@ The key of the mapping entry can be in the following formats: * `$xxx`: definition of a variable that can be reused in or as a mapping value. The variable can be used in other mapping key or in record paths using the `${xxx}` syntax. * `"svc/rc"`: the parser value will be stored in a the resource `rc` of the service `svc` +#### Names handling + +Eclipse sensiNact is based on EMF to manage its model, which requires all the names it handles to be valid Java identifiers. +As a result, the Device Factory Core normalizes all model, provider, service and resource names before forwarding data to sensiNact. + +By default, the normalization process allows all Java identifier characters, including non-Latin characters and diacritics. +Invalid characters (space, ...) are replaced with underscores. + +The mapping options include a `names.ascii` flag that can be set to true to restrict names to digits, underscores and Latin letters without diacritics. +The device factory first normalizes the input in NKFD form to remove diacritics, then replaces all rejected characters with underscores. +If the entire name is rejected, it will be replaced by a string of underscores that has the same length as the original name. + +In both cases, if the normalized name doesn't begin with a valid Java identifier start character or is a reserved Java keyword, then it is prefixed with an underscore. + +Here are some examples of transformation: + +| Input | Default Normalization | ASCII Normalization | +|-------------------|-----------------------|---------------------| +| `état` | `état` | `etat` | +| `État` | `État` | `Etat` | +| `int/3.14` | `_int/_3_14` | `_int/_3_14` | +| `-Greek/Γαλαξιας` | `_Greek/Γαλαξιας` | `_Greek/________` | + + +:::{warning} +Before [pull request #297](https://github.com/eclipse/org.eclipse.sensinact.gateway/pull/297), the normalization was performed in ASCII mode, and the replacement character was the hyphen (`-`). +As this character is invalid in a Java identifier, it is now replaced with an underscore (`_`). +::: + ### Mapping value The value of a mapping can be defined in many different formats. @@ -294,10 +323,10 @@ For example: ```json { // ... - "${svcName}/${rcName}-txt": "${rcValuePath}", // service/rc-txt = "data" - "${svcName}/${rcName}-value":{ + "${svcName}/${rcName}_txt": "${rcValuePath}", // service/rc_txt = "data" + "${svcName}/${rcName}_value":{ "path": "${rcValuePath}" - // service/rc-value = + // service/rc_value = } } ``` @@ -317,3 +346,7 @@ The following entries are supported: Dates without timezone are considered to be in UTC. * Number inputs: * `numbers.locale`: the name of the locale to use to parse numbers, *e.g.* `fr`, `en_us`, `zh_Hand_TW`. +* Name handling: + * `names.ascii`: if set to true, all model, provider, service and resource names will be normalized to contain only ASCII letters, digits and underscores. If set to false (default), those names will be normalized to be valid Java identifiers. +* Model name handling: + * `model.raw`: if set to true, the explicit model names won't be escaped. This allows to use URLs of existing EMF models. diff --git a/docs/source/southbound/device-factory/tuto-parser.md b/docs/source/southbound/device-factory/tuto-parser.md index 2eb7e05ba..ca8b779a0 100644 --- a/docs/source/southbound/device-factory/tuto-parser.md +++ b/docs/source/southbound/device-factory/tuto-parser.md @@ -617,7 +617,7 @@ Then we configure the MQTT device factory and tell it to use our parser by a con "mapping": { "$name": "provider", "@provider": { - "literal": "sensor-${name}" + "literal": "sensor_${name}" }, "@name": { "literal": "Sensor ${name}" @@ -653,18 +653,18 @@ With those configurations, the parser will be called by the device factory core **Note:** Each mapping configuration must at least contain the `@Provider` placeholder. Each record must therefore handle a path that returns a provider name. The result will be the update/creation of the following providers: - * `sensor-a` - * `admin` + * `sensor_a` + * `admin` * `location`: `null` @ 2023-06-20T13:40:00+0200 * `friendlyName`: `"Sensor a"` @ 2023-06-20T13:40:00+0200 - * `sensor`: + * `sensor`: * `temperature`: 40.0 @ 2023-06-20T13:40:00+0200 * `humidity`: 0.0 @ 2023-06-20T13:40:00+0200 - * `sensor-c` - * `admin` + * `sensor_c` + * `admin` * `location`: `null` @ 2023-06-20T13:40:00+0200 * `friendlyName`: `"Sensor c"` @ 2023-06-20T13:40:00+0200 - * `sensor`: + * `sensor`: * `temperature`: 38.0 @ 2023-06-20T13:40:00+0200 * `humidity`: 15.0 @ 2023-06-20T13:40:00+0200 7. Now consider a second payload: @@ -677,21 +677,21 @@ With those configurations, the parser will be called by the device factory core ``` Following the same steps as before, the providers will be updated as: - * `sensor-a` + * `sensor_a` * `admin` * `location`: `null` @ 2023-06-20T13:40:00+0200 * `friendlyName`: `"Sensor a"` @ 2023-06-20T13:50:00+0200 * `sensor`: * `temperature`: 42.0 @ 2023-06-20T13:50:00+0200 * `humidity`: 0.0 @ 2023-06-20T13:50:00+0200 - * `sensor-b` + * `sensor_b` * `admin` * `location`: `null` @ 2023-06-20T13:50:00+0200 * `friendlyName`: `"Sensor b"` @ 2023-06-20T13:50:00+0200 * `sensor`: * `temperature`: 21.0 @ 2023-06-20T13:50:00+0200 * `humidity`: 30.0 @ 2023-06-20T13:50:00+0200 - * `sensor-c` + * `sensor_c` * `admin` * `location`: `null` 2023-06-20T13:40:00+0200 * `friendlyName`: `"Sensor c"` @ 2023-06-20T13:50:00+0200 @@ -740,7 +740,7 @@ This will ensure it is visible by other Maven projects and ease the creation of "mapping": { "$name": "provider", "@provider": { - "literal": "sensor-${name}" + "literal": "sensor_${name}" }, "@name": { "literal": "Sensor ${name}" @@ -802,7 +802,7 @@ After all those steps, the configuration file `${SENSINACT_HOME}/configuration/c "mapping": { "$name": "provider", "@provider": { - "literal": "sensor-${name}" + "literal": "sensor_${name}" }, "@name": { "literal": "Sensor ${name}" @@ -830,10 +830,10 @@ After all those steps, the configuration file `${SENSINACT_HOME}/configuration/c * To check the value of a resource, you can use any HTTP client (`curl`, `httPIE`, a browser...). With the payload we will send via MQTT, below are the URLs where we will find our resources values. Note that accessing those URLs before sending any data will return 404 errors. - * - * - * - * + * + * + * + * * To send messages, we will use the [MQTT dashboard websocket client](https://www.hivemq.com/demos/websocket-client/): 1. Connect the broker using the default configuration diff --git a/docs/source/southbound/http/http-device-factory.md b/docs/source/southbound/http/http-device-factory.md index 91e318a17..abe129277 100644 --- a/docs/source/southbound/http/http-device-factory.md +++ b/docs/source/southbound/http/http-device-factory.md @@ -58,7 +58,7 @@ Here is an example of configuration for HTTP device factory that will get the de "mapping": { "$station_id": "station_id", "@provider": { - "literal": "cycling-${station_id}" + "literal": "cycling_${station_id}" }, "@name": "name", "@latitude": "lat", @@ -80,7 +80,7 @@ Here is an example of configuration for HTTP device factory that will get the de "mapping": { "$station_id": "station_id", "@provider": { - "literal": "cycling-${station_id}" + "literal": "cycling_${station_id}" }, "station/active": "is_installed", "station/stationCode": "stationCode", From c39f58dac27b20bcf1806dea2aa005d0a36afadc Mon Sep 17 00:00:00 2001 From: Thomas Calmant Date: Wed, 3 Jan 2024 13:28:39 +0100 Subject: [PATCH 7/7] Fix readability issue in CSV doc --- docs/source/southbound/device-factory/csv.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/southbound/device-factory/csv.md b/docs/source/southbound/device-factory/csv.md index c55c00c3c..164d0aa43 100644 --- a/docs/source/southbound/device-factory/csv.md +++ b/docs/source/southbound/device-factory/csv.md @@ -38,8 +38,8 @@ Here are the paths that can be used: The CSV parser has the ID `csv`. It accepts the following options: -* `encoding`: the payload encoding, as supported by [`java.nio.charset.Charset`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html), *e.g.* `"UTF-8"`, `"latin-1"`. -* `delimiter`: the CSV delimiter character, *e.g.* ",", ";", "\t". +* `encoding`: the payload encoding, as supported by [`java.nio.charset.Charset`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/charset/Charset.html), *e.g.* `"UTF-8"` (default), `"latin-1"`. +* `delimiter`: the CSV delimiter character, *e.g.* `,` (default), `;` or `\t`. * `header`: a boolean flag to indicate if the CSV payload has a header ## Example @@ -55,6 +55,8 @@ Here is an example configuration to parse that payload: { "parser": "csv", "parser.options": { + "encoding": "UTF-8", + "delimiter": ",", "header": true }, "mapping": {