This is an automated email from the ASF dual-hosted git repository.

zehnder pushed a commit to branch SP-1713
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit c92ef0118c592a3d7f025449611bb54cc032f58f
Author: Philipp Zehnder <[email protected]>
AuthorDate: Mon Jun 26 10:29:22 2023 +0200

    [#1713] Fix NullPointer in OPC UA browsing. Fix OPC UA demo docker 
container.
---
 .../cli/deploy/standalone/opcua/docker-compose.yml |   1 +
 .../streampipes-connectors-opcua/pom.xml           |  12 +
 .../connectors/opcua/adapter/OpcUaNodeBrowser.java |  36 +--
 .../opcua/adapter/OpcUaNodeMetadataExtractor.java  | 171 ++++++++++++++
 .../adapter/OpcUaNodeMetadataExtractorTest.java    | 258 +++++++++++++++++++++
 5 files changed, 444 insertions(+), 34 deletions(-)

diff --git a/installer/cli/deploy/standalone/opcua/docker-compose.yml 
b/installer/cli/deploy/standalone/opcua/docker-compose.yml
index 65ed1de07..994d8b90b 100644
--- a/installer/cli/deploy/standalone/opcua/docker-compose.yml
+++ b/installer/cli/deploy/standalone/opcua/docker-compose.yml
@@ -21,6 +21,7 @@ services:
       - ./config-generated/:/appdata
     ports:
       - 50000:50000
+    command: --ut=true
     logging:
       driver: "json-file"
       options:
diff --git a/streampipes-extensions/streampipes-connectors-opcua/pom.xml 
b/streampipes-extensions/streampipes-connectors-opcua/pom.xml
index b5b3350e9..b7c34ab0d 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/pom.xml
+++ b/streampipes-extensions/streampipes-connectors-opcua/pom.xml
@@ -60,6 +60,18 @@
             <groupId>org.eclipse.milo</groupId>
             <artifactId>sdk-client</artifactId>
         </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
index b9ef833c5..fa33a2896 100644
--- 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
@@ -28,20 +28,16 @@ import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
 import org.eclipse.milo.opcua.stack.core.Identifiers;
-import org.eclipse.milo.opcua.stack.core.StatusCodes;
 import org.eclipse.milo.opcua.stack.core.UaException;
 import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
 import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
 import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
@@ -115,42 +111,13 @@ public class OpcUaNodeBrowser {
           childNode.setNodeName(node.getDisplayName().getText());
           childNode.setInternalNodeName(node.getNodeId().toParseableString());
           childNode.setDataNode(isDataNode(node));
-          childNode.setNodeMetadata(buildMetadata(client, node));
+          childNode.setNodeMetadata(new OpcUaNodeMetadataExtractor(client, 
node).extract());
           return childNode;
         })
         .collect(Collectors.toList());
   }
 
-  private Map<String, Object> buildMetadata(OpcUaClient client,
-                                            UaNode node) {
-    var metadata = new HashMap<String, Object>();
-    metadata.put("NamespaceIndex", 
node.getNodeId().getNamespaceIndex().toString());
-    metadata.put("NodeClass", node.getNodeClass().toString());
-    metadata.put("Description", node.getDescription().getText());
-    metadata.put("BrowseName", node.getBrowseName().getName());
-    metadata.put("DisplayName", node.getDisplayName().getText());
 
-    if (node instanceof UaVariableNode) {
-      var dataTypeNodeId = ((UaVariableNode) node).getDataType();
-      metadata.put("Value", String.valueOf(((UaVariableNode) 
node).getValue().getValue().getValue()));
-      try {
-        var dataTypeNode = client.getAddressSpace().getNode(dataTypeNodeId);
-        var value = client.readValue(0, TimestampsToReturn.Both, 
node.getNodeId()).get();
-        metadata.put("SourceTime", 
value.getSourceTime().getJavaDate().toString());
-        metadata.put("ServerTime", 
value.getServerTime().getJavaDate().toString());
-        var statusCode = value.getStatusCode().getValue();
-        var statusCodeName = StatusCodes.lookup(statusCode).get();
-        if (statusCodeName.length > 0) {
-          metadata.put("Status", statusCodeName[0]);
-        }
-        metadata.put("DataType", dataTypeNode.getDisplayName().getText());
-      } catch (UaException | ExecutionException | InterruptedException e) {
-        throw new RuntimeException(e);
-      }
-    }
-
-    return metadata;
-  }
 
   private AddressSpace getAddressSpace() {
     return client.getAddressSpace();
@@ -160,4 +127,5 @@ public class OpcUaNodeBrowser {
     return (node.getNodeClass().equals(NodeClass.Variable) || 
(node.getNodeClass().equals(NodeClass.VariableType)))
         && node instanceof UaVariableNode;
   }
+
 }
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
new file mode 100644
index 000000000..04dda6eb6
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.adapter;
+
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
+import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
+import org.eclipse.milo.opcua.stack.core.StatusCodes;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+public class OpcUaNodeMetadataExtractor {
+  private final OpcUaClient client;
+  private final UaNode node;
+
+  private final Map<String, Object> metadata;
+
+  public OpcUaNodeMetadataExtractor(OpcUaClient client, UaNode node) {
+    this.client = client;
+    this.node = node;
+    this.metadata = new HashMap<>();
+  }
+
+  public Map<String, Object> extract() {
+    extractNamespaceIndex();
+    extractNodeClass();
+    extractDescription();
+    extractBrowseName();
+    extractDisplayName();
+
+    if (node instanceof UaVariableNode) {
+      extractValue();
+      try {
+        var dataTypeNodeId = ((UaVariableNode) node).getDataType();
+        var dataTypeNode = client.getAddressSpace().getNode(dataTypeNodeId);
+        var value = client.readValue(0, TimestampsToReturn.Both, 
node.getNodeId()).get();
+
+        extractSourceTime(value);
+        extractServerTime(value);
+        extractStatusCode(value);
+        extractDataType(dataTypeNode);
+      } catch (UaException | ExecutionException | InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    return metadata;
+  }
+
+
+  public void extractDescription() {
+    if (node.getDescription() != null) {
+      add("Description", node.getDescription().getText());
+    } else {
+      add("Description", "");
+    }
+  }
+
+  public void extractNamespaceIndex() {
+    if (node.getNodeId() != null) {
+      add("NamespaceIndex", node.getNodeId().getNamespaceIndex().toString());
+    } else {
+      add("NamespaceIndex", "");
+    }
+  }
+
+  public void extractNodeClass() {
+    if (node.getNodeClass() != null) {
+      add("NodeClass", node.getNodeClass().toString());
+    } else {
+      add("NodeClass", "");
+    }
+  }
+
+  public void extractBrowseName() {
+    if (node.getBrowseName() != null) {
+      add("BrowseName", node.getBrowseName().getName());
+    } else {
+      add("BrowseName", "");
+    }
+  }
+
+  public void extractDisplayName() {
+    if (node.getDisplayName() != null) {
+      add("DisplayName", node.getDisplayName().getText());
+    } else {
+      add("DisplayName", "");
+    }
+  }
+
+  public void extractValue() {
+    if (node instanceof UaVariableNode) {
+      var v1 = ((UaVariableNode) node).getValue();
+      if (v1 != null) {
+        var v2 = v1.getValue();
+        if (v2 != null) {
+          add("Value", String.valueOf(v2.getValue()));
+        }
+      }
+    }
+  }
+
+  public void extractSourceTime(DataValue value) {
+    add("SourceTime", dateTimeToString(value.getSourceTime()));
+  }
+
+  public void extractServerTime(DataValue value) {
+    add("ServerTime", dateTimeToString(value.getServerTime()));
+  }
+
+  public void extractStatusCode(DataValue value) {
+    var statusCode = value.getStatusCode();
+    if (statusCode != null) {
+      var statusCodeValue = statusCode.getValue();
+      var lookup = StatusCodes.lookup(statusCodeValue);
+      if (lookup.isPresent()) {
+        var statusCodeName = lookup.get();
+        if (statusCodeName.length > 0) {
+          metadata.put("Status", statusCodeName[0]);
+        }
+      }
+    }
+  }
+
+  public void extractDataType(UaNode dataTypeNode) {
+    if (dataTypeNode.getDisplayName() != null) {
+      metadata.put("DataType", dataTypeNode.getDisplayName().getText());
+    } else {
+      metadata.put("DataType", "");
+    }
+  }
+
+  private String dateTimeToString(DateTime time) {
+    if (time != null) {
+      return time.getJavaDate().toString();
+    } else {
+      return "";
+    }
+  }
+
+  public Map<String, Object> getMetadata() {
+    return metadata;
+  }
+
+  private void add(String key, String value) {
+    metadata.put(key, value);
+  }
+
+}
diff --git 
a/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractorTest.java
 
b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractorTest.java
new file mode 100644
index 000000000..e9b5e7691
--- /dev/null
+++ 
b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractorTest.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.adapter;
+
+import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OpcUaNodeMetadataExtractorTest {
+
+  private final String date = "Tue Jan 02 23:52:56 CET 1601";
+
+  @Test
+  public void testExtractDescription() {
+    var description = "test";
+    var node = mock(UaNode.class);
+    when(node.getDescription()).thenReturn(new LocalizedText(description));
+    var extractor = getExtractor(node);
+
+    extractor.extractDescription();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("Description"));
+    assertEquals(description, metadata.get("Description"));
+  }
+
+  @Test
+  public void testExtractDescriptionNull() {
+    var node = mock(UaNode.class);
+    when(node.getDescription()).thenReturn(null);
+    var extractor = getExtractor(node);
+
+    extractor.extractDescription();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("Description"));
+    assertEquals("", metadata.get("Description"));
+  }
+
+  @Test
+  public void testExtractNamespaceIndex() {
+    var node = mock(UaNode.class);
+    when(node.getNodeId()).thenReturn(new NodeId(1, 1));
+    var extractor = getExtractor(node);
+
+    extractor.extractNamespaceIndex();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("NamespaceIndex"));
+    assertEquals("1", metadata.get("NamespaceIndex"));
+  }
+
+  @Test
+  public void testExtractNamespaceIndexNull() {
+    var node = mock(UaNode.class);
+    when(node.getNodeId()).thenReturn(null);
+    var extractor = getExtractor(node);
+
+    extractor.extractNamespaceIndex();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("NamespaceIndex"));
+    assertEquals("", metadata.get("NamespaceIndex"));
+  }
+
+  @Test
+  public void testExtractNodeClass() {
+    var node = mock(UaNode.class);
+    when(node.getNodeClass()).thenReturn(NodeClass.Variable);
+    var extractor = getExtractor(node);
+
+    extractor.extractNodeClass();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("NodeClass"));
+    assertEquals("Variable", metadata.get("NodeClass"));
+  }
+
+  @Test
+  public void testExtractNodeClassNull() {
+    var node = mock(UaNode.class);
+    when(node.getNodeClass()).thenReturn(null);
+    var extractor = getExtractor(node);
+
+    extractor.extractNodeClass();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("NodeClass"));
+    assertEquals("", metadata.get("NodeClass"));
+  }
+
+  @Test
+  public void testExtractBrowseName() {
+    var expectedNodeName = "nodeName";
+    var node = mock(UaNode.class);
+    when(node.getBrowseName()).thenReturn(new QualifiedName(1, 
expectedNodeName));
+    var extractor = getExtractor(node);
+
+    extractor.extractBrowseName();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("BrowseName"));
+    assertEquals(expectedNodeName, metadata.get("BrowseName"));
+  }
+
+  @Test
+  public void testExtractBrowseNameNull() {
+    var node = mock(UaNode.class);
+    when(node.getBrowseName()).thenReturn(null);
+    var extractor = getExtractor(node);
+
+    extractor.extractBrowseName();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("BrowseName"));
+    assertEquals("", metadata.get("BrowseName"));
+  }
+
+  @Test
+  public void testExtractDisplayName() {
+    var expectedName = "expectedName";
+    var node = mock(UaNode.class);
+    when(node.getDisplayName()).thenReturn(new LocalizedText(expectedName));
+    var extractor = getExtractor(node);
+
+    extractor.extractDisplayName();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("DisplayName"));
+    assertEquals(expectedName, metadata.get("DisplayName"));
+  }
+
+  @Test
+  public void testExtractDisplayNameNull() {
+    var node = mock(UaNode.class);
+    when(node.getDisplayName()).thenReturn(null);
+    var extractor = getExtractor(node);
+
+    extractor.extractDisplayName();
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("DisplayName"));
+    assertEquals("", metadata.get("DisplayName"));
+  }
+
+  @Test
+  public void testExtractSourceTime() {
+    var value = mock(DataValue.class);
+    when(value.getSourceTime()).thenReturn(new DateTime(1687762000000L));
+    var extractor = getExtractor();
+
+    extractor.extractSourceTime(value);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("SourceTime"));
+    assertEquals(date, metadata.get("SourceTime"));
+  }
+
+  @Test
+  public void testExtractSourceTimeNull() {
+    var value = mock(DataValue.class);
+    when(value.getSourceTime()).thenReturn(null);
+    var extractor = getExtractor();
+
+    extractor.extractSourceTime(value);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("SourceTime"));
+    assertEquals("", metadata.get("SourceTime"));
+  }
+
+  @Test
+  public void testExtractServerTime() {
+    var value = mock(DataValue.class);
+    when(value.getServerTime()).thenReturn(new DateTime(1687762000000L));
+    var extractor = getExtractor();
+
+    extractor.extractServerTime(value);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("ServerTime"));
+    assertEquals(date, metadata.get("ServerTime"));
+  }
+
+  @Test
+  public void testExtractServerTimeNull() {
+    var value = mock(DataValue.class);
+    when(value.getServerTime()).thenReturn(null);
+    var extractor = getExtractor();
+
+    extractor.extractServerTime(value);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("ServerTime"));
+    assertEquals("", metadata.get("ServerTime"));
+  }
+
+  @Test
+  public void testExtractDataType() {
+    var expectedName = "expectedName";
+    var dataTypeNode = mock(UaNode.class);
+    when(dataTypeNode.getDisplayName()).thenReturn(new 
LocalizedText(expectedName));
+    var extractor = getExtractor();
+
+    extractor.extractDataType(dataTypeNode);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("DataType"));
+    assertEquals(expectedName, metadata.get("DataType"));
+  }
+
+  @Test
+  public void testExtractDataTypeNull() {
+    var dataTypeNode = mock(UaNode.class);
+    when(dataTypeNode.getDisplayName()).thenReturn(null);
+    var extractor = getExtractor();
+
+    extractor.extractDataType(dataTypeNode);
+
+    var metadata = extractor.getMetadata();
+    assertTrue(metadata.containsKey("DataType"));
+    assertEquals("", metadata.get("DataType"));
+  }
+
+  private OpcUaNodeMetadataExtractor getExtractor(UaNode node) {
+    return new OpcUaNodeMetadataExtractor(null, node);
+  }
+
+  private OpcUaNodeMetadataExtractor getExtractor() {
+    return new OpcUaNodeMetadataExtractor(null, null);
+  }
+}
\ No newline at end of file

Reply via email to