From 8315a0d9b01cf40f6bd4de6eac6692854f27d00c Mon Sep 17 00:00:00 2001 From: Mohammad Ghazanfar Ali Danish <62088117+mdanish98@users.noreply.github.com> Date: Thu, 1 Feb 2024 09:15:06 +0100 Subject: [PATCH] Fixes string values not working with V3 (#282) * Fixes string values not working with V3 Signed-off-by: Mohammad Ghazanfar Ali Danish * Address review remarks Signed-off-by: Mohammad Ghazanfar Ali Danish --------- Signed-off-by: Mohammad Ghazanfar Ali Danish --- .../basyx/databridge/aas/AASEndpoint.java | 27 ++--- .../databridge/aas/util/AASComponentUtil.java | 109 ++++++++++++++++++ .../src/main/resources/aasserver.json | 6 + .../main/resources/jsonatatransformer.json | 6 + .../src/main/resources/mqttconsumer.json | 8 +- .../src/main/resources/productName.jsonata | 1 + .../src/main/resources/routes.json | 8 +- .../dotaasv3api/test/TestAASUpdater.java | 51 ++++++-- 8 files changed, 186 insertions(+), 30 deletions(-) create mode 100644 databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/util/AASComponentUtil.java create mode 100644 databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/productName.jsonata diff --git a/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/AASEndpoint.java b/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/AASEndpoint.java index 342d5673..a1191396 100644 --- a/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/AASEndpoint.java +++ b/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/AASEndpoint.java @@ -46,6 +46,7 @@ import org.eclipse.basyx.vab.protocol.http.connector.HTTPConnectorFactory; import org.eclipse.digitaltwin.basyx.databridge.aas.api.ApiType; import org.eclipse.digitaltwin.basyx.databridge.aas.http.HTTPRequest; +import org.eclipse.digitaltwin.basyx.databridge.aas.util.AASComponentUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,7 +84,7 @@ public AASEndpoint(String uri, AASComponent component) { public Producer createProducer() throws Exception { return new AASProducer(this); } - + @Override public Consumer createConsumer(Processor processor) throws Exception { return null; @@ -135,7 +136,7 @@ public void setPropertyValue(Object content) throws IOException { if (api.equals(ApiType.BASYX)) { setPropertyValueUsingBaSyxAPI(content); } else { - setPropertyValueUsingDotAasV3Api(wrapStringValue(content.toString())); + setPropertyValueUsingDotAasV3Api(AASComponentUtil.wrapContent(content.toString())); } logger.info("Transferred message={}", content.toString()); @@ -187,13 +188,6 @@ private String createBaSyxApiProxyUrl() { return proxyUrl; } - private String wrapStringValue(String content) { - if (content.isEmpty()) - return content; - - return "\"" + content + "\""; - } - private Object getContent(Object messageBody, ValueType propertyValueType) { if (propertyValueType.equals(ValueType.String)) return removeQuotesFromString(messageBody.toString()); @@ -201,11 +195,11 @@ private Object getContent(Object messageBody, ValueType propertyValueType) { return ValueTypeHelper.getJavaObject(messageBody, propertyValueType); } - private static String removeQuotesFromString(String messageBody) { + private String removeQuotesFromString(String messageBody) { if (messageBody == null) return null; - if (messageBody.startsWith("\"") && messageBody.endsWith("\"")) { + if (AASComponentUtil.isAlreadyWrapped(messageBody)) { return messageBody.substring(1, messageBody.length() - 1); } @@ -226,10 +220,9 @@ public String getFullProxyUrl() { return createDotAasApiProxyUrl(); } - - @Override - public PollingConsumer createPollingConsumer() throws Exception { - AASPollingConsumer consumer = new AASPollingConsumer(this); - return consumer; - } + + @Override + public PollingConsumer createPollingConsumer() throws Exception { + return new AASPollingConsumer(this); + } } diff --git a/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/util/AASComponentUtil.java b/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/util/AASComponentUtil.java new file mode 100644 index 00000000..5e629fba --- /dev/null +++ b/databridge.camel-aas/src/main/java/org/eclipse/digitaltwin/basyx/databridge/aas/util/AASComponentUtil.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * 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.databridge.aas.util; + +/** + * An utility class for the AAS Component + * + * @author danish + */ +public class AASComponentUtil { + + private AASComponentUtil() { + throw new IllegalStateException("Utility class"); + } + + /** + * A convenient method to wrap the content as String + * + * e.g., + * + *
+	 * 72 -> "72"
+	 * 56.23 -> "56.23"
+	 * "example" -> "example"
+	 * "example -> RuntimeException (Malformed from the right)
+	 * example" -> RuntimeException (Malformed from the left)
+	 * 
+	 * 
+ * + * @param content + * @return + */ + public static String wrapContent(String content) { + + if (content == null || content.isEmpty()) + return ""; + + if (isAlreadyWrapped(content)) + return content; + + throwExceptionIfMalformedWrapping(content); + + return wrapStringValue(content); + } + + /** + * Checks whether the provided string value is already wrapped in quotes ("") or not + * + * e.g., + * + *
+	 * 56.23 -> false
+	 * "example" -> true
+	 * example -> false
+	 * 
+	 * 
+ * + * @param content + * @return + */ + public static boolean isAlreadyWrapped(String content) { + return content.startsWith("\"") && content.endsWith("\""); + } + + private static String wrapStringValue(String content) { + if (content.isEmpty()) + return content; + + return "\"" + content + "\""; + } + + private static void throwExceptionIfMalformedWrapping(String content) { + + if (isRightMalformed(content) || isLeftMalformed(content)) + throw new RuntimeException("The content's: " + content + " formatting is malformed."); + } + + private static boolean isLeftMalformed(String content) { + return !content.startsWith("\"") && content.endsWith("\""); + } + + private static boolean isRightMalformed(String content) { + return content.startsWith("\"") && !content.endsWith("\""); + } + +} diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/aasserver.json b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/aasserver.json index ea514097..877944e4 100644 --- a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/aasserver.json +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/aasserver.json @@ -4,5 +4,11 @@ "submodelEndpoint": "http://localhost:4001/submodels/c3VibW9kZWxJZA==", "idShortPath": "DotAASV3ConformantApiSMC.DotAASV3ConformantApiProperty", "api": "DotAAS-V3" + }, + { + "uniqueId": "ConnectedTestSubmodel/DotAASV3ConformantApiStringProperty", + "submodelEndpoint": "http://localhost:4001/submodels/c3VibW9kZWxJZA==", + "idShortPath": "DotAASV3ConformantApiSMC.DotAASV3ConformantApiStringProperty", + "api": "DotAAS-V3" } ] \ No newline at end of file diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/jsonatatransformer.json b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/jsonatatransformer.json index 685985d8..479baddb 100644 --- a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/jsonatatransformer.json +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/jsonatatransformer.json @@ -4,5 +4,11 @@ "queryPath": "weight.jsonata", "inputType": "JsonString", "outputType": "JsonString" + }, + { + "uniqueId": "productName", + "queryPath": "productName.jsonata", + "inputType": "JsonString", + "outputType": "JsonString" } ] \ No newline at end of file diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/mqttconsumer.json b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/mqttconsumer.json index b9299d86..b4558a40 100644 --- a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/mqttconsumer.json +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/mqttconsumer.json @@ -1,8 +1,14 @@ [ { - "uniqueId": "mqttSource", + "uniqueId": "mqttSource1", "serverUrl": "localhost", "serverPort": 1884, "topic": "DotAASV3ConformantProperty" + }, + { + "uniqueId": "mqttSource2", + "serverUrl": "localhost", + "serverPort": 1884, + "topic": "DotAASV3ConformantPropertyStringValue" } ] \ No newline at end of file diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/productName.jsonata b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/productName.jsonata new file mode 100644 index 00000000..c2546089 --- /dev/null +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/productName.jsonata @@ -0,0 +1 @@ +Account.Order[0].Product[0].'Product Name' \ No newline at end of file diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/routes.json b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/routes.json index 23f1d647..b1ac0e87 100644 --- a/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/routes.json +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/main/resources/routes.json @@ -1,8 +1,14 @@ [ { - "datasource": "mqttSource", + "datasource": "mqttSource1", "transformers": ["weight"], "datasinks": ["ConnectedTestSubmodel/DotAASV3ConformantApiProperty"], "trigger": "event" + }, + { + "datasource": "mqttSource2", + "transformers": ["productName"], + "datasinks": ["ConnectedTestSubmodel/DotAASV3ConformantApiStringProperty"], + "trigger": "event" } ] \ No newline at end of file diff --git a/databridge.examples/databridge.examples.dot-aas-v3-api/src/test/java/org/eclipse/digitaltwin/basyx/databridge/examples/dotaasv3api/test/TestAASUpdater.java b/databridge.examples/databridge.examples.dot-aas-v3-api/src/test/java/org/eclipse/digitaltwin/basyx/databridge/examples/dotaasv3api/test/TestAASUpdater.java index b5c15798..681838e9 100644 --- a/databridge.examples/databridge.examples.dot-aas-v3-api/src/test/java/org/eclipse/digitaltwin/basyx/databridge/examples/dotaasv3api/test/TestAASUpdater.java +++ b/databridge.examples/databridge.examples.dot-aas-v3-api/src/test/java/org/eclipse/digitaltwin/basyx/databridge/examples/dotaasv3api/test/TestAASUpdater.java @@ -60,8 +60,10 @@ public class TestAASUpdater { - private static final String PROPERTY_VALUE = "\"0.75\""; - private static final String PROPERTY_VALUE_PATH = "/submodels/c3VibW9kZWxJZA==/submodel-elements/DotAASV3ConformantApiSMC.DotAASV3ConformantApiProperty/$value"; + private static final String PROPERTY_INTEGER_VALUE = "\"0.75\""; + private static final String PROPERTY_INTEGER_VALUE_PATH = "/submodels/c3VibW9kZWxJZA==/submodel-elements/DotAASV3ConformantApiSMC.DotAASV3ConformantApiProperty/$value"; + private static final String PROPERTY_STRING_VALUE = "\"Bowler Hat\""; + private static final String PROPERTY_STRING_VALUE_PATH = "/submodels/c3VibW9kZWxJZA==/submodel-elements/DotAASV3ConformantApiSMC.DotAASV3ConformantApiStringProperty/$value"; private static Logger logger = LoggerFactory.getLogger(TestAASUpdater.class); @@ -86,18 +88,29 @@ public static void tearDown() { } @Test - public void getDotAASV3ConformantPropertyValue() throws MqttSecurityException, MqttPersistenceException, MqttException, InterruptedException { + public void getDotAASV3ConformantPropertyIntegerValue() throws MqttSecurityException, MqttPersistenceException, MqttException, InterruptedException { publishNewDatapoint("DotAASV3ConformantProperty"); waitForPropagation(); - verifyPropertyValueUpdateRequest(); + verifyPropertyValueUpdateRequestIntegerValue(); + } + + @Test + public void getDotAASV3ConformantPropertyStringValue() throws MqttSecurityException, MqttPersistenceException, MqttException, InterruptedException { + publishNewDatapoint("DotAASV3ConformantPropertyStringValue"); + + waitForPropagation(); + + verifyPropertyValueUpdateRequestStringValue(); } private static void configureAndStartMockserver() { mockServer = ClientAndServer.startClientAndServer(4001); + + createExpectationForPatchRequestForIntegerValue(); - createExpectationForPatchRequest(); + createExpectationForPatchRequestForStringValue(); } private static void configureAndStartUpdaterComponent() { @@ -143,18 +156,34 @@ private static void configureAndStartMqttBroker() throws IOException { } @SuppressWarnings("resource") - private static void createExpectationForPatchRequest() { + private static void createExpectationForPatchRequestForIntegerValue() { new MockServerClient("localhost", 4001) - .when(request().withMethod("PATCH").withPath(PROPERTY_VALUE_PATH) - .withBody(PROPERTY_VALUE).withHeader("Content-Type", "application/json")) + .when(request().withMethod("PATCH").withPath(PROPERTY_INTEGER_VALUE_PATH) + .withBody(PROPERTY_INTEGER_VALUE).withHeader("Content-Type", "application/json")) .respond(response().withStatusCode(HttpStatus.SC_CREATED) .withHeaders(new Header("Content-Type", "application/json; charset=utf-8"))); } + + @SuppressWarnings("resource") + private static void createExpectationForPatchRequestForStringValue() { + new MockServerClient("localhost", 4001) + .when(request().withMethod("PATCH").withPath(PROPERTY_STRING_VALUE_PATH) + .withBody(PROPERTY_STRING_VALUE).withHeader("Content-Type", "application/json")) + .respond(response().withStatusCode(HttpStatus.SC_CREATED) + .withHeaders(new Header("Content-Type", "application/json; charset=utf-8"))); + } @SuppressWarnings("resource") - private void verifyPropertyValueUpdateRequest() { + private void verifyPropertyValueUpdateRequestIntegerValue() { + new MockServerClient("localhost", 4001).verify(request().withMethod("PATCH") + .withPath(PROPERTY_INTEGER_VALUE_PATH).withHeader("Content-Type", "application/json") + .withBody(PROPERTY_INTEGER_VALUE), VerificationTimes.exactly(1)); + } + + @SuppressWarnings("resource") + private void verifyPropertyValueUpdateRequestStringValue() { new MockServerClient("localhost", 4001).verify(request().withMethod("PATCH") - .withPath(PROPERTY_VALUE_PATH).withHeader("Content-Type", "application/json") - .withBody(PROPERTY_VALUE), VerificationTimes.exactly(1)); + .withPath(PROPERTY_STRING_VALUE_PATH).withHeader("Content-Type", "application/json") + .withBody(PROPERTY_STRING_VALUE), VerificationTimes.exactly(1)); } }