You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2022/01/10 21:38:22 UTC

[incubator-streampipes] branch STREAMPIPES-505 created (now 72d0676)

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

riemer pushed a change to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git.


      at 72d0676  [STREAMPIPES-505] Improve OPC-UA adapter

This branch includes the following new commits:

     new 7884f29  [STREAMPIPES-504] Refactor runtime-resolvable configs, add initial TreeInputStaticProperty
     new 2e90b69  [hotfix] Add TreeInput to pipeline template visitor
     new 9ec04f8  [STREAMPIPES-504] Improve rendering of runtime-resolvable properties
     new 27051a5  [STREAMPIPES-504] Add UI implementation of tree input
     new dbe82ca  [STREAMPIPES-505] Improve runtime-resolvable configuration of adapters, split OPC adapter
     new c1d7bae  [STREAMPIPES-505] Rename OpcUa to SpUpcUaClient
     new 8cce5db  [STREAMPIPES-505] Extract node browser to own class
     new 72d0676  [STREAMPIPES-505] Improve OPC-UA adapter

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[incubator-streampipes] 03/08: [STREAMPIPES-504] Improve rendering of runtime-resolvable properties

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 9ec04f8b0acc2af8b51f86346fdce65e93743ba2
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sat Jan 8 15:06:51 2022 +0100

    [STREAMPIPES-504] Improve rendering of runtime-resolvable properties
---
 .../RuntimeResolvableTreeInputStaticProperty.java  |  5 ++
 .../endpoint/ExtensionsServiceEndpointUtils.java   | 11 +++--
 .../sdk/extractor/AbstractParameterExtractor.java  |  2 +-
 .../api/IPipelineElementDescriptionStorage.java    |  4 ++
 .../couchdb/impl/DataProcessorStorageImpl.java     |  3 +-
 .../PipelineElementDescriptionStorageImpl.java     | 21 ++++++++
 ui/src/app/core-ui/core-ui.module.ts               |  2 +
 .../static-property.component.html                 | 24 +++++++---
 .../static-properties/static-property.component.ts |  6 ++-
 ...tatic-runtime-resolvable-any-input.component.ts | 13 +++--
 .../base-runtime-resolvable-input.ts               | 39 ++++++++-------
 .../base-runtime-resolvable-selection-input.ts     | 41 ++++++++++++++++
 ...tic-runtime-resolvable-oneof-input.component.ts | 13 +++--
 .../static-tree-input.component.html               | 17 +++++++
 .../static-tree-input.component.scss               | 17 +++++++
 .../static-tree-input.component.ts                 | 56 ++++++++++++++++++++++
 16 files changed, 236 insertions(+), 38 deletions(-)

diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java
index 08381c3..fbfba8d 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java
@@ -18,6 +18,7 @@
 
 package org.apache.streampipes.model.staticproperty;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -28,12 +29,16 @@ public class RuntimeResolvableTreeInputStaticProperty extends StaticProperty {
 
   public RuntimeResolvableTreeInputStaticProperty() {
     super(StaticPropertyType.RuntimeResolvableTreeInputStaticProperty);
+    this.dependsOn = new ArrayList<>();
+    this.nodes = new ArrayList<>();
   }
 
   public RuntimeResolvableTreeInputStaticProperty(String internalName,
                                                   String label,
                                                   String description) {
     super(StaticPropertyType.RuntimeResolvableTreeInputStaticProperty, internalName, label, description);
+    this.dependsOn = new ArrayList<>();
+    this.nodes = new ArrayList<>();
   }
 
   public RuntimeResolvableTreeInputStaticProperty(RuntimeResolvableTreeInputStaticProperty other) {
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
index d298702..af6f81a 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/endpoint/ExtensionsServiceEndpointUtils.java
@@ -23,6 +23,8 @@ import org.apache.streampipes.model.graph.DataProcessorInvocation;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.svcdiscovery.api.model.SpServiceUrlProvider;
 
+import java.util.NoSuchElementException;
+
 public class ExtensionsServiceEndpointUtils {
 
   public static SpServiceUrlProvider getPipelineElementType(NamedStreamPipesEntity entity) {
@@ -30,9 +32,12 @@ public class ExtensionsServiceEndpointUtils {
   }
 
   public static SpServiceUrlProvider getPipelineElementType(String appId) {
-    DataProcessorDescription desc = StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineElementDescriptionStorage().getDataProcessorByAppId(appId);
-    return desc != null ? SpServiceUrlProvider.DATA_PROCESSOR : SpServiceUrlProvider.DATA_SINK;
-
+    try {
+      StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineElementDescriptionStorage().getDataProcessorByAppId(appId);
+      return SpServiceUrlProvider.DATA_PROCESSOR;
+    } catch (NoSuchElementException e) {
+      return SpServiceUrlProvider.DATA_SINK;
+    }
   }
 
   private static boolean isDataProcessor(NamedStreamPipesEntity entity) {
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
index 17191da..4d50ce7 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
@@ -240,7 +240,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
             .collect(Collectors.toList());
   }
 
-  private <S extends StaticProperty> S getStaticPropertyByName(String internalName, Class<S>
+  public <S extends StaticProperty> S getStaticPropertyByName(String internalName, Class<S>
           spType) {
     return spType.cast(getStaticPropertyByName(internalName));
   }
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineElementDescriptionStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineElementDescriptionStorage.java
index 830c4a0..a6d9693 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineElementDescriptionStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IPipelineElementDescriptionStorage.java
@@ -73,6 +73,10 @@ public interface IPipelineElementDescriptionStorage {
 	
 	boolean exists(DataProcessorDescription processorDescription);
 
+	boolean existsDataProcessorByAppId(String appId);
+
+	boolean existsDataSinkByAppId(String appId);
+
 	boolean existsDataProcessor(String elementId);
 
 	boolean existsDataStream(String elementId);
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/DataProcessorStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/DataProcessorStorageImpl.java
index 4d54087..3ae0cd6 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/DataProcessorStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/DataProcessorStorageImpl.java
@@ -23,6 +23,7 @@ import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
 import org.apache.streampipes.storage.couchdb.utils.Utils;
 
 import java.util.List;
+import java.util.NoSuchElementException;
 
 public class DataProcessorStorageImpl extends AbstractDao<DataProcessorDescription> implements IDataProcessorStorage {
 
@@ -64,7 +65,7 @@ public class DataProcessorStorageImpl extends AbstractDao<DataProcessorDescripti
             .stream()
             .filter(p -> p.getAppId().equals(appId))
             .findFirst()
-            .orElseThrow(IllegalArgumentException::new);
+            .orElseThrow(NoSuchElementException::new);
   }
 
   private String getCurrentRev(String elementId) {
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineElementDescriptionStorageImpl.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineElementDescriptionStorageImpl.java
index 409814b..ce1b095 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineElementDescriptionStorageImpl.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/PipelineElementDescriptionStorageImpl.java
@@ -29,6 +29,7 @@ import org.apache.streampipes.storage.api.IPipelineElementDescriptionStorageCach
 
 import java.net.URI;
 import java.util.List;
+import java.util.NoSuchElementException;
 
 public class PipelineElementDescriptionStorageImpl implements IPipelineElementDescriptionStorageCache {
 
@@ -159,6 +160,26 @@ public class PipelineElementDescriptionStorageImpl implements IPipelineElementDe
   }
 
   @Override
+  public boolean existsDataProcessorByAppId(String appId) {
+    try {
+      getDataProcessorByAppId(appId);
+      return true;
+    } catch (NoSuchElementException e) {
+      return false;
+    }
+  }
+
+  @Override
+  public boolean existsDataSinkByAppId(String appId) {
+    try {
+      getDataSinkByAppId(appId);
+      return true;
+    } catch (NoSuchElementException e) {
+      return false;
+    }
+  }
+
+  @Override
   public boolean existsDataProcessor(String elementId) {
     return getDataProcessorById(elementId) != null;
   }
diff --git a/ui/src/app/core-ui/core-ui.module.ts b/ui/src/app/core-ui/core-ui.module.ts
index beba7dd..e8de4ad 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -83,6 +83,7 @@ import { SplitSectionComponent } from './split-section/split-section.component';
 import { ObjectPermissionDialogComponent } from './object-permission-dialog/object-permission-dialog.component';
 import { StaticSlideToggleComponent } from './static-properties/static-slide-toggle/static-slide-toggle.component';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { StaticRuntimeResolvableTreeInputComponent } from './static-properties/static-runtime-resolvable-tree-input/static-tree-input.component';
 
 @NgModule({
   imports: [
@@ -139,6 +140,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
     StaticOneOfInputComponent,
     StaticRuntimeResolvableAnyInputComponent,
     StaticRuntimeResolvableOneOfInputComponent,
+    StaticRuntimeResolvableTreeInputComponent,
     StaticSlideToggleComponent,
     StatusWidgetComponent,
     LabelListItemComponent,
diff --git a/ui/src/app/core-ui/static-properties/static-property.component.html b/ui/src/app/core-ui/static-properties/static-property.component.html
index 65b9b67..a524931 100644
--- a/ui/src/app/core-ui/static-properties/static-property.component.html
+++ b/ui/src/app/core-ui/static-properties/static-property.component.html
@@ -25,11 +25,11 @@
         </div>
         <div fxFlex="100">
             <sp-static-code-input *ngIf="isCodeInputStaticProperty(staticProperty)"
-                                   [staticProperty]="staticProperty"
-                                   [parentForm]="parentForm"
-                                   [eventSchemas]="eventSchemas"
-                                   [fieldName]="fieldName"
-                                   (updateEmitter)="emitUpdate($event)">
+                                  [staticProperty]="staticProperty"
+                                  [parentForm]="parentForm"
+                                  [eventSchemas]="eventSchemas"
+                                  [fieldName]="fieldName"
+                                  (updateEmitter)="emitUpdate($event)">
             </sp-static-code-input>
 
             <app-static-secret-input *ngIf="isSecretStaticProperty(staticProperty)"
@@ -152,8 +152,20 @@
                                     [displayRecommended]="displayRecommended"
                                     [staticProperty]="staticProperty" class="test fullWidth"
                                     (updateEmitter)="emitUpdate($event)">
-
             </sp-static-slide-toggle>
+            <sp-runtime-resolvable-tree-input
+                    *ngIf="isTreeInputStaticProperty(staticProperty)"
+                    [adapterId]="adapterId"
+                    [parentForm]="parentForm"
+                    [completedStaticProperty]="completedStaticProperty"
+                    [pipelineElement]="pipelineElement"
+                    [eventSchemas]="eventSchemas"
+                    [staticProperties]="staticProperties"
+                    [fieldName]="fieldName"
+                    [displayRecommended]="displayRecommended"
+                    [staticProperty]="staticProperty" class="test fullWidth"
+                    (updateEmitter)="emitUpdate($event)">
+            </sp-runtime-resolvable-tree-input>
         </div>
     </div>
     <mat-divider></mat-divider>
diff --git a/ui/src/app/core-ui/static-properties/static-property.component.ts b/ui/src/app/core-ui/static-properties/static-property.component.ts
index 6ba0b8c..d1e5897 100644
--- a/ui/src/app/core-ui/static-properties/static-property.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-property.component.ts
@@ -34,7 +34,7 @@ import {
   MappingPropertyUnary,
   OneOfStaticProperty,
   RuntimeResolvableAnyStaticProperty,
-  RuntimeResolvableOneOfStaticProperty,
+  RuntimeResolvableOneOfStaticProperty, RuntimeResolvableTreeInputStaticProperty,
   SecretStaticProperty, SlideToggleStaticProperty,
   StaticProperty,
   StaticPropertyAlternatives,
@@ -163,6 +163,10 @@ export class StaticPropertyComponent implements OnInit {
     return val instanceof SlideToggleStaticProperty;
   }
 
+  isTreeInputStaticProperty(val) {
+    return val instanceof RuntimeResolvableTreeInputStaticProperty;
+  }
+
   valueChange(hasInput) {
     //this.staticProperty.isValid = hasInput;
     this.validateEmitter.emit();
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.ts
index 586c815..dcba752 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.ts
@@ -17,16 +17,16 @@
  */
 
 import { Component, OnInit } from '@angular/core';
-import { BaseRuntimeResolvableInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-input';
-import { RuntimeResolvableAnyStaticProperty } from '../../../core-model/gen/streampipes-model';
+import { RuntimeResolvableAnyStaticProperty, StaticPropertyUnion } from '../../../core-model/gen/streampipes-model';
 import { RuntimeResolvableService } from '../static-runtime-resolvable-input/runtime-resolvable.service';
+import { BaseRuntimeResolvableSelectionInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-selection-input';
 
 @Component({
     selector: 'app-static-runtime-resolvable-any-input',
     templateUrl: './static-runtime-resolvable-any-input.component.html',
     styleUrls: ['./static-runtime-resolvable-any-input.component.css']
 })
-export class StaticRuntimeResolvableAnyInputComponent extends BaseRuntimeResolvableInput<RuntimeResolvableAnyStaticProperty> implements OnInit {
+export class StaticRuntimeResolvableAnyInputComponent extends BaseRuntimeResolvableSelectionInput<RuntimeResolvableAnyStaticProperty> implements OnInit {
 
     constructor(runtimeResolvableService: RuntimeResolvableService) {
         super(runtimeResolvableService);
@@ -43,7 +43,12 @@ export class StaticRuntimeResolvableAnyInputComponent extends BaseRuntimeResolva
         this.staticProperty.options.find(option => option.elementId === id).selected = true;
     }
 
-    afterOptionsLoaded() {
+    afterOptionsLoaded(staticProperty: RuntimeResolvableAnyStaticProperty) {
+        this.staticProperty.options = staticProperty.options;
+    }
+
+    parse(staticProperty: StaticPropertyUnion): RuntimeResolvableAnyStaticProperty {
+        return staticProperty as RuntimeResolvableAnyStaticProperty;
     }
 
 }
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
index 51251c0..7e419c7 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
@@ -22,8 +22,7 @@ import {
   RuntimeOptionsResponse,
   RuntimeResolvableAnyStaticProperty,
   RuntimeResolvableOneOfStaticProperty,
-  SelectionStaticProperty,
-  SelectionStaticPropertyUnion,
+  RuntimeResolvableTreeInputStaticProperty,
   StaticProperty,
   StaticPropertyUnion
 } from '../../../core-model/gen/streampipes-model';
@@ -33,9 +32,9 @@ import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core';
 import { ConfigurationInfo } from '../../../connect/model/ConfigurationInfo';
 
 @Directive()
-export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyStaticProperty | RuntimeResolvableOneOfStaticProperty>
-    extends AbstractStaticPropertyRenderer<T>
-    implements OnChanges {
+export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyStaticProperty | RuntimeResolvableOneOfStaticProperty | RuntimeResolvableTreeInputStaticProperty>
+  extends AbstractStaticPropertyRenderer<T>
+  implements OnChanges {
 
   @Input()
   completedStaticProperty: ConfigurationInfo;
@@ -49,12 +48,6 @@ export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyS
   }
 
   onInit() {
-    if (this.staticProperty.options.length === 0 && (!this.staticProperty.dependsOn || this.staticProperty.dependsOn.length == 0)) {
-      this.loadOptionsFromRestApi();
-    } else if (this.staticProperty.options.length > 0) {
-      this.showOptions = true;
-    }
-
     if (this.staticProperty.dependsOn && this.staticProperty.dependsOn.length > 0) {
       this.staticProperty.dependsOn.forEach(dp => {
         this.dependentStaticProperties.set(dp, false);
@@ -76,23 +69,31 @@ export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyS
     this.showOptions = false;
     this.loading = true;
     const observable: Observable<RuntimeOptionsResponse> = this.adapterId ?
-        this.runtimeResolvableService.fetchRemoteOptionsForAdapter(resolvableOptionsParameterRequest, this.adapterId) :
-        this.runtimeResolvableService.fetchRemoteOptionsForPipelineElement(resolvableOptionsParameterRequest);
+      this.runtimeResolvableService.fetchRemoteOptionsForAdapter(resolvableOptionsParameterRequest, this.adapterId) :
+      this.runtimeResolvableService.fetchRemoteOptionsForPipelineElement(resolvableOptionsParameterRequest);
     observable.subscribe(msg => {
       const property = StaticProperty.fromDataUnion(msg.staticProperty);
-      if (property instanceof SelectionStaticProperty) {
-      this.staticProperty.options = property.options;
+      if (this.isRuntimeResolvableProperty(property)) {
+        this.afterOptionsLoaded(this.parse(property));
       }
-      this.afterOptionsLoaded();
       this.loading = false;
       this.showOptions = true;
     });
   }
 
+  isRuntimeResolvableProperty(property: StaticPropertyUnion) {
+    return property instanceof RuntimeResolvableAnyStaticProperty ||
+      property instanceof RuntimeResolvableOneOfStaticProperty ||
+      property instanceof RuntimeResolvableTreeInputStaticProperty;
+  }
+
   ngOnChanges(changes: SimpleChanges): void {
     if (changes['completedStaticProperty']) {
       if (this.completedStaticProperty !== undefined) {
-        this.dependentStaticProperties.set(this.completedStaticProperty.staticPropertyInternalName, this.completedStaticProperty.configured);
+        this.dependentStaticProperties.set(
+          this.completedStaticProperty.staticPropertyInternalName,
+          this.completedStaticProperty.configured
+        );
         if (Array.from(this.dependentStaticProperties.values()).every(v => v === true)) {
           this.loadOptionsFromRestApi();
         }
@@ -100,6 +101,8 @@ export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyS
     }
   }
 
-  abstract afterOptionsLoaded();
+  abstract parse(staticProperty: StaticPropertyUnion): T;
+
+  abstract afterOptionsLoaded(staticProperty: T);
 
 }
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-selection-input.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-selection-input.ts
new file mode 100644
index 0000000..72d65d1
--- /dev/null
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-selection-input.ts
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ *
+ */
+
+import {
+  RuntimeResolvableAnyStaticProperty,
+  RuntimeResolvableOneOfStaticProperty
+} from '../../../core-model/gen/streampipes-model';
+import { Directive } from '@angular/core';
+import { BaseRuntimeResolvableInput } from './base-runtime-resolvable-input';
+
+@Directive()
+export abstract class BaseRuntimeResolvableSelectionInput<T extends RuntimeResolvableAnyStaticProperty | RuntimeResolvableOneOfStaticProperty>
+  extends BaseRuntimeResolvableInput<T> {
+
+
+  onInit() {
+    if (this.staticProperty.options.length === 0 && (!this.staticProperty.dependsOn || this.staticProperty.dependsOn.length == 0)) {
+      this.loadOptionsFromRestApi();
+    } else if (this.staticProperty.options.length > 0) {
+      this.showOptions = true;
+    }
+
+    super.onInit();
+  }
+
+  }
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
index bf8bf4f..1fc13dc 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.ts
@@ -17,9 +17,9 @@
  */
 
 import { Component, OnChanges, OnInit } from '@angular/core';
-import { RuntimeResolvableOneOfStaticProperty } from '../../../core-model/gen/streampipes-model';
-import { BaseRuntimeResolvableInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-input';
+import { RuntimeResolvableOneOfStaticProperty, StaticPropertyUnion } from '../../../core-model/gen/streampipes-model';
 import { RuntimeResolvableService } from '../static-runtime-resolvable-input/runtime-resolvable.service';
+import { BaseRuntimeResolvableSelectionInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-selection-input';
 
 @Component({
     selector: 'app-static-runtime-resolvable-oneof-input',
@@ -27,7 +27,7 @@ import { RuntimeResolvableService } from '../static-runtime-resolvable-input/run
     styleUrls: ['./static-runtime-resolvable-oneof-input.component.css']
 })
 export class StaticRuntimeResolvableOneOfInputComponent
-    extends BaseRuntimeResolvableInput<RuntimeResolvableOneOfStaticProperty> implements OnInit, OnChanges {
+    extends BaseRuntimeResolvableSelectionInput<RuntimeResolvableOneOfStaticProperty> implements OnInit, OnChanges {
 
     constructor(runtimeResolvableService: RuntimeResolvableService) {
         super(runtimeResolvableService);
@@ -37,7 +37,8 @@ export class StaticRuntimeResolvableOneOfInputComponent
         super.onInit();
     }
 
-    afterOptionsLoaded() {
+    afterOptionsLoaded(staticProperty: RuntimeResolvableOneOfStaticProperty) {
+        this.staticProperty.options = staticProperty.options;
         if (this.staticProperty.options && this.staticProperty.options.length > 0) {
             this.staticProperty.options[0].selected = true;
         }
@@ -49,4 +50,8 @@ export class StaticRuntimeResolvableOneOfInputComponent
         }
         this.staticProperty.options.find(option => option.elementId === id).selected = true;
     }
+
+    parse(staticProperty: StaticPropertyUnion): RuntimeResolvableOneOfStaticProperty {
+        return staticProperty as RuntimeResolvableOneOfStaticProperty;
+    }
 }
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
new file mode 100644
index 0000000..fb99b64
--- /dev/null
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
@@ -0,0 +1,17 @@
+<!--
+  ~ 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.
+  ~
+  -->
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
new file mode 100644
index 0000000..13cbc4a
--- /dev/null
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ *
+ */
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
new file mode 100644
index 0000000..8dd99c8
--- /dev/null
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { BaseRuntimeResolvableInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-input';
+import {
+  RuntimeResolvableAnyStaticProperty,
+  RuntimeResolvableTreeInputStaticProperty, StaticPropertyUnion
+} from '../../../core-model/gen/streampipes-model';
+import { RuntimeResolvableService } from '../static-runtime-resolvable-input/runtime-resolvable.service';
+
+@Component({
+  selector: 'sp-runtime-resolvable-tree-input',
+  templateUrl: './static-tree-input.component.html',
+  styleUrls: ['./static-tree-input.component.scss']
+})
+export class StaticRuntimeResolvableTreeInputComponent extends BaseRuntimeResolvableInput<RuntimeResolvableTreeInputStaticProperty> implements OnInit {
+
+  constructor(runtimeResolvableService: RuntimeResolvableService) {
+    super(runtimeResolvableService);
+  }
+
+  ngOnInit(): void {
+    if (this.staticProperty.nodes.length === 0 && (!this.staticProperty.dependsOn || this.staticProperty.dependsOn.length === 0)) {
+      this.loadOptionsFromRestApi();
+    } else if (this.staticProperty.nodes.length > 0) {
+      this.showOptions = true;
+    }
+    super.onInit();
+  }
+
+  parse(staticProperty: StaticPropertyUnion): RuntimeResolvableTreeInputStaticProperty {
+    return staticProperty as RuntimeResolvableTreeInputStaticProperty;
+  }
+
+  afterOptionsLoaded(staticProperty: RuntimeResolvableTreeInputStaticProperty) {
+    this.staticProperty = staticProperty;
+    console.log(staticProperty);
+  }
+
+}

[incubator-streampipes] 05/08: [STREAMPIPES-505] Improve runtime-resolvable configuration of adapters, split OPC adapter

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit dbe82cafe380bebf5f6152e6715c44c8b690d8b7
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Mon Jan 10 10:53:49 2022 +0100

    [STREAMPIPES-505] Improve runtime-resolvable configuration of adapters, split OPC adapter
---
 .../worker/management/RuntimeResovable.java        |  25 +-
 .../worker/rest/RuntimeResolvableResource.java     |  17 +-
 .../opcua/MiloOpcUaConfigurationProvider.java      | 100 +++++++
 .../connect/iiot/adapters/opcua/OpcNode.java       |   9 +
 .../connect/iiot/adapters/opcua/OpcUa.java         | 296 +++------------------
 .../connect/iiot/adapters/opcua/OpcUaAdapter.java  |  44 ++-
 .../opcua/configuration/SpOpcUaConfig.java         | 210 +++++++++++++++
 .../opcua/configuration/SpOpcUaConfigBuilder.java  |  99 +++++++
 .../adapters/opcua/utils/OpcUaNodeVariants.java    |   2 +-
 .../iiot/adapters/opcua/utils/OpcUaUtil.java       |  37 ++-
 .../org/apache/streampipes/model/util/Cloner.java  |   2 +
 .../sdk/extractor/AbstractParameterExtractor.java  |  52 +++-
 12 files changed, 568 insertions(+), 325 deletions(-)

diff --git a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/management/RuntimeResovable.java b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/management/RuntimeResovable.java
index a4252ce..fe4b003 100644
--- a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/management/RuntimeResovable.java
+++ b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/management/RuntimeResovable.java
@@ -19,6 +19,7 @@
 package org.apache.streampipes.connect.container.worker.management;
 
 import org.apache.streampipes.connect.adapter.AdapterRegistry;
+import org.apache.streampipes.connect.api.Connector;
 import org.apache.streampipes.connect.api.IAdapter;
 import org.apache.streampipes.connect.api.IFormat;
 import org.apache.streampipes.connect.api.IProtocol;
@@ -42,17 +43,17 @@ public class RuntimeResovable {
         }
     }
 
-     public static ResolvesContainerProvidedOptions getRuntimeResolvableAdapter(String id) throws IllegalArgumentException {
-        id = id.replaceAll("sp:", SP_NS);
-        Map<String, IAdapter> allAdapters = DeclarersSingleton.getInstance().getAllAdaptersMap();
-        Map<String, IProtocol> allProtocols =  DeclarersSingleton.getInstance().getAllProtocolsMap();
-
-        if (allAdapters.containsKey(id)) {
-            return (ResolvesContainerProvidedOptions) allAdapters.get(id);
-        } else if (allProtocols.containsKey(id)) {
-            return (ResolvesContainerProvidedOptions) allProtocols.get(id);
-        } else {
-            throw new IllegalArgumentException("Could not find adapter with id " + id);
-        }
+    public static Connector getAdapterOrProtocol(String id) {
+      id = id.replaceAll("sp:", SP_NS);
+      Map<String, IAdapter> allAdapters = DeclarersSingleton.getInstance().getAllAdaptersMap();
+      Map<String, IProtocol> allProtocols =  DeclarersSingleton.getInstance().getAllProtocolsMap();
+
+      if (allAdapters.containsKey(id)) {
+        return allAdapters.get(id);
+      } else if (allProtocols.containsKey(id)) {
+        return allProtocols.get(id);
+      } else {
+        throw new IllegalArgumentException("Could not find adapter with id " + id);
+      }
     }
 }
diff --git a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
index d9c9c6e..92a81fd 100644
--- a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
+++ b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
@@ -18,9 +18,11 @@
 
 package org.apache.streampipes.connect.container.worker.rest;
 
+import org.apache.streampipes.connect.api.Connector;
 import org.apache.streampipes.connect.container.worker.management.RuntimeResovable;
 import org.apache.streampipes.container.api.ResolvesContainerProvidedOptions;
 import org.apache.streampipes.container.api.RuntimeResolvableRequestHandler;
+import org.apache.streampipes.container.api.SupportsRuntimeConfig;
 import org.apache.streampipes.model.runtime.RuntimeOptionsRequest;
 import org.apache.streampipes.model.runtime.RuntimeOptionsResponse;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
@@ -41,10 +43,17 @@ public class RuntimeResolvableResource extends AbstractSharedRestInterface {
     public Response fetchConfigurations(@PathParam("id") String elementId,
                                         RuntimeOptionsRequest runtimeOptionsRequest) {
 
-        ResolvesContainerProvidedOptions adapterClass =
-                RuntimeResovable.getRuntimeResolvableAdapter(elementId);
-
-        RuntimeOptionsResponse response = new RuntimeResolvableRequestHandler().handleRuntimeResponse(adapterClass, runtimeOptionsRequest);
+        Connector connector = RuntimeResovable.getAdapterOrProtocol(elementId);
+        RuntimeOptionsResponse response;
+        RuntimeResolvableRequestHandler handler = new RuntimeResolvableRequestHandler();
+
+        if (connector instanceof ResolvesContainerProvidedOptions) {
+            response = handler.handleRuntimeResponse((ResolvesContainerProvidedOptions) connector, runtimeOptionsRequest);
+        } else if (connector instanceof SupportsRuntimeConfig) {
+            response = handler.handleRuntimeResponse((SupportsRuntimeConfig) connector, runtimeOptionsRequest);
+        } else {
+            throw new WebApplicationException(javax.ws.rs.core.Response.Status.BAD_REQUEST);
+        }
 
         return ok(response);
     }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/MiloOpcUaConfigurationProvider.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/MiloOpcUaConfigurationProvider.java
new file mode 100644
index 0000000..e4336d8
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/MiloOpcUaConfigurationProvider.java
@@ -0,0 +1,100 @@
+/*
+ * 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.connect.iiot.adapters.opcua;
+
+import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfig;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
+import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.List;
+
+public class MiloOpcUaConfigurationProvider {
+
+  public OpcUaClientConfig makeClientConfig(SpOpcUaConfig spOpcConfig) throws Exception {
+    String opcServerUrl = spOpcConfig.getOpcServerURL();
+    List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(opcServerUrl).get();
+    String host = opcServerUrl.split("://")[1].split(":")[0];
+
+    EndpointDescription tmpEndpoint = endpoints
+            .stream()
+            .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
+            .findFirst()
+            .orElseThrow(() -> new Exception("No endpoint with security policy none"));
+
+    tmpEndpoint = updateEndpointUrl(tmpEndpoint, host);
+    endpoints = Collections.singletonList(tmpEndpoint);
+
+    EndpointDescription endpoint = endpoints
+            .stream()
+            .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
+            .findFirst().orElseThrow(() -> new Exception("no desired endpoints returned"));
+
+    return buildConfig(endpoint, spOpcConfig);
+  }
+
+  private OpcUaClientConfig buildConfig(EndpointDescription endpoint,
+                                        SpOpcUaConfig spOpcConfig) {
+
+    OpcUaClientConfigBuilder builder = defaultBuilder(endpoint);
+    if (!spOpcConfig.isUnauthenticated()) {
+      builder.setIdentityProvider(new UsernameProvider(spOpcConfig.getUsername(), spOpcConfig.getPassword()));
+    }
+    return builder.build();
+  }
+
+  private OpcUaClientConfigBuilder defaultBuilder(EndpointDescription endpoint) {
+    return OpcUaClientConfig.builder()
+            .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
+            .setApplicationUri("urn:eclipse:milo:examples:client")
+            .setEndpoint(endpoint);
+  }
+
+  private EndpointDescription updateEndpointUrl(
+          EndpointDescription original, String hostname) throws URISyntaxException {
+
+    URI uri = new URI(original.getEndpointUrl()).parseServerAuthority();
+
+    String endpointUrl = String.format(
+            "%s://%s:%s%s",
+            uri.getScheme(),
+            hostname,
+            uri.getPort(),
+            uri.getPath()
+    );
+
+    return new EndpointDescription(
+            endpointUrl,
+            original.getServer(),
+            original.getServerCertificate(),
+            original.getSecurityMode(),
+            original.getSecurityPolicyUri(),
+            original.getUserIdentityTokens(),
+            original.getTransportProfileUri(),
+            original.getSecurityLevel()
+    );
+  }
+}
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcNode.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcNode.java
index 3c91249..99c8da6 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcNode.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcNode.java
@@ -31,6 +31,7 @@ public class OpcNode {
     Datatypes type;
     NodeId nodeId;
     int opcUnitId;
+    private boolean readable;
 
     /**
      * Constructor for class OpcNode without an OPC UA unit identifier. <br>
@@ -95,6 +96,14 @@ public class OpcNode {
         return this.opcUnitId !=0;
     }
 
+    public boolean isReadable() {
+        return readable;
+    }
+
+    public void setReadable(boolean readable) {
+        this.readable = readable;
+    }
+
     /**
      * Returns the corresponding QUDT URI if the {@code opcUnitId} is given,
      * otherwise it returns an empty string. <br>
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java
index 8fabc9d..a947421 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java
@@ -19,42 +19,29 @@
 package org.apache.streampipes.connect.iiot.adapters.opcua;
 
 
-import static org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.retrieveDataTypesFromServer;
-import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
-import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
-
 import org.apache.streampipes.connect.api.exception.AdapterException;
-import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil;
-import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.OpcUaLabels;
+import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfig;
 import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaNodeVariants;
 import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaTypes;
-import org.apache.streampipes.model.connect.adapter.AdapterDescription;
-import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 import org.apache.streampipes.sdk.utils.Datatypes;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
-import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
 import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
 import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
-import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
 import org.eclipse.milo.opcua.stack.core.AttributeId;
 import org.eclipse.milo.opcua.stack.core.Identifiers;
 import org.eclipse.milo.opcua.stack.core.UaException;
-import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
 import org.eclipse.milo.opcua.stack.core.types.builtin.*;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.*;
 import org.eclipse.milo.opcua.stack.core.types.structured.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
@@ -62,25 +49,27 @@ import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.retrieveDataTypesFromServer;
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
+
 /***
  * Wrapper class for all OPC UA specific stuff.
  */
 public class OpcUa {
 
-    static Logger LOG = LoggerFactory.getLogger(OpcUa.class);
+    private static final Logger LOG = LoggerFactory.getLogger(OpcUa.class);
 
-    private NodeId originNodeId;
-    private String opcServerURL;
     private OpcUaClient client;
-    private boolean unauthenticated;
-    private Integer pullIntervalMilliSeconds;
-    private String user;
-    private String password;
+    private final SpOpcUaConfig spOpcConfig;
     private List<Map<String, Integer>> unitIDs = new ArrayList<>();
-    private List<String> selectedNodeNames;
 
     private static final AtomicLong clientHandles = new AtomicLong(1L);
 
+    public OpcUa(SpOpcUaConfig config) {
+        this.spOpcConfig = config;
+    }
+
     /***
      *
      * @return current {@link org.eclipse.milo.opcua.sdk.client.OpcUaClient}
@@ -89,193 +78,14 @@ public class OpcUa {
         return this.client;
     }
 
-
-    /**
-     * Constructor for security level {@code None}, OPC server given by url and subscription-based
-     *
-     * @param opcServerURL complete OPC UA server url
-     * @param namespaceIndex namespace index of the given node
-     * @param nodeId node identifier
-     * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
-     * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveOptions(String, StaticPropertyExtractor)}
-     */
-    public OpcUa(String opcServerURL, int namespaceIndex, String nodeId, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
-
-        this.opcServerURL = opcServerURL;
-        this.unauthenticated = true;
-        this.pullIntervalMilliSeconds = pullIntervalMilliSeconds;
-        this.selectedNodeNames = selectedNodeNames;
-
-        if (isInteger(nodeId)) {
-            int integerNodeId = Integer.parseInt(nodeId);
-            this.originNodeId = new NodeId(namespaceIndex, integerNodeId);
-        } else {
-            this.originNodeId = new NodeId(namespaceIndex, nodeId);
-        }
-    }
-
-    /**
-     * Constructor for security level {@code None} and OPC server given by hostname and port number
-     *
-     * @param opcServer OPC UA hostname
-     * @param opcServerPort OPC UA port number
-     * @param namespaceIndex namespace index of the given node
-     * @param nodeId node identifier
-     * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
-     * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveOptions(String, StaticPropertyExtractor)}
-     */
-    public OpcUa(String opcServer, int opcServerPort, int namespaceIndex, String nodeId, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
-        this( opcServer + ":" + opcServerPort, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
-    }
-
-    /**
-     * Constructor for security level {@code Sign} and OPC server given by url
-     *
-     * @param opcServerURL complete OPC UA server url
-     * @param namespaceIndex namespace index of the given node
-     * @param nodeId node identifier
-     * @param username username to authenticate at the OPC UA server
-     * @param password corresponding password to given user name
-     * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
-     * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveOptions(String, StaticPropertyExtractor)}
-     */
-    public OpcUa(String opcServerURL, int namespaceIndex, String nodeId, String username, String password, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
-        this(opcServerURL, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
-        this.unauthenticated = false;
-        this.user = username;
-        this.password = password;
-    }
-
-    /**
-     * Constructor for OPC UA security level {@code Sign} and OPC server given by hostname and port number
-     *
-     * @param opcServer OPC UA hostname
-     * @param opcServerPort OPC UA port number
-     * @param namespaceIndex namespace index of the given node
-     * @param nodeId node identifier
-     * @param username username to authenticate at the OPC UA server
-     * @param password corresponding password to given user name
-     * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
-     * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveOptions(String, StaticPropertyExtractor)}
-     */
-    public OpcUa(String opcServer, int opcServerPort, int namespaceIndex, String nodeId, String username, String password, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
-        this (opcServer, opcServerPort, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
-        this.unauthenticated = false;
-        this.user = username;
-        this.password = password;
-    }
-
-    /**
-     * Creates {@link OpcUa}  instance in accordance with the given {@link org.apache.streampipes.sdk.extractor.StaticPropertyExtractor}.
-     * @param extractor extractor for user inputs
-     * @return {@link OpcUa}  instance based on information from {@code extractor}
-     */
-    public static OpcUa from(StaticPropertyExtractor extractor) {
-
-        String selectedAlternativeConnection = extractor.selectedAlternativeInternalId(OpcUaLabels.OPC_HOST_OR_URL.name());
-        String selectedAlternativeAuthentication = extractor.selectedAlternativeInternalId(OpcUaLabels.ACCESS_MODE.name());
-
-        int namespaceIndex = extractor.singleValueParameter(OpcUaLabels.NAMESPACE_INDEX.name(), int.class);
-        String nodeId = extractor.singleValueParameter(OpcUaLabels.NODE_ID.name(), String.class);
-
-        boolean usePullMode = extractor.selectedAlternativeInternalId(OpcUaLabels.ADAPTER_TYPE.name()).equals(OpcUaLabels.PULL_MODE.name());
-        boolean useURL = selectedAlternativeConnection.equals(OpcUaLabels.OPC_URL.name());
-        boolean unauthenticated =  selectedAlternativeAuthentication.equals(OpcUaLabels.UNAUTHENTICATED.name());
-
-        Integer pullIntervalSeconds = null;
-        if (usePullMode) {
-            pullIntervalSeconds = extractor.singleValueParameter(OpcUaLabels.PULLING_INTERVAL.name(), Integer.class);
-        }
-
-        List<String> selectedNodeNames = extractor.selectedMultiValues(OpcUaLabels.AVAILABLE_NODES.name(), String.class);
-
-        if (useURL && unauthenticated){
-
-            String serverAddress = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_URL.name(), String.class);
-            serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
-
-            return new OpcUa(serverAddress, namespaceIndex, nodeId, pullIntervalSeconds, selectedNodeNames);
-
-        } else if(!useURL && unauthenticated){
-            String serverAddress = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_HOST.name(), String.class);
-            serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
-            int port = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_PORT.name(), int.class);
-
-            return new OpcUa(serverAddress, port, namespaceIndex, nodeId, pullIntervalSeconds, selectedNodeNames);
-        } else {
-
-            String username = extractor.singleValueParameter(OpcUaLabels.USERNAME.name(), String.class);
-            String password = extractor.secretValue(OpcUaLabels.PASSWORD.name());
-
-            if (useURL) {
-                String serverAddress = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_URL.name(), String.class);
-                serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
-
-                return new OpcUa(serverAddress, namespaceIndex, nodeId, username, password, pullIntervalSeconds, selectedNodeNames);
-            } else {
-                String serverAddress = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_HOST.name(), String.class);
-                serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
-                int port = extractor.singleValueParameter(OpcUaLabels.OPC_SERVER_PORT.name(), int.class);
-
-                return new OpcUa(serverAddress, port, namespaceIndex, nodeId, username, password, pullIntervalSeconds, selectedNodeNames);
-            }
-        }
-
-    }
-
-    /***
-     * Creates {@link OpcUa}  instance in accordance with the given {@link org.apache.streampipes.model.connect.adapter.AdapterDescription}
-     * @param adapterDescription description of current adapter
-     * @return {@link OpcUa}  instance based on information from {@code adapterDescription}
-     */
-    public static OpcUa from(AdapterDescription adapterDescription){
-
-        StaticPropertyExtractor extractor =
-                StaticPropertyExtractor.from(adapterDescription.getConfig(), new ArrayList<>());
-
-        return from(extractor);
-    }
-
     /***
      * Establishes appropriate connection to OPC UA endpoint depending on the {@link OpcUa} instance
      *
-     * @throws Exception
+     * @throws Exception An exception occurring during OPC connection
      */
     public void connect() throws Exception {
-
-        List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(this.opcServerURL).get();
-        String host = this.opcServerURL.split("://")[1].split(":")[0];
-
-        EndpointDescription tmpEndpoint = endpoints
-                .stream()
-                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
-                .findFirst()
-                .orElseThrow(() -> new Exception("No endpoint with security policy none"));
-
-        tmpEndpoint = updateEndpointUrl(tmpEndpoint, host);
-        endpoints = Collections.singletonList(tmpEndpoint);
-
-        EndpointDescription endpoint = endpoints
-                .stream()
-                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
-                .findFirst().orElseThrow(() -> new Exception("no desired endpoints returned"));
-
-        OpcUaClientConfig config;
-        if (this.unauthenticated) {
-            config = OpcUaClientConfig.builder()
-                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
-                    .setApplicationUri("urn:eclipse:milo:examples:client")
-                    .setEndpoint(endpoint)
-                    .build();
-        } else {
-            config = OpcUaClientConfig.builder()
-                    .setIdentityProvider(new UsernameProvider(this.user, this.password))
-                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
-                    .setApplicationUri("urn:eclipse:milo:examples:client")
-                    .setEndpoint(endpoint)
-                    .build();
-        }
-        this.client = OpcUaClient.create(config);
+        OpcUaClientConfig clientConfig = new MiloOpcUaConfigurationProvider().makeClientConfig(spOpcConfig);
+        this.client = OpcUaClient.create(clientConfig);
         client.connect().get();
     }
 
@@ -283,38 +93,14 @@ public class OpcUa {
         client.disconnect();
     }
 
-    private EndpointDescription updateEndpointUrl(
-            EndpointDescription original, String hostname) throws URISyntaxException {
-
-        URI uri = new URI(original.getEndpointUrl()).parseServerAuthority();
-
-        String endpointUrl = String.format(
-                "%s://%s:%s%s",
-                uri.getScheme(),
-                hostname,
-                uri.getPort(),
-                uri.getPath()
-        );
-
-        return new EndpointDescription(
-                endpointUrl,
-                original.getServer(),
-                original.getServerCertificate(),
-                original.getSecurityMode(),
-                original.getSecurityPolicyUri(),
-                original.getUserIdentityTokens(),
-                original.getTransportProfileUri(),
-                original.getSecurityLevel()
-        );
-    }
-
     /***
-     * Search for related nodes relative to {@link OpcUa#originNodeId}
-     * @param selectNodes indicates whether only nodes of {@link OpcUa#selectedNodeNames} should be returned
-     * @return List of {@link OpcNode}s related to {@link OpcUa#originNodeId}
+     * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
+     * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
+     * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
      * @throws AdapterException
      */
     public List<OpcNode> browseNode(boolean selectNodes) throws AdapterException {
+        NodeId originNodeId = spOpcConfig.getOriginNodeId();
         List<OpcNode> discoveredNodes = browseNode(originNodeId, selectNodes);
 
         if (discoveredNodes.size() == 0) {
@@ -367,9 +153,9 @@ public class OpcUa {
     }
 
     /***
-     * Search for related nodes relative to {@link OpcUa#originNodeId}
-     * @param selectNodes indicates whether only nodes of {@link OpcUa#selectedNodeNames} should be returned
-     * @return List of {@link OpcNode}s related to {@link OpcUa#originNodeId}
+     * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
+     * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
+     * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
      * @throws AdapterException
      */
     private List<OpcNode> browseNode(NodeId browseRoot, boolean selectNodes) throws AdapterException {
@@ -463,12 +249,13 @@ public class OpcUa {
 
         if (selectNodes) {
             // filter for nodes that were selected by the user during configuration
-            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getLabel()))
+//            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getLabel()))
+//                    .collect(Collectors.toList());
+            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getNodeId().getIdentifier().toString()))
                     .collect(Collectors.toList());
         }
 
         return result;
-
     }
 
 
@@ -484,7 +271,6 @@ public class OpcUa {
          */
         UaSubscription subscription = this.client.getSubscriptionManager().createSubscription(1000.0).get();
 
-
         List<CompletableFuture<DataValue>> values = new ArrayList<>();
 
         for (NodeId node : nodes) {
@@ -540,33 +326,17 @@ public class OpcUa {
                 System.out.println("failed to create item for " + item.getReadValueId().getNodeId() + item.getStatusCode());
             }
         }
-
-    }
-
-    public static boolean isInteger(String s) {
-        try {
-            Integer.parseInt(s);
-        } catch(NumberFormatException | NullPointerException e) {
-            return false;
-        }
-        // only got here if we didn't return false
-        return true;
-    }
-
-
-    public List<String> getSelectedNodeNames() {
-        return selectedNodeNames;
     }
 
-    public String getOpcServerURL() {
-        return opcServerURL;
+    private List<String> getSelectedNodeNames() {
+        return spOpcConfig.getSelectedNodeNames();
     }
 
     public boolean inPullMode() {
-        return !(this.pullIntervalMilliSeconds == null);
+        return !(spOpcConfig.getPullIntervalMilliSeconds() == null);
     }
 
     public int getPullIntervalMilliSeconds() {
-        return this.pullIntervalMilliSeconds;
+        return spOpcConfig.getPullIntervalMilliSeconds();
     }
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
index dee67e9..e885c87 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
@@ -19,17 +19,18 @@
 package org.apache.streampipes.connect.iiot.adapters.opcua;
 
 import org.apache.streampipes.connect.adapter.Adapter;
+import org.apache.streampipes.connect.adapter.util.PollingSettings;
 import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.api.exception.ParseException;
-import org.apache.streampipes.connect.adapter.util.PollingSettings;
 import org.apache.streampipes.connect.iiot.adapters.PullAdapter;
+import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfigBuilder;
 import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil;
 import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.OpcUaLabels;
-import org.apache.streampipes.container.api.ResolvesContainerProvidedOptions;
+import org.apache.streampipes.container.api.SupportsRuntimeConfig;
 import org.apache.streampipes.model.AdapterType;
 import org.apache.streampipes.model.connect.adapter.SpecificAdapterStreamDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
-import org.apache.streampipes.model.staticproperty.Option;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
 import org.apache.streampipes.sdk.StaticProperties;
 import org.apache.streampipes.sdk.builder.adapter.SpecificDataStreamAdapterBuilder;
 import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
@@ -47,7 +48,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
-public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvidedOptions {
+public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
 
     public static final String ID = "org.apache.streampipes.connect.iiot.adapters.opcua";
 
@@ -99,7 +100,7 @@ public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvid
         @Override
     public void startAdapter() throws AdapterException {
 
-        this.opcUa = OpcUa.from(this.adapterDescription);
+        this.opcUa = new OpcUa(SpOpcUaConfigBuilder.from(this.adapterDescription));
 
         if (this.opcUa.inPullMode()) {
             super.startAdapter();
@@ -121,21 +122,20 @@ public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvid
     @Override
     protected void pullData() {
 
-            CompletableFuture<List<DataValue>> response = this.opcUa.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
-            try {
-
-            List<DataValue> returnValues = response.get();
-                for (int i = 0; i<returnValues.size(); i++) {
+        CompletableFuture<List<DataValue>> response = this.opcUa.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
+        try {
+        List<DataValue> returnValues = response.get();
+            for (int i = 0; i<returnValues.size(); i++) {
 
-                    Object value = returnValues.get(i).getValue().getValue();
-                    this.event.put(this.allNodes.get(i).getLabel(), value);
+                Object value = returnValues.get(i).getValue().getValue();
+                this.event.put(this.allNodes.get(i).getLabel(), value);
 
-                }
-             } catch (InterruptedException | ExecutionException ie) {
-                ie.printStackTrace();
-             }
+            }
+         } catch (InterruptedException | ExecutionException ie) {
+            ie.printStackTrace();
+         }
 
-            adapterPipeline.process(this.event);
+        adapterPipeline.process(this.event);
 
     }
 
@@ -201,7 +201,7 @@ public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvid
                 )
                 .requiredTextParameter(Labels.withId(OpcUaLabels.NAMESPACE_INDEX.name()))
                 .requiredTextParameter(Labels.withId(OpcUaLabels.NODE_ID.name()))
-                .requiredMultiValueSelectionFromContainer(
+                .requiredRuntimeResolvableTreeInput(
                         Labels.withId(OpcUaLabels.AVAILABLE_NODES.name()),
                         Arrays.asList(OpcUaLabels.NAMESPACE_INDEX.name(), OpcUaLabels.NODE_ID.name())
                 )
@@ -219,7 +219,6 @@ public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvid
 
     @Override
     public GuessSchema getSchema(SpecificAdapterStreamDescription adapterDescription) throws AdapterException, ParseException {
-
         return OpcUaUtil.getSchema(adapterDescription);
     }
 
@@ -229,10 +228,7 @@ public class OpcUaAdapter extends PullAdapter implements ResolvesContainerProvid
     }
 
     @Override
-    public List<Option> resolveOptions(String requestId, StaticPropertyExtractor parameterExtractor) {
-
-        return OpcUaUtil.resolveOptions(requestId, parameterExtractor);
-
+    public StaticProperty resolveConfiguration(String staticPropertyInternalName, StaticPropertyExtractor extractor) {
+        return OpcUaUtil.resolveConfiguration(staticPropertyInternalName, extractor);
     }
-
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
new file mode 100644
index 0000000..a2a5c1e
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
@@ -0,0 +1,210 @@
+/*
+ * 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.connect.iiot.adapters.opcua.configuration;
+
+import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil;
+import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+
+import java.util.List;
+
+public class SpOpcUaConfig {
+
+  private String opcServerURL;
+  private int namespaceIndex;
+
+  private boolean unauthenticated;
+
+  private String username;
+  private String password;
+
+  private List<String> selectedNodeNames;
+  private Integer pullIntervalMilliSeconds;
+  private NodeId originNodeId;
+
+  public SpOpcUaConfig() {
+  }
+
+  /**
+   * Constructor for security level {@code None}, OPC server given by url and subscription-based
+   *
+   * @param opcServerURL complete OPC UA server url
+   * @param namespaceIndex namespace index of the given node
+   * @param nodeId node identifier
+   * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
+   * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveConfiguration(String, StaticPropertyExtractor)} (String, StaticPropertyExtractor)}
+   */
+  public SpOpcUaConfig(String opcServerURL, int namespaceIndex, String nodeId, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
+
+    this.opcServerURL = opcServerURL;
+    this.unauthenticated = true;
+    this.pullIntervalMilliSeconds = pullIntervalMilliSeconds;
+    this.selectedNodeNames = selectedNodeNames;
+
+    if (isInteger(nodeId)) {
+      int integerNodeId = Integer.parseInt(nodeId);
+      this.originNodeId = new NodeId(namespaceIndex, integerNodeId);
+    } else {
+      this.originNodeId = new NodeId(namespaceIndex, nodeId);
+    }
+  }
+
+  /**
+   * Constructor for security level {@code None} and OPC server given by hostname and port number
+   *
+   * @param opcServer OPC UA hostname
+   * @param opcServerPort OPC UA port number
+   * @param namespaceIndex namespace index of the given node
+   * @param nodeId node identifier
+   * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
+   * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveConfiguration(String, StaticPropertyExtractor)}
+   */
+  public SpOpcUaConfig(String opcServer,
+                       int opcServerPort,
+                       int namespaceIndex,
+                       String nodeId,
+                       int pullIntervalMilliSeconds,
+                       List<String> selectedNodeNames) {
+    this( opcServer + ":" + opcServerPort, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
+  }
+
+  /**
+   * Constructor for security level {@code Sign} and OPC server given by url
+   *
+   * @param opcServerURL complete OPC UA server url
+   * @param namespaceIndex namespace index of the given node
+   * @param nodeId node identifier
+   * @param username username to authenticate at the OPC UA server
+   * @param password corresponding password to given user name
+   * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
+   * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveConfiguration(String, StaticPropertyExtractor)}
+   */
+  public SpOpcUaConfig(String opcServerURL,
+                       int namespaceIndex,
+                       String nodeId,
+                       String username,
+                       String password,
+                       int pullIntervalMilliSeconds,
+                       List<String> selectedNodeNames) {
+    this(opcServerURL, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
+    this.unauthenticated = false;
+    this.username = username;
+    this.password = password;
+  }
+
+  /**
+   * Constructor for OPC UA security level {@code Sign} and OPC server given by hostname and port number
+   *
+   * @param opcServer OPC UA hostname
+   * @param opcServerPort OPC UA port number
+   * @param namespaceIndex namespace index of the given node
+   * @param nodeId node identifier
+   * @param username username to authenticate at the OPC UA server
+   * @param password corresponding password to given user name
+   * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
+   * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveConfiguration(String, StaticPropertyExtractor)}
+   */
+  public SpOpcUaConfig(String opcServer,
+                       int opcServerPort,
+                       int namespaceIndex,
+                       String nodeId,
+                       String username,
+                       String password,
+                       int pullIntervalMilliSeconds,
+                       List<String> selectedNodeNames) {
+    this (opcServer, opcServerPort, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
+    this.unauthenticated = false;
+    this.username = username;
+    this.password = password;
+  }
+
+  public String getOpcServerURL() {
+    return opcServerURL;
+  }
+
+  public void setOpcServerURL(String opcServerURL) {
+    this.opcServerURL = opcServerURL;
+  }
+
+  public boolean isUnauthenticated() {
+    return unauthenticated;
+  }
+
+  public void setUnauthenticated(boolean unauthenticated) {
+    this.unauthenticated = unauthenticated;
+  }
+
+  public int getNamespaceIndex() {
+    return namespaceIndex;
+  }
+
+  public void setNamespaceIndex(int namespaceIndex) {
+    this.namespaceIndex = namespaceIndex;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getPassword() {
+    return password;
+  }
+
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  public List<String> getSelectedNodeNames() {
+    return selectedNodeNames;
+  }
+
+  public void setSelectedNodeNames(List<String> selectedNodeNames) {
+    this.selectedNodeNames = selectedNodeNames;
+  }
+
+  public Integer getPullIntervalMilliSeconds() {
+    return pullIntervalMilliSeconds;
+  }
+
+  public void setPullIntervalMilliSeconds(Integer pullIntervalMilliSeconds) {
+    this.pullIntervalMilliSeconds = pullIntervalMilliSeconds;
+  }
+
+  public NodeId getOriginNodeId() {
+    return originNodeId;
+  }
+
+  public void setOriginNodeId(NodeId originNodeId) {
+    this.originNodeId = originNodeId;
+  }
+
+  public static boolean isInteger(String s) {
+    try {
+      Integer.parseInt(s);
+    } catch(NumberFormatException | NullPointerException e) {
+      return false;
+    }
+    // only got here if we didn't return false
+    return true;
+  }
+}
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfigBuilder.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfigBuilder.java
new file mode 100644
index 0000000..f37d5a1
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfigBuilder.java
@@ -0,0 +1,99 @@
+/*
+ * 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.connect.iiot.adapters.opcua.configuration;
+
+import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SpOpcUaConfigBuilder {
+
+  /**
+   * Creates {@link SpOpcUaConfig}  instance in accordance with the given {@link org.apache.streampipes.sdk.extractor.StaticPropertyExtractor}.
+   * @param extractor extractor for user inputs
+   * @return {@link SpOpcUaConfig}  instance based on information from {@code extractor}
+   */
+  public static SpOpcUaConfig from(StaticPropertyExtractor extractor) {
+
+    String selectedAlternativeConnection = extractor.selectedAlternativeInternalId(OpcUaUtil.OpcUaLabels.OPC_HOST_OR_URL.name());
+    String selectedAlternativeAuthentication = extractor.selectedAlternativeInternalId(OpcUaUtil.OpcUaLabels.ACCESS_MODE.name());
+
+    int namespaceIndex = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.NAMESPACE_INDEX.name(), int.class);
+    String nodeId = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.NODE_ID.name(), String.class);
+
+    boolean usePullMode = extractor.selectedAlternativeInternalId(OpcUaUtil.OpcUaLabels.ADAPTER_TYPE.name()).equals(OpcUaUtil.OpcUaLabels.PULL_MODE.name());
+    boolean useURL = selectedAlternativeConnection.equals(OpcUaUtil.OpcUaLabels.OPC_URL.name());
+    boolean unauthenticated =  selectedAlternativeAuthentication.equals(OpcUaUtil.OpcUaLabels.UNAUTHENTICATED.name());
+
+    Integer pullIntervalSeconds = null;
+    if (usePullMode) {
+      pullIntervalSeconds = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.PULLING_INTERVAL.name(), Integer.class);
+    }
+
+    List<String> selectedNodeNames = extractor.selectedTreeNodesInternalNames(OpcUaUtil.OpcUaLabels.AVAILABLE_NODES.name(), String.class, true);
+
+    if (useURL && unauthenticated){
+
+      String serverAddress = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_URL.name(), String.class);
+      serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
+
+      return new SpOpcUaConfig(serverAddress, namespaceIndex, nodeId, pullIntervalSeconds, selectedNodeNames);
+
+    } else if(!useURL && unauthenticated){
+      String serverAddress = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_HOST.name(), String.class);
+      serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
+      int port = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_PORT.name(), int.class);
+
+      return new SpOpcUaConfig(serverAddress, port, namespaceIndex, nodeId, pullIntervalSeconds, selectedNodeNames);
+    } else {
+
+      String username = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.USERNAME.name(), String.class);
+      String password = extractor.secretValue(OpcUaUtil.OpcUaLabels.PASSWORD.name());
+
+      if (useURL) {
+        String serverAddress = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_URL.name(), String.class);
+        serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
+
+        return new SpOpcUaConfig(serverAddress, namespaceIndex, nodeId, username, password, pullIntervalSeconds, selectedNodeNames);
+      } else {
+        String serverAddress = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_HOST.name(), String.class);
+        serverAddress = OpcUaUtil.formatServerAddress(serverAddress);
+        int port = extractor.singleValueParameter(OpcUaUtil.OpcUaLabels.OPC_SERVER_PORT.name(), int.class);
+
+        return new SpOpcUaConfig(serverAddress, port, namespaceIndex, nodeId, username, password, pullIntervalSeconds, selectedNodeNames);
+      }
+    }
+  }
+
+  /***
+   * Creates {@link SpOpcUaConfig}  instance in accordance with the given {@link org.apache.streampipes.model.connect.adapter.AdapterDescription}
+   * @param adapterDescription description of current adapter
+   * @return {@link SpOpcUaConfig}  instance based on information from {@code adapterDescription}
+   */
+  public static SpOpcUaConfig from(AdapterDescription adapterDescription){
+
+    StaticPropertyExtractor extractor =
+            StaticPropertyExtractor.from(adapterDescription.getConfig(), new ArrayList<>());
+
+    return from(extractor);
+  }
+}
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaNodeVariants.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaNodeVariants.java
index abf0b87..51700c4 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaNodeVariants.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaNodeVariants.java
@@ -29,7 +29,7 @@ public enum OpcUaNodeVariants {
     Property(68),
     EUInformation(887);
 
-    // ID as specific in OPC UA standard
+    // ID as specified in OPC UA standard
     private final int id;
 
     private OpcUaNodeVariants(int id){
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
index ef164ab..a882d74 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
@@ -22,11 +22,13 @@ import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.api.exception.ParseException;
 import org.apache.streampipes.connect.iiot.adapters.opcua.OpcNode;
 import org.apache.streampipes.connect.iiot.adapters.opcua.OpcUa;
+import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfigBuilder;
 import org.apache.streampipes.model.connect.adapter.SpecificAdapterStreamDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
 import org.apache.streampipes.model.schema.EventProperty;
 import org.apache.streampipes.model.schema.EventSchema;
-import org.apache.streampipes.model.staticproperty.Option;
+import org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty;
+import org.apache.streampipes.model.staticproperty.TreeInputNode;
 import org.apache.streampipes.sdk.builder.PrimitivePropertyBuilder;
 import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
@@ -35,7 +37,8 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
 
 import java.net.URI;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
 
 /***
  * Collection of several utility functions in context of OPC UA
@@ -68,7 +71,7 @@ public class OpcUaUtil {
         EventSchema eventSchema = new EventSchema();
         List<EventProperty> allProperties = new ArrayList<>();
 
-        OpcUa opcUa = OpcUa.from(adapterStreamDescription);
+        OpcUa opcUa = new OpcUa(SpOpcUaConfigBuilder.from(adapterStreamDescription));
 
         try {
             opcUa.connect();
@@ -108,28 +111,32 @@ public class OpcUaUtil {
 
     /***
      * OPC UA specific implementation of {@link org.apache.streampipes.container.api.ResolvesContainerProvidedOptions#resolveOptions(String, StaticPropertyExtractor)}.  }
-     * @param requestId
+     * @param internalName The internal name of the Static Property
      * @param parameterExtractor
      * @return {@code List<Option>} with available node names for the given OPC UA configuration
      */
-    public static List<Option> resolveOptions (String requestId, StaticPropertyExtractor parameterExtractor) {
+    public static RuntimeResolvableTreeInputStaticProperty resolveConfiguration (String internalName,
+                                                                                 StaticPropertyExtractor parameterExtractor) {
 
+        RuntimeResolvableTreeInputStaticProperty config = parameterExtractor
+                .getStaticPropertyByName(internalName, RuntimeResolvableTreeInputStaticProperty.class);
         // access mode and host/url have to be selected
         try {
             parameterExtractor.selectedAlternativeInternalId(OpcUaLabels.OPC_HOST_OR_URL.name());
             parameterExtractor.selectedAlternativeInternalId(OpcUaLabels.ACCESS_MODE.name());
         } catch (NullPointerException nullPointerException) {
-            return new ArrayList<>();
+            return config;
         }
 
-        OpcUa opcUa = OpcUa.from(parameterExtractor);
+        OpcUa opcUa = new OpcUa(SpOpcUaConfigBuilder.from(parameterExtractor));
 
-        List<Option> nodeOptions = new ArrayList<>();
+        List<TreeInputNode> nodeOptions = new ArrayList<>();
         try{
             opcUa.connect();
 
             for(OpcNode opcNode: opcUa.browseNode(false)) {
-                nodeOptions.add(new Option(opcNode.getLabel(), opcNode.getNodeId().getIdentifier().toString()));
+                TreeInputNode node = makeTreeInputNode(opcNode);
+                nodeOptions.add(node);
             }
 
             opcUa.disconnect();
@@ -137,7 +144,17 @@ public class OpcUaUtil {
             e.printStackTrace();
         }
 
-        return nodeOptions;
+        config.setNodes(nodeOptions);
+
+        return config;
+    }
+
+    private static TreeInputNode makeTreeInputNode(OpcNode opcNode) {
+        TreeInputNode node = new TreeInputNode();
+        node.setNodeName(opcNode.getLabel());
+        node.setInternalNodeName(opcNode.getNodeId().getIdentifier().toString());
+        node.setDataNode(true);
+        return node;
     }
 
     public static String getRuntimeNameOfNode(NodeId nodeId) {
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/util/Cloner.java b/streampipes-model/src/main/java/org/apache/streampipes/model/util/Cloner.java
index 8c785f6..27891d7 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/util/Cloner.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/util/Cloner.java
@@ -117,6 +117,8 @@ public class Cloner {
       return new ColorPickerStaticProperty((ColorPickerStaticProperty) o);
     } else if (o instanceof SlideToggleStaticProperty) {
       return new SlideToggleStaticProperty((SlideToggleStaticProperty) o);
+    } else if (o instanceof RuntimeResolvableTreeInputStaticProperty) {
+      return new RuntimeResolvableTreeInputStaticProperty((RuntimeResolvableTreeInputStaticProperty) o);
     } else {
       return new StaticPropertyAlternative((StaticPropertyAlternative) o);
     }
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
index 4d50ce7..eedd0a8 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/extractor/AbstractParameterExtractor.java
@@ -102,7 +102,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   public String codeblockValue(String internalName) {
-    return getStaticPropertyByName(internalName,CodeInputStaticProperty.class).getValue();
+    return getStaticPropertyByName(internalName, CodeInputStaticProperty.class).getValue();
   }
 
   public String selectedColor(String internalName) {
@@ -110,8 +110,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   /**
-   * @deprecated
-   * This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
+   * @deprecated This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
    * Use the StreamPipes Client File API instead (e.g., StreamPipesClientResolver.makeStreamPipesClientInstance()).
    **/
   @Deprecated
@@ -120,8 +119,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   /**
-   * @deprecated
-   * This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
+   * @deprecated This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
    * Use the StreamPipes Client File API instead (e.g., StreamPipesClientResolver.makeStreamPipesClientInstance()).
    **/
   public byte[] fileContentsAsByteArray(String internalName) throws IOException {
@@ -129,8 +127,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   /**
-   * @deprecated
-   * This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
+   * @deprecated This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
    * Use the StreamPipes Client File API instead (e.g., StreamPipesClientResolver.makeStreamPipesClientInstance()).
    **/
   public InputStream fileContentsAsStream(String internalName) throws IOException {
@@ -142,8 +139,7 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   /**
-   * @deprecated
-   * This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
+   * @deprecated This won't work after release 0.69.0 as all API requests against the core need to be authenticated.
    * Use the StreamPipes Client File API instead (e.g., StreamPipesClientResolver.makeStreamPipesClientInstance()).
    **/
   public String selectedFileFetchUrl(String internalName) {
@@ -198,8 +194,8 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
   }
 
   public Boolean comparePropertyRuntimeType(EventProperty eventProperty,
-                                                   Datatypes datatype,
-                                                   boolean ignoreListElements) {
+                                            Datatypes datatype,
+                                            boolean ignoreListElements) {
     EventPropertyPrimitive testProperty = null;
     if (eventProperty instanceof EventPropertyList && !ignoreListElements) {
       testProperty = (EventPropertyPrimitive) ((EventPropertyList) eventProperty).getEventProperty();
@@ -240,6 +236,40 @@ public abstract class AbstractParameterExtractor<T extends InvocableStreamPipesE
             .collect(Collectors.toList());
   }
 
+  public <V> List<V> selectedTreeNodesInternalNames(String internalName,
+                                                    Class<V> targetClass,
+                                                    boolean onlyDataNodes) {
+    List<TreeInputNode> allNodes = new ArrayList<>();
+    RuntimeResolvableTreeInputStaticProperty sp = getStaticPropertyByName(internalName, RuntimeResolvableTreeInputStaticProperty.class);
+    if (sp.getNodes().size() > 0) {
+      sp.getNodes().forEach(node -> buildFlatTree(node, allNodes));
+    }
+
+    if (allNodes.size() > 0) {
+      return allNodes
+              .stream()
+              .filter(node -> {
+                if (!onlyDataNodes) {
+                  return true;
+                } else {
+                  return node.isDataNode();
+                }
+              })
+              .filter(TreeInputNode::isSelected)
+              .map(node -> typeParser.parse(node.getInternalNodeName(), targetClass))
+              .collect(Collectors.toList());
+    } else {
+      return new ArrayList<>();
+    }
+  }
+
+  private void buildFlatTree(TreeInputNode parent, List<TreeInputNode> collector) {
+    collector.add(parent);
+    if (parent.hasChildren()) {
+      parent.getChildren().forEach(child -> buildFlatTree(child, collector));
+    }
+  }
+
   public <S extends StaticProperty> S getStaticPropertyByName(String internalName, Class<S>
           spType) {
     return spType.cast(getStaticPropertyByName(internalName));

[incubator-streampipes] 02/08: [hotfix] Add TreeInput to pipeline template visitor

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 2e90b69eb3401e6747e5ad25147280bd75e640eb
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Fri Jan 7 23:39:03 2022 +0100

    [hotfix] Add TreeInput to pipeline template visitor
---
 .../streampipes/manager/template/PipelineElementTemplateVisitor.java | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
index 591bba2..5da9a2b 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/template/PipelineElementTemplateVisitor.java
@@ -166,6 +166,11 @@ public class PipelineElementTemplateVisitor implements StaticPropertyVisitor {
     }
   }
 
+  @Override
+  public void visit(RuntimeResolvableTreeInputStaticProperty treeInputStaticProperty) {
+    // TODO support templates for tree input
+  }
+
   private Object getValue(StaticProperty sp) {
     return ((Map<String, Object>) configs.get(sp.getInternalName())).get("value");
   }

[incubator-streampipes] 04/08: [STREAMPIPES-504] Add UI implementation of tree input

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 27051a59cfd0576141fe0a8f92a5980d004a6c9a
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Sun Jan 9 14:39:24 2022 +0100

    [STREAMPIPES-504] Add UI implementation of tree input
---
 ui/src/app/core-ui/core-ui.module.ts               |  5 ++-
 ...tic-runtime-resolvable-any-input.component.html |  4 +-
 ...c-runtime-resolvable-oneof-input.component.html |  5 ++-
 .../static-tree-input.component.html               | 37 ++++++++++++++++
 .../static-tree-input.component.scss               | 19 ++++++++
 .../static-tree-input.component.ts                 | 50 +++++++++++++++++++---
 6 files changed, 112 insertions(+), 8 deletions(-)

diff --git a/ui/src/app/core-ui/core-ui.module.ts b/ui/src/app/core-ui/core-ui.module.ts
index e8de4ad..6f1feab 100644
--- a/ui/src/app/core-ui/core-ui.module.ts
+++ b/ui/src/app/core-ui/core-ui.module.ts
@@ -84,6 +84,7 @@ import { ObjectPermissionDialogComponent } from './object-permission-dialog/obje
 import { StaticSlideToggleComponent } from './static-properties/static-slide-toggle/static-slide-toggle.component';
 import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 import { StaticRuntimeResolvableTreeInputComponent } from './static-properties/static-runtime-resolvable-tree-input/static-tree-input.component';
+import { MatTreeModule } from "@angular/material/tree";
 
 @NgModule({
   imports: [
@@ -105,9 +106,11 @@ import { StaticRuntimeResolvableTreeInputComponent } from './static-properties/s
     MatSliderModule,
     MatSlideToggleModule,
     MatChipsModule,
+    MatTreeModule,
     PortalModule,
     OverlayModule,
-    QuillModule.forRoot()
+    QuillModule.forRoot(),
+    MatTreeModule
   ],
   declarations: [
     BarchartWidgetComponent,
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
index 3e466cb..211cce1 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-any-input/static-runtime-resolvable-any-input.component.html
@@ -36,7 +36,9 @@
         <span>(waiting for input)</span>
     </div>
     <div fxLayout="column" *ngIf="loading">
-        <mat-spinner [mode]="'indeterminate'" [diameter]="20"></mat-spinner>
+        <mat-spinner color="accent"
+                     [mode]="'indeterminate'"
+                     [diameter]="20"></mat-spinner>
     </div>
 
     <div *ngIf="staticProperty.horizontalRendering">
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
index 8feaaf3..e58f8ad 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-oneof-input/static-runtime-resolvable-oneof-input.component.html
@@ -42,7 +42,10 @@
                 <span>(waiting for input)</span>
             </div>
             <div fxLayout="column" *ngIf="loading">
-                <mat-spinner [mode]="'indeterminate'" [diameter]="20"></mat-spinner>
+                <mat-spinner
+                        color="accent"
+                        [mode]="'indeterminate'"
+                        [diameter]="20"></mat-spinner>
             </div>
         </div>
     </div>
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
index fb99b64..1d3f187 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.html
@@ -15,3 +15,40 @@
   ~ limitations under the License.
   ~
   -->
+
+<div [formGroup]="parentForm" id="formWrapper" fxFlex="100" fxLayout="column">
+    <div>
+        <button mat-button mat-raised-button color="accent" class="small-button"
+                (click)="loadOptionsFromRestApi()"
+                style="margin-right:10px;margin-left:10px;" [disabled]="!showOptions">
+            <span>Reload</span>
+        </button>
+    </div>
+    <mat-tree [dataSource]="dataSource" [treeControl]="treeControl" class="sp-tree">
+        <mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
+            <mat-checkbox color="accent"
+                          [checked]="node.selected"
+                          (change)="toggleNodeSelection(node)">{{node.nodeName}}
+            </mat-checkbox>
+        </mat-tree-node>
+        <mat-nested-tree-node *matTreeNodeDef="let node; when: hasChild">
+            <div class="mat-tree-node">
+                <button mat-icon-button matTreeNodeToggle
+                        [attr.aria-label]="'Toggle ' + node.nodeName">
+                    <mat-icon class="mat-icon-rtl-mirror">
+                        {{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
+                    </mat-icon>
+                </button>
+                <mat-checkbox color="accent"
+                              [checked]="descendantsAllSelected(node)"
+                              [indeterminate]="descendantsPartiallySelected(node)"
+                              (change)="toggleAllNodeSelection(node)">{{node.nodeName}}
+                </mat-checkbox>
+            </div>
+            <div [class.sp-tree-invisible]="!treeControl.isExpanded(node)"
+                 role="group">
+                <ng-container matTreeNodeOutlet></ng-container>
+            </div>
+        </mat-nested-tree-node>
+    </mat-tree>
+</div>
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
index 13cbc4a..3ab79e4 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.scss
@@ -15,3 +15,22 @@
  * limitations under the License.
  *
  */
+
+.sp-tree-invisible {
+  display: none;
+}
+
+.sp-tree ul,
+.sp-tree li {
+  margin-top: 0;
+  margin-bottom: 0;
+  list-style-type: none;
+}
+
+.sp-tree .mat-nested-tree-node div[role=group] {
+  padding-left: 40px;
+}
+
+.sp-tree div[role=group] > .mat-tree-node {
+  padding-left: 40px;
+}
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
index 8dd99c8..0686093 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-tree-input/static-tree-input.component.ts
@@ -19,26 +19,39 @@
 import { Component, OnInit } from '@angular/core';
 import { BaseRuntimeResolvableInput } from '../static-runtime-resolvable-input/base-runtime-resolvable-input';
 import {
-  RuntimeResolvableAnyStaticProperty,
-  RuntimeResolvableTreeInputStaticProperty, StaticPropertyUnion
+  RuntimeResolvableTreeInputStaticProperty,
+  StaticPropertyUnion,
+  TreeInputNode
 } from '../../../core-model/gen/streampipes-model';
 import { RuntimeResolvableService } from '../static-runtime-resolvable-input/runtime-resolvable.service';
+import { NestedTreeControl } from '@angular/cdk/tree';
+import { MatTreeNestedDataSource } from '@angular/material/tree';
 
 @Component({
   selector: 'sp-runtime-resolvable-tree-input',
   templateUrl: './static-tree-input.component.html',
   styleUrls: ['./static-tree-input.component.scss']
 })
-export class StaticRuntimeResolvableTreeInputComponent extends BaseRuntimeResolvableInput<RuntimeResolvableTreeInputStaticProperty> implements OnInit {
+export class StaticRuntimeResolvableTreeInputComponent
+  extends BaseRuntimeResolvableInput<RuntimeResolvableTreeInputStaticProperty> implements OnInit {
+
+  treeControl = new NestedTreeControl<TreeInputNode>(node => node.children);
+  dataSource = new MatTreeNestedDataSource<TreeInputNode>();
 
   constructor(runtimeResolvableService: RuntimeResolvableService) {
     super(runtimeResolvableService);
   }
 
+  hasChild = (_: number, node: TreeInputNode) => !!node.children && node.children.length > 0;
+
   ngOnInit(): void {
+    this.treeControl = new NestedTreeControl<TreeInputNode>(node => node.children);
+    this.dataSource = new MatTreeNestedDataSource<TreeInputNode>();
+
     if (this.staticProperty.nodes.length === 0 && (!this.staticProperty.dependsOn || this.staticProperty.dependsOn.length === 0)) {
       this.loadOptionsFromRestApi();
     } else if (this.staticProperty.nodes.length > 0) {
+      this.dataSource.data = this.staticProperty.nodes;
       this.showOptions = true;
     }
     super.onInit();
@@ -49,8 +62,35 @@ export class StaticRuntimeResolvableTreeInputComponent extends BaseRuntimeResolv
   }
 
   afterOptionsLoaded(staticProperty: RuntimeResolvableTreeInputStaticProperty) {
-    this.staticProperty = staticProperty;
-    console.log(staticProperty);
+    this.staticProperty.nodes = staticProperty.nodes;
+    this.dataSource.data = this.staticProperty.nodes;
+  }
+
+  toggleNodeSelection(node: TreeInputNode) {
+    node.selected = !node.selected;
+  }
+
+  toggleAllNodeSelection(node: any) {
+    const descendants = this.treeControl.getDescendants(node);
+    const newState = !node.selected;
+    node.selected = newState;
+    descendants.forEach(d => d.selected = newState);
+  }
+
+  descendantsAllSelected(node: TreeInputNode) {
+    const descendants = this.treeControl.getDescendants(node);
+    const allSelected = descendants.length > 0 &&
+      descendants.every(child => {
+        return child.selected;
+      });
+    node.selected = allSelected;
+    return allSelected;
+  }
+
+  descendantsPartiallySelected(node: TreeInputNode) {
+    const descendants = this.treeControl.getDescendants(node);
+    const result = descendants.some(child => child.selected);
+    return result && !this.descendantsAllSelected(node);
   }
 
 }

[incubator-streampipes] 01/08: [STREAMPIPES-504] Refactor runtime-resolvable configs, add initial TreeInputStaticProperty

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 7884f293bc6e1c21fd6dabe3d785aea4cb91dd14
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Fri Jan 7 23:38:39 2022 +0100

    [STREAMPIPES-504] Refactor runtime-resolvable configs, add initial TreeInputStaticProperty
---
 .../worker/rest/RuntimeResolvableResource.java     | 13 +---
 .../api/InvocablePipelineElementResource.java      | 33 +++++-----
 .../api/ResolvesContainerProvidedOptions.java      |  7 +-
 .../api/RuntimeResolvableRequestHandler.java       | 50 +++++++++++++++
 .../container/api/SupportsRuntimeConfig.java       | 32 ++-------
 .../model/runtime/RuntimeOptionsResponse.java      | 21 +++---
 .../RuntimeResolvableTreeInputStaticProperty.java  | 65 +++++++++++++++++++
 .../staticproperty/SlideToggleStaticProperty.java  |  2 +-
 .../model/staticproperty/StaticProperty.java       |  1 +
 .../model/staticproperty/StaticPropertyType.java   |  6 +-
 .../staticproperty/StaticPropertyVisitor.java      |  2 +
 .../model/staticproperty/TreeInputNode.java        | 75 ++++++++++++++++++++++
 .../resource/management/secret/SecretVisitor.java  |  5 ++
 .../apache/streampipes/sdk/StaticProperties.java   | 12 ++++
 ...AbstractConfigurablePipelineElementBuilder.java |  6 ++
 ui/src/app/core-model/gen/streampipes-model.ts     | 68 ++++++++++++++++----
 .../base-runtime-resolvable-input.ts               | 11 +++-
 17 files changed, 324 insertions(+), 85 deletions(-)

diff --git a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
index dd8417f..d9c9c6e 100644
--- a/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
+++ b/streampipes-connect-container-worker/src/main/java/org/apache/streampipes/connect/container/worker/rest/RuntimeResolvableResource.java
@@ -20,17 +20,15 @@ package org.apache.streampipes.connect.container.worker.rest;
 
 import org.apache.streampipes.connect.container.worker.management.RuntimeResovable;
 import org.apache.streampipes.container.api.ResolvesContainerProvidedOptions;
+import org.apache.streampipes.container.api.RuntimeResolvableRequestHandler;
 import org.apache.streampipes.model.runtime.RuntimeOptionsRequest;
 import org.apache.streampipes.model.runtime.RuntimeOptionsResponse;
-import org.apache.streampipes.model.staticproperty.Option;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
 import org.apache.streampipes.rest.shared.impl.AbstractSharedRestInterface;
-import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import java.util.List;
 
 @Path("/api/v1/worker/resolvable")
 public class RuntimeResolvableResource extends AbstractSharedRestInterface {
@@ -46,14 +44,9 @@ public class RuntimeResolvableResource extends AbstractSharedRestInterface {
         ResolvesContainerProvidedOptions adapterClass =
                 RuntimeResovable.getRuntimeResolvableAdapter(elementId);
 
-        List<Option> availableOptions =
-                adapterClass.resolveOptions(runtimeOptionsRequest.getRequestId(),
-                        StaticPropertyExtractor.from(runtimeOptionsRequest.getStaticProperties(),
-                                runtimeOptionsRequest.getInputStreams(),
-                                runtimeOptionsRequest.getAppId()));
+        RuntimeOptionsResponse response = new RuntimeResolvableRequestHandler().handleRuntimeResponse(adapterClass, runtimeOptionsRequest);
 
-        return ok(new RuntimeOptionsResponse(runtimeOptionsRequest,
-                availableOptions));
+        return ok(response);
     }
 
 }
diff --git a/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java b/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
index 0cab11b..9936bd2 100644
--- a/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/api/InvocablePipelineElementResource.java
@@ -28,16 +28,13 @@ import org.apache.streampipes.model.Response;
 import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
 import org.apache.streampipes.model.runtime.RuntimeOptionsRequest;
 import org.apache.streampipes.model.runtime.RuntimeOptionsResponse;
-import org.apache.streampipes.model.staticproperty.Option;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
 import org.apache.streampipes.sdk.extractor.AbstractParameterExtractor;
-import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
-import java.util.List;
 import java.util.Map;
 
 public abstract class InvocablePipelineElementResource<I extends InvocableStreamPipesEntity, D extends Declarer<?>,
@@ -60,7 +57,8 @@ public abstract class InvocablePipelineElementResource<I extends InvocableStream
   @Produces(MediaType.APPLICATION_JSON)
   @Consumes(MediaType.APPLICATION_JSON)
   @JacksonSerialized
-  public javax.ws.rs.core.Response invokeRuntime(@PathParam("elementId") String elementId, I graph) {
+  public javax.ws.rs.core.Response invokeRuntime(@PathParam("elementId") String elementId,
+                                                 I graph) {
 
     try {
       if (isDebug()) {
@@ -103,19 +101,20 @@ public abstract class InvocablePipelineElementResource<I extends InvocableStream
   @Consumes(MediaType.APPLICATION_JSON)
   @JacksonSerialized
   public javax.ws.rs.core.Response fetchConfigurations(@PathParam("elementId") String elementId,
-                                                       RuntimeOptionsRequest runtimeOptionsRequest) {
-
-    ResolvesContainerProvidedOptions resolvesOptions = (ResolvesContainerProvidedOptions) getDeclarerById(elementId);
-
-    List<Option> availableOptions =
-            resolvesOptions.resolveOptions(runtimeOptionsRequest.getRequestId(),
-                    StaticPropertyExtractor.from(
-                            runtimeOptionsRequest.getStaticProperties(),
-                            runtimeOptionsRequest.getInputStreams(),
-                            runtimeOptionsRequest.getAppId()
-                    ));
-
-    return ok(new RuntimeOptionsResponse(runtimeOptionsRequest, availableOptions));
+                                                       RuntimeOptionsRequest req) {
+
+    D declarer = getDeclarerById(elementId);
+    RuntimeOptionsResponse responseOptions;
+
+    if (declarer instanceof ResolvesContainerProvidedOptions) {
+      responseOptions = new RuntimeResolvableRequestHandler().handleRuntimeResponse((ResolvesContainerProvidedOptions) declarer, req);
+      return ok(responseOptions);
+    } else if (declarer instanceof SupportsRuntimeConfig) {
+      responseOptions = new RuntimeResolvableRequestHandler().handleRuntimeResponse((SupportsRuntimeConfig) declarer, req);
+      return ok(responseOptions);
+    } else {
+      throw new WebApplicationException(javax.ws.rs.core.Response.Status.BAD_REQUEST);
+    }
   }
 
   @POST
diff --git a/streampipes-container/src/main/java/org/apache/streampipes/container/api/ResolvesContainerProvidedOptions.java b/streampipes-container/src/main/java/org/apache/streampipes/container/api/ResolvesContainerProvidedOptions.java
index ce2211f..a32a1de 100644
--- a/streampipes-container/src/main/java/org/apache/streampipes/container/api/ResolvesContainerProvidedOptions.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/api/ResolvesContainerProvidedOptions.java
@@ -22,7 +22,12 @@ import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 
 import java.util.List;
 
+/**
+ * deprecated: use {@link SupportsRuntimeConfig} instead
+ */
+@Deprecated
 public interface ResolvesContainerProvidedOptions {
 
-  List<Option> resolveOptions(String requestId, StaticPropertyExtractor parameterExtractor);
+  List<Option> resolveOptions(String staticPropertyInternalName,
+                              StaticPropertyExtractor parameterExtractor);
 }
diff --git a/streampipes-container/src/main/java/org/apache/streampipes/container/api/RuntimeResolvableRequestHandler.java b/streampipes-container/src/main/java/org/apache/streampipes/container/api/RuntimeResolvableRequestHandler.java
new file mode 100644
index 0000000..4826caa
--- /dev/null
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/api/RuntimeResolvableRequestHandler.java
@@ -0,0 +1,50 @@
+package org.apache.streampipes.container.api;
+
+import org.apache.streampipes.model.runtime.RuntimeOptionsRequest;
+import org.apache.streampipes.model.runtime.RuntimeOptionsResponse;
+import org.apache.streampipes.model.staticproperty.Option;
+import org.apache.streampipes.model.staticproperty.SelectionStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
+
+import java.util.List;
+
+public class RuntimeResolvableRequestHandler {
+
+  // for backwards compatibility
+  public RuntimeOptionsResponse handleRuntimeResponse(ResolvesContainerProvidedOptions resolvesOptions,
+                                                      RuntimeOptionsRequest req) {
+    List<Option> availableOptions =
+            resolvesOptions.resolveOptions(req.getRequestId(),
+                    makeExtractor(req));
+
+    SelectionStaticProperty sp = getConfiguredProperty(req);
+    sp.setOptions(availableOptions);
+
+    return new RuntimeOptionsResponse(req, sp);
+  }
+
+  public RuntimeOptionsResponse handleRuntimeResponse(SupportsRuntimeConfig declarer,
+                                                      RuntimeOptionsRequest req) {
+    StaticProperty result = declarer.resolveConfiguration(
+            req.getRequestId(),
+            makeExtractor(req));
+
+    return new RuntimeOptionsResponse(req, result);
+  }
+
+  private SelectionStaticProperty getConfiguredProperty(RuntimeOptionsRequest req) {
+    return req.getStaticProperties()
+            .stream()
+            .filter(p -> p.getInternalName().equals(req.getRequestId()))
+            .map(p -> (SelectionStaticProperty) p)
+            .findFirst()
+            .get();
+  }
+
+  private StaticPropertyExtractor makeExtractor(RuntimeOptionsRequest req) {
+    return StaticPropertyExtractor.from(req.getStaticProperties(),
+            req.getInputStreams(),
+            req.getAppId());
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptions.java b/streampipes-container/src/main/java/org/apache/streampipes/container/api/SupportsRuntimeConfig.java
similarity index 56%
rename from streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptions.java
rename to streampipes-container/src/main/java/org/apache/streampipes/container/api/SupportsRuntimeConfig.java
index 1742379..10cacd1 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptions.java
+++ b/streampipes-container/src/main/java/org/apache/streampipes/container/api/SupportsRuntimeConfig.java
@@ -15,35 +15,15 @@
  * limitations under the License.
  *
  */
-package org.apache.streampipes.model.runtime;
 
-public class RuntimeOptions {
+package org.apache.streampipes.container.api;
 
-  private String label;
-  private String additionalPayload;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 
-  public RuntimeOptions(String label, String additionalPayload) {
-    this.label = label;
-    this.additionalPayload = additionalPayload;
-  }
+public interface SupportsRuntimeConfig {
 
-  public RuntimeOptions() {
+  StaticProperty resolveConfiguration(String staticPropertyInternalName,
+                                      StaticPropertyExtractor extractor);
 
-  }
-
-  public String getLabel() {
-    return label;
-  }
-
-  public void setLabel(String label) {
-    this.label = label;
-  }
-
-  public String getAdditionalPayload() {
-    return additionalPayload;
-  }
-
-  public void setAdditionalPayload(String additionalPayload) {
-    this.additionalPayload = additionalPayload;
-  }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptionsResponse.java b/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptionsResponse.java
index b56d02b..7467e5c 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptionsResponse.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/runtime/RuntimeOptionsResponse.java
@@ -18,32 +18,29 @@
 package org.apache.streampipes.model.runtime;
 
 import org.apache.streampipes.model.shared.annotation.TsModel;
-import org.apache.streampipes.model.staticproperty.Option;
-
-import java.util.ArrayList;
-import java.util.List;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
 
 @TsModel
 public class RuntimeOptionsResponse extends RuntimeOptionsRequest {
 
-  private List<Option> options;
+  private StaticProperty staticProperty;
 
   public RuntimeOptionsResponse() {
     super();
-    this.options = new ArrayList<>();
   }
 
-  public RuntimeOptionsResponse(RuntimeOptionsRequest request, List<Option> options) {
+  public RuntimeOptionsResponse(RuntimeOptionsRequest request,
+                                StaticProperty staticProperty) {
     super();
     this.requestId = request.getRequestId();
-    this.options = options;
+    this.staticProperty = staticProperty;
   }
 
-  public List<Option> getOptions() {
-    return options;
+  public StaticProperty getStaticProperty() {
+    return staticProperty;
   }
 
-  public void setOptions(List<Option> options) {
-    this.options = options;
+  public void setStaticProperty(StaticProperty staticProperty) {
+    this.staticProperty = staticProperty;
   }
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java
new file mode 100644
index 0000000..08381c3
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/RuntimeResolvableTreeInputStaticProperty.java
@@ -0,0 +1,65 @@
+/*
+ * 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.model.staticproperty;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class RuntimeResolvableTreeInputStaticProperty extends StaticProperty {
+
+  private List<String> dependsOn;
+  private List<TreeInputNode> nodes;
+
+  public RuntimeResolvableTreeInputStaticProperty() {
+    super(StaticPropertyType.RuntimeResolvableTreeInputStaticProperty);
+  }
+
+  public RuntimeResolvableTreeInputStaticProperty(String internalName,
+                                                  String label,
+                                                  String description) {
+    super(StaticPropertyType.RuntimeResolvableTreeInputStaticProperty, internalName, label, description);
+  }
+
+  public RuntimeResolvableTreeInputStaticProperty(RuntimeResolvableTreeInputStaticProperty other) {
+    super(other);
+    this.dependsOn = other.getDependsOn();
+    this.nodes = other.getNodes().stream().map(TreeInputNode::new).collect(Collectors.toList());
+  }
+
+  @Override
+  public void accept(StaticPropertyVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  public List<TreeInputNode> getNodes() {
+    return nodes;
+  }
+
+  public void setNodes(List<TreeInputNode> nodes) {
+    this.nodes = nodes;
+  }
+
+  public List<String> getDependsOn() {
+    return dependsOn;
+  }
+
+  public void setDependsOn(List<String> dependsOn) {
+    this.dependsOn = dependsOn;
+  }
+}
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/SlideToggleStaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/SlideToggleStaticProperty.java
index 89c1644..fd6f5df 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/SlideToggleStaticProperty.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/SlideToggleStaticProperty.java
@@ -24,7 +24,7 @@ public class SlideToggleStaticProperty extends StaticProperty {
   private boolean defaultValue;
 
   public SlideToggleStaticProperty() {
-    super();
+    super(StaticPropertyType.SlideToggleStaticProperty);
   }
 
   public SlideToggleStaticProperty(String internalName,
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
index e4584a6..ced85ad 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticProperty.java
@@ -36,6 +36,7 @@ import org.apache.streampipes.model.shared.annotation.TsModel;
         @JsonSubTypes.Type(OneOfStaticProperty.class),
         @JsonSubTypes.Type(RuntimeResolvableAnyStaticProperty.class),
         @JsonSubTypes.Type(RuntimeResolvableOneOfStaticProperty.class),
+        @JsonSubTypes.Type(RuntimeResolvableTreeInputStaticProperty.class),
         @JsonSubTypes.Type(SecretStaticProperty.class),
         @JsonSubTypes.Type(StaticPropertyAlternative.class),
         @JsonSubTypes.Type(StaticPropertyAlternatives.class),
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyType.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyType.java
index 0b3247b..453f51a 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyType.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyType.java
@@ -20,6 +20,7 @@ package org.apache.streampipes.model.staticproperty;
 
 public enum StaticPropertyType {
     AnyStaticProperty,
+    CodeInputStaticProperty,
     CollectionStaticProperty,
     ColorPickerStaticProperty,
     DomainStaticProperty,
@@ -31,11 +32,10 @@ public enum StaticPropertyType {
     OneOfStaticProperty,
     RuntimeResolvableAnyStaticProperty,
     RuntimeResolvableOneOfStaticProperty,
+    RuntimeResolvableTreeInputStaticProperty,
     StaticPropertyGroup,
     StaticPropertyAlternatives,
     StaticPropertyAlternative,
     SecretStaticProperty,
-    SlideToggleStaticProperty,
-    CodeInputStaticProperty;
-
+    SlideToggleStaticProperty
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyVisitor.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyVisitor.java
index 3de8909..2505627 100644
--- a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyVisitor.java
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/StaticPropertyVisitor.java
@@ -52,4 +52,6 @@ public interface StaticPropertyVisitor {
   void visit(RemoteOneOfStaticProperty remoteOneOfStaticProperty);
 
   void visit(SlideToggleStaticProperty slideToggleStaticProperty);
+
+  void visit(RuntimeResolvableTreeInputStaticProperty treeInputStaticProperty);
 }
diff --git a/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/TreeInputNode.java b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/TreeInputNode.java
new file mode 100644
index 0000000..c2f3dd8
--- /dev/null
+++ b/streampipes-model/src/main/java/org/apache/streampipes/model/staticproperty/TreeInputNode.java
@@ -0,0 +1,75 @@
+package org.apache.streampipes.model.staticproperty;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class TreeInputNode {
+
+  private boolean dataNode;
+  private boolean selected;
+  private String nodeName;
+  private String internalNodeName;
+
+  List<TreeInputNode> children;
+
+  public TreeInputNode() {
+    this.children = new ArrayList<>();
+  }
+
+  public TreeInputNode(TreeInputNode other) {
+    this.dataNode = other.isDataNode();
+    this.selected = other.isSelected();
+    this.nodeName = other.getNodeName();
+    this.internalNodeName = other.getInternalNodeName();
+    this.children = other.getChildren().stream().map(TreeInputNode::new).collect(Collectors.toList());
+  }
+
+  public boolean hasChildren() {
+    return children.size() > 0;
+  }
+
+  public List<TreeInputNode> getChildren() {
+    return children;
+  }
+
+  public void setChildren(List<TreeInputNode> children) {
+    this.children = children;
+  }
+
+  public boolean isSelected() {
+    return selected;
+  }
+
+  public void setSelected(boolean selected) {
+    this.selected = selected;
+  }
+
+  public String getNodeName() {
+    return nodeName;
+  }
+
+  public void setNodeName(String nodeName) {
+    this.nodeName = nodeName;
+  }
+
+  public String getInternalNodeName() {
+    return internalNodeName;
+  }
+
+  public void setInternalNodeName(String internalNodeName) {
+    this.internalNodeName = internalNodeName;
+  }
+
+  public boolean isDataNode() {
+    return dataNode;
+  }
+
+  public void setDataNode(boolean dataNode) {
+    this.dataNode = dataNode;
+  }
+
+  public void addChild(TreeInputNode node) {
+    this.children.add(node);
+  }
+}
diff --git a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/secret/SecretVisitor.java b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/secret/SecretVisitor.java
index 478fb62..36e36eb 100644
--- a/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/secret/SecretVisitor.java
+++ b/streampipes-resource-management/src/main/java/org/apache/streampipes/resource/management/secret/SecretVisitor.java
@@ -117,4 +117,9 @@ public class SecretVisitor implements StaticPropertyVisitor {
   public void visit(SlideToggleStaticProperty slideToggleStaticProperty) {
     // Do nothing
   }
+
+  @Override
+  public void visit(RuntimeResolvableTreeInputStaticProperty treeInputStaticProperty) {
+    // Do nothing
+  }
 }
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
index e074de2..efacba4 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/StaticProperties.java
@@ -110,6 +110,18 @@ public class StaticProperties {
     return rsp;
   }
 
+  public static RuntimeResolvableTreeInputStaticProperty runtimeResolvableTreeInput(Label label,
+                                                                                    List<String> dependsOn) {
+    RuntimeResolvableTreeInputStaticProperty treeInput = new RuntimeResolvableTreeInputStaticProperty(
+            label.getInternalId(),
+            label.getLabel(),
+            label.getDescription());
+
+    treeInput.setDependsOn(dependsOn);
+
+    return treeInput;
+  }
+
   public static StaticProperty integerFreeTextProperty(Label label,
                                                        PropertyValueSpecification propertyValueSpecification) {
     FreeTextStaticProperty fsp = integerFreeTextProperty(label);
diff --git a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractConfigurablePipelineElementBuilder.java b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractConfigurablePipelineElementBuilder.java
index 8310d20..af6df4e 100644
--- a/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractConfigurablePipelineElementBuilder.java
+++ b/streampipes-sdk/src/main/java/org/apache/streampipes/sdk/builder/AbstractConfigurablePipelineElementBuilder.java
@@ -864,6 +864,12 @@ public abstract class AbstractConfigurablePipelineElementBuilder<BU extends
     return me();
   }
 
+  public BU requiredRuntimeResolvableTreeInput(Label label,
+                                               List<String> dependsOn) {
+    this.staticProperties.add(StaticProperties.runtimeResolvableTreeInput(label, dependsOn));
+    return me();
+  }
+
   /**
    * Defines a collection of configuration parameters of the specified staticProperties.
    * The developer can fill the staticProperties multiply times.
diff --git a/ui/src/app/core-model/gen/streampipes-model.ts b/ui/src/app/core-model/gen/streampipes-model.ts
index f2eb41d..3c66804 100644
--- a/ui/src/app/core-model/gen/streampipes-model.ts
+++ b/ui/src/app/core-model/gen/streampipes-model.ts
@@ -18,10 +18,10 @@
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2022-01-06 22:53:01.
+// Generated using typescript-generator version 2.27.744 on 2022-01-07 22:58:50.
 
 export class AbstractStreamPipesEntity {
-  "@class": "org.apache.streampipes.model.base.AbstractStreamPipesEntity" | "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStream [...]
+  "@class": "org.apache.streampipes.model.base.AbstractStreamPipesEntity" | "org.apache.streampipes.model.base.NamedStreamPipesEntity" | "org.apache.streampipes.model.connect.adapter.AdapterDescription" | "org.apache.streampipes.model.connect.adapter.AdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.GenericAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.SpecificAdapterSetDescription" | "org.apache.streampipes.model.connect.adapter.AdapterStream [...]
   elementId: string;
 
   static fromData(data: AbstractStreamPipesEntity, target?: AbstractStreamPipesEntity): AbstractStreamPipesEntity {
@@ -36,7 +36,7 @@ export class AbstractStreamPipesEntity {
 }
 
 export class UnnamedStreamPipesEntity extends AbstractStreamPipesEntity {
-  "@class": "org.apache.streampipes.model.base.UnnamedStreamPipesEntity" | "org.apache.streampipes.model.connect.guess.GuessSchema" | "org.apache.streampipes.model.connect.rules.TransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddValueTransformationRuleDescription" | "org.apache.streampip [...]
+  "@class": "org.apache.streampipes.model.base.UnnamedStreamPipesEntity" | "org.apache.streampipes.model.connect.guess.GuessSchema" | "org.apache.streampipes.model.connect.rules.TransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.ValueTransformationRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddTimestampRuleDescription" | "org.apache.streampipes.model.connect.rules.value.AddValueTransformationRuleDescription" | "org.apache.streampip [...]
 
   static fromData(data: UnnamedStreamPipesEntity, target?: UnnamedStreamPipesEntity): UnnamedStreamPipesEntity {
     if (!data) {
@@ -193,8 +193,8 @@ export class AdapterDescription extends NamedStreamPipesEntity {
     instance.correspondingServiceGroup = data.correspondingServiceGroup;
     instance.correspondingDataStreamElementId = data.correspondingDataStreamElementId;
     instance.streamRules = __getCopyArrayFn(__identity<any>())(data.streamRules);
-    instance.valueRules = __getCopyArrayFn(__identity<any>())(data.valueRules);
     instance.schemaRules = __getCopyArrayFn(__identity<any>())(data.schemaRules);
+    instance.valueRules = __getCopyArrayFn(__identity<any>())(data.valueRules);
     return instance;
   }
 
@@ -395,7 +395,7 @@ export class AddValueTransformationRuleDescription extends ValueTransformationRu
 }
 
 export class StaticProperty extends UnnamedStreamPipesEntity {
-  "@class": "org.apache.streampipes.model.staticproperty.StaticProperty" | "org.apache.streampipes.model.staticproperty.CodeInputStaticProperty" | "org.apache.streampipes.model.staticproperty.CollectionStaticProperty" | "org.apache.streampipes.model.staticproperty.ColorPickerStaticProperty" | "org.apache.streampipes.model.staticproperty.DomainStaticProperty" | "org.apache.streampipes.model.staticproperty.FileStaticProperty" | "org.apache.streampipes.model.staticproperty.FreeTextStaticPro [...]
+  "@class": "org.apache.streampipes.model.staticproperty.StaticProperty" | "org.apache.streampipes.model.staticproperty.CodeInputStaticProperty" | "org.apache.streampipes.model.staticproperty.CollectionStaticProperty" | "org.apache.streampipes.model.staticproperty.ColorPickerStaticProperty" | "org.apache.streampipes.model.staticproperty.DomainStaticProperty" | "org.apache.streampipes.model.staticproperty.FileStaticProperty" | "org.apache.streampipes.model.staticproperty.FreeTextStaticPro [...]
   description: string;
   index: number;
   internalName: string;
@@ -451,6 +451,8 @@ export class StaticProperty extends UnnamedStreamPipesEntity {
         return RuntimeResolvableAnyStaticProperty.fromData(data);
       case "org.apache.streampipes.model.staticproperty.RuntimeResolvableOneOfStaticProperty":
         return RuntimeResolvableOneOfStaticProperty.fromData(data);
+      case "org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty":
+        return RuntimeResolvableTreeInputStaticProperty.fromData(data);
       case "org.apache.streampipes.model.staticproperty.SecretStaticProperty":
         return SecretStaticProperty.fromData(data);
       case "org.apache.streampipes.model.staticproperty.StaticPropertyAlternative":
@@ -1649,9 +1651,9 @@ export class GenericAdapterSetDescription extends AdapterSetDescription implemen
     }
     const instance = target || new GenericAdapterSetDescription();
     super.fromData(data, instance);
+    instance.eventSchema = EventSchema.fromData(data.eventSchema);
     instance.formatDescription = FormatDescription.fromData(data.formatDescription);
     instance.protocolDescription = ProtocolDescription.fromData(data.protocolDescription);
-    instance.eventSchema = EventSchema.fromData(data.eventSchema);
     return instance;
   }
 }
@@ -1668,9 +1670,9 @@ export class GenericAdapterStreamDescription extends AdapterStreamDescription im
     }
     const instance = target || new GenericAdapterStreamDescription();
     super.fromData(data, instance);
+    instance.eventSchema = EventSchema.fromData(data.eventSchema);
     instance.formatDescription = FormatDescription.fromData(data.formatDescription);
     instance.protocolDescription = ProtocolDescription.fromData(data.protocolDescription);
-    instance.eventSchema = EventSchema.fromData(data.eventSchema);
     return instance;
   }
 }
@@ -2439,9 +2441,9 @@ export class PipelineTemplateDescription extends NamedStreamPipesEntity {
     const instance = target || new PipelineTemplateDescription();
     super.fromData(data, instance);
     instance.boundTo = __getCopyArrayFn(BoundPipelineElement.fromData)(data.boundTo);
+    instance.pipelineTemplateId = data.pipelineTemplateId;
     instance.pipelineTemplateDescription = data.pipelineTemplateDescription;
     instance.pipelineTemplateName = data.pipelineTemplateName;
-    instance.pipelineTemplateId = data.pipelineTemplateId;
     return instance;
   }
 }
@@ -2628,6 +2630,7 @@ export class RuntimeOptionsRequest extends UnnamedStreamPipesEntity {
   inputStreams: SpDataStreamUnion[];
   requestId: string;
   staticProperties: StaticPropertyUnion[];
+  staticPropertyInternalName: string;
 
   static fromData(data: RuntimeOptionsRequest, target?: RuntimeOptionsRequest): RuntimeOptionsRequest {
     if (!data) {
@@ -2636,6 +2639,7 @@ export class RuntimeOptionsRequest extends UnnamedStreamPipesEntity {
     const instance = target || new RuntimeOptionsRequest();
     super.fromData(data, instance);
     instance.requestId = data.requestId;
+    instance.staticPropertyInternalName = data.staticPropertyInternalName;
     instance.appId = data.appId;
     instance.staticProperties = __getCopyArrayFn(StaticProperty.fromDataUnion)(data.staticProperties);
     instance.inputStreams = __getCopyArrayFn(SpDataStream.fromDataUnion)(data.inputStreams);
@@ -2646,7 +2650,7 @@ export class RuntimeOptionsRequest extends UnnamedStreamPipesEntity {
 
 export class RuntimeOptionsResponse extends RuntimeOptionsRequest {
   "@class": "org.apache.streampipes.model.runtime.RuntimeOptionsResponse";
-  options: Option[];
+  staticProperty: StaticPropertyUnion;
 
   static fromData(data: RuntimeOptionsResponse, target?: RuntimeOptionsResponse): RuntimeOptionsResponse {
     if (!data) {
@@ -2654,7 +2658,7 @@ export class RuntimeOptionsResponse extends RuntimeOptionsRequest {
     }
     const instance = target || new RuntimeOptionsResponse();
     super.fromData(data, instance);
-    instance.options = __getCopyArrayFn(Option.fromData)(data.options);
+    instance.staticProperty = StaticProperty.fromDataUnion(data.staticProperty);
     return instance;
   }
 }
@@ -2689,6 +2693,23 @@ export class RuntimeResolvableOneOfStaticProperty extends OneOfStaticProperty {
   }
 }
 
+export class RuntimeResolvableTreeInputStaticProperty extends StaticProperty {
+  "@class": "org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty";
+  dependsOn: string[];
+  nodes: TreeInputNode[];
+
+  static fromData(data: RuntimeResolvableTreeInputStaticProperty, target?: RuntimeResolvableTreeInputStaticProperty): RuntimeResolvableTreeInputStaticProperty {
+    if (!data) {
+      return data;
+    }
+    const instance = target || new RuntimeResolvableTreeInputStaticProperty();
+    super.fromData(data, instance);
+    instance.dependsOn = __getCopyArrayFn(__identity<string>())(data.dependsOn);
+    instance.nodes = __getCopyArrayFn(TreeInputNode.fromData)(data.nodes);
+    return instance;
+  }
+}
+
 export class SecretStaticProperty extends StaticProperty {
   "@class": "org.apache.streampipes.model.staticproperty.SecretStaticProperty";
   encrypted: boolean;
@@ -2825,8 +2846,8 @@ export class SpDataSet extends SpDataStream {
     instance.datasetInvocationId = data.datasetInvocationId;
     instance.correspondingPipeline = data.correspondingPipeline;
     instance.selectedEndpointUrl = data.selectedEndpointUrl;
-    instance.brokerHostname = data.brokerHostname;
     instance.actualTopicName = data.actualTopicName;
+    instance.brokerHostname = data.brokerHostname;
     return instance;
   }
 }
@@ -3030,6 +3051,27 @@ export class TransportFormat extends UnnamedStreamPipesEntity {
   }
 }
 
+export class TreeInputNode {
+  children: TreeInputNode[];
+  dataNode: boolean;
+  internalNodeName: string;
+  nodeName: string;
+  selected: boolean;
+
+  static fromData(data: TreeInputNode, target?: TreeInputNode): TreeInputNode {
+    if (!data) {
+      return data;
+    }
+    const instance = target || new TreeInputNode();
+    instance.dataNode = data.dataNode;
+    instance.selected = data.selected;
+    instance.nodeName = data.nodeName;
+    instance.internalNodeName = data.internalNodeName;
+    instance.children = __getCopyArrayFn(TreeInputNode.fromData)(data.children);
+    return instance;
+  }
+}
+
 export class UnitTransformRuleDescription extends ValueTransformationRuleDescription {
   "@class": "org.apache.streampipes.model.connect.rules.value.UnitTransformRuleDescription";
   fromUnitRessourceURL: string;
@@ -3149,9 +3191,9 @@ export type SelectionStaticPropertyUnion = AnyStaticProperty | OneOfStaticProper
 
 export type SpDataStreamUnion = SpDataStream | SpDataSet;
 
-export type StaticPropertyType = "AnyStaticProperty" | "CollectionStaticProperty" | "ColorPickerStaticProperty" | "DomainStaticProperty" | "FreeTextStaticProperty" | "FileStaticProperty" | "MappingPropertyUnary" | "MappingPropertyNary" | "MatchingStaticProperty" | "OneOfStaticProperty" | "RuntimeResolvableAnyStaticProperty" | "RuntimeResolvableOneOfStaticProperty" | "StaticPropertyGroup" | "StaticPropertyAlternatives" | "StaticPropertyAlternative" | "SecretStaticProperty" | "SlideToggleS [...]
+export type StaticPropertyType = "AnyStaticProperty" | "CodeInputStaticProperty" | "CollectionStaticProperty" | "ColorPickerStaticProperty" | "DomainStaticProperty" | "FreeTextStaticProperty" | "FileStaticProperty" | "MappingPropertyUnary" | "MappingPropertyNary" | "MatchingStaticProperty" | "OneOfStaticProperty" | "RuntimeResolvableAnyStaticProperty" | "RuntimeResolvableOneOfStaticProperty" | "RuntimeResolvableTreeInputStaticProperty" | "StaticPropertyGroup" | "StaticPropertyAlternative [...]
 
-export type StaticPropertyUnion = AnyStaticProperty | CodeInputStaticProperty | CollectionStaticProperty | ColorPickerStaticProperty | DomainStaticProperty | FileStaticProperty | FreeTextStaticProperty | MappingPropertyUnary | MappingPropertyNary | MatchingStaticProperty | OneOfStaticProperty | RuntimeResolvableAnyStaticProperty | RuntimeResolvableOneOfStaticProperty | SecretStaticProperty | StaticPropertyAlternative | StaticPropertyAlternatives | StaticPropertyGroup | SlideToggleStaticP [...]
+export type StaticPropertyUnion = AnyStaticProperty | CodeInputStaticProperty | CollectionStaticProperty | ColorPickerStaticProperty | DomainStaticProperty | FileStaticProperty | FreeTextStaticProperty | MappingPropertyUnary | MappingPropertyNary | MatchingStaticProperty | OneOfStaticProperty | RuntimeResolvableAnyStaticProperty | RuntimeResolvableOneOfStaticProperty | RuntimeResolvableTreeInputStaticProperty | SecretStaticProperty | StaticPropertyAlternative | StaticPropertyAlternatives [...]
 
 export type StreamTransformationRuleDescriptionUnion = EventRateTransformationRuleDescription | RemoveDuplicatesTransformationRuleDescription;
 
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
index 8daffb5..51251c0 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-input/base-runtime-resolvable-input.ts
@@ -21,7 +21,11 @@ import {
   RuntimeOptionsRequest,
   RuntimeOptionsResponse,
   RuntimeResolvableAnyStaticProperty,
-  RuntimeResolvableOneOfStaticProperty
+  RuntimeResolvableOneOfStaticProperty,
+  SelectionStaticProperty,
+  SelectionStaticPropertyUnion,
+  StaticProperty,
+  StaticPropertyUnion
 } from '../../../core-model/gen/streampipes-model';
 import { RuntimeResolvableService } from './runtime-resolvable.service';
 import { Observable } from 'rxjs';
@@ -75,7 +79,10 @@ export abstract class BaseRuntimeResolvableInput<T extends RuntimeResolvableAnyS
         this.runtimeResolvableService.fetchRemoteOptionsForAdapter(resolvableOptionsParameterRequest, this.adapterId) :
         this.runtimeResolvableService.fetchRemoteOptionsForPipelineElement(resolvableOptionsParameterRequest);
     observable.subscribe(msg => {
-      this.staticProperty.options = msg.options;
+      const property = StaticProperty.fromDataUnion(msg.staticProperty);
+      if (property instanceof SelectionStaticProperty) {
+      this.staticProperty.options = property.options;
+      }
       this.afterOptionsLoaded();
       this.loading = false;
       this.showOptions = true;

[incubator-streampipes] 06/08: [STREAMPIPES-505] Rename OpcUa to SpUpcUaClient

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit c1d7baecbd211eb00a65ec6217a855cbc87d7e85
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Mon Jan 10 10:56:47 2022 +0100

    [STREAMPIPES-505] Rename OpcUa to SpUpcUaClient
---
 .../connect/iiot/adapters/opcua/OpcUaAdapter.java  | 22 +++++++++++-----------
 .../opcua/{OpcUa.java => SpOpcUaClient.java}       |  8 ++++----
 .../iiot/adapters/opcua/utils/OpcUaUtil.java       | 18 +++++++++---------
 3 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
index e885c87..d7c0c9b 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
@@ -53,7 +53,7 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
     public static final String ID = "org.apache.streampipes.connect.iiot.adapters.opcua";
 
     private int pullingIntervalMilliSeconds;
-    private OpcUa opcUa;
+    private SpOpcUaClient spOpcUaClient;
     private List<OpcNode> allNodes;
     private List<NodeId> allNodeIds;
     private int numberProperties;
@@ -76,19 +76,19 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
 
         this.allNodeIds = new ArrayList<>();
         try {
-            this.opcUa.connect();
-            this.allNodes = this.opcUa.browseNode(true);
+            this.spOpcUaClient.connect();
+            this.allNodes = this.spOpcUaClient.browseNode(true);
 
 
                 for (OpcNode node : this.allNodes) {
                     this.allNodeIds.add(node.nodeId);
                 }
 
-            if (opcUa.inPullMode()) {
-                this.pullingIntervalMilliSeconds = opcUa.getPullIntervalMilliSeconds();
+            if (spOpcUaClient.inPullMode()) {
+                this.pullingIntervalMilliSeconds = spOpcUaClient.getPullIntervalMilliSeconds();
             } else {
                 this.numberProperties = this.allNodeIds.size();
-                this.opcUa.createListSubscription(this.allNodeIds, this);
+                this.spOpcUaClient.createListSubscription(this.allNodeIds, this);
             }
 
 
@@ -100,9 +100,9 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
         @Override
     public void startAdapter() throws AdapterException {
 
-        this.opcUa = new OpcUa(SpOpcUaConfigBuilder.from(this.adapterDescription));
+        this.spOpcUaClient = new SpOpcUaClient(SpOpcUaConfigBuilder.from(this.adapterDescription));
 
-        if (this.opcUa.inPullMode()) {
+        if (this.spOpcUaClient.inPullMode()) {
             super.startAdapter();
         } else {
             this.before();
@@ -112,9 +112,9 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
     @Override
     public void stopAdapter() throws AdapterException {
         // close connection
-        this.opcUa.disconnect();
+        this.spOpcUaClient.disconnect();
 
-        if (this.opcUa.inPullMode()){
+        if (this.spOpcUaClient.inPullMode()){
             super.stopAdapter();
         }
     }
@@ -122,7 +122,7 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
     @Override
     protected void pullData() {
 
-        CompletableFuture<List<DataValue>> response = this.opcUa.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
+        CompletableFuture<List<DataValue>> response = this.spOpcUaClient.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
         try {
         List<DataValue> returnValues = response.get();
             for (int i = 0; i<returnValues.size(); i++) {
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
similarity index 98%
rename from streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java
rename to streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
index a947421..0a947f7 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUa.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
@@ -56,9 +56,9 @@ import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
 /***
  * Wrapper class for all OPC UA specific stuff.
  */
-public class OpcUa {
+public class SpOpcUaClient {
 
-    private static final Logger LOG = LoggerFactory.getLogger(OpcUa.class);
+    private static final Logger LOG = LoggerFactory.getLogger(SpOpcUaClient.class);
 
     private OpcUaClient client;
     private final SpOpcUaConfig spOpcConfig;
@@ -66,7 +66,7 @@ public class OpcUa {
 
     private static final AtomicLong clientHandles = new AtomicLong(1L);
 
-    public OpcUa(SpOpcUaConfig config) {
+    public SpOpcUaClient(SpOpcUaConfig config) {
         this.spOpcConfig = config;
     }
 
@@ -79,7 +79,7 @@ public class OpcUa {
     }
 
     /***
-     * Establishes appropriate connection to OPC UA endpoint depending on the {@link OpcUa} instance
+     * Establishes appropriate connection to OPC UA endpoint depending on the {@link SpOpcUaClient} instance
      *
      * @throws Exception An exception occurring during OPC connection
      */
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
index a882d74..ab4c096 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
@@ -21,7 +21,7 @@ package org.apache.streampipes.connect.iiot.adapters.opcua.utils;
 import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.api.exception.ParseException;
 import org.apache.streampipes.connect.iiot.adapters.opcua.OpcNode;
-import org.apache.streampipes.connect.iiot.adapters.opcua.OpcUa;
+import org.apache.streampipes.connect.iiot.adapters.opcua.SpOpcUaClient;
 import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfigBuilder;
 import org.apache.streampipes.model.connect.adapter.SpecificAdapterStreamDescription;
 import org.apache.streampipes.model.connect.guess.GuessSchema;
@@ -71,11 +71,11 @@ public class OpcUaUtil {
         EventSchema eventSchema = new EventSchema();
         List<EventProperty> allProperties = new ArrayList<>();
 
-        OpcUa opcUa = new OpcUa(SpOpcUaConfigBuilder.from(adapterStreamDescription));
+        SpOpcUaClient spOpcUaClient = new SpOpcUaClient(SpOpcUaConfigBuilder.from(adapterStreamDescription));
 
         try {
-            opcUa.connect();
-            List<OpcNode> selectedNodes = opcUa.browseNode(true);
+            spOpcUaClient.connect();
+            List<OpcNode> selectedNodes = spOpcUaClient.browseNode(true);
 
             if (!selectedNodes.isEmpty()) {
                 for (OpcNode opcNode : selectedNodes) {
@@ -95,7 +95,7 @@ public class OpcUaUtil {
                 }
             }
 
-            opcUa.disconnect();
+            spOpcUaClient.disconnect();
 
         } catch (Exception e) {
             throw new AdapterException("Could not guess schema for opc node! " + e.getMessage());
@@ -128,18 +128,18 @@ public class OpcUaUtil {
             return config;
         }
 
-        OpcUa opcUa = new OpcUa(SpOpcUaConfigBuilder.from(parameterExtractor));
+        SpOpcUaClient spOpcUaClient = new SpOpcUaClient(SpOpcUaConfigBuilder.from(parameterExtractor));
 
         List<TreeInputNode> nodeOptions = new ArrayList<>();
         try{
-            opcUa.connect();
+            spOpcUaClient.connect();
 
-            for(OpcNode opcNode: opcUa.browseNode(false)) {
+            for(OpcNode opcNode: spOpcUaClient.browseNode(false)) {
                 TreeInputNode node = makeTreeInputNode(opcNode);
                 nodeOptions.add(node);
             }
 
-            opcUa.disconnect();
+            spOpcUaClient.disconnect();
         } catch (Exception e) {
             e.printStackTrace();
         }

[incubator-streampipes] 07/08: [STREAMPIPES-505] Extract node browser to own class

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 8cce5dbc3c4b5d7feb8f603173f0481abd0aea88
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Mon Jan 10 11:39:42 2022 +0100

    [STREAMPIPES-505] Extract node browser to own class
---
 .../connect/iiot/adapters/opcua/OpcUaAdapter.java  |   3 +-
 .../iiot/adapters/opcua/OpcUaNodeBrowser.java      | 231 +++++++++++++++++++++
 .../connect/iiot/adapters/opcua/SpOpcUaClient.java | 199 ++----------------
 .../opcua/configuration/SpOpcUaConfig.java         |   6 +-
 .../iiot/adapters/opcua/utils/OpcUaUtil.java       |   8 +-
 5 files changed, 255 insertions(+), 192 deletions(-)

diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
index d7c0c9b..94fad42 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
@@ -77,7 +77,8 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
         this.allNodeIds = new ArrayList<>();
         try {
             this.spOpcUaClient.connect();
-            this.allNodes = this.spOpcUaClient.browseNode(true);
+            OpcUaNodeBrowser browserClient = new OpcUaNodeBrowser(this.spOpcUaClient.getClient(), this.spOpcUaClient.getSpOpcConfig());
+            this.allNodes = browserClient.browseNode(true);
 
 
                 for (OpcNode node : this.allNodes) {
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java
new file mode 100644
index 0000000..10709b4
--- /dev/null
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java
@@ -0,0 +1,231 @@
+/*
+ * 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.connect.iiot.adapters.opcua;
+
+import org.apache.streampipes.connect.api.exception.AdapterException;
+import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfig;
+import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaNodeVariants;
+import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaTypes;
+import org.apache.streampipes.sdk.utils.Datatypes;
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
+import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
+import org.eclipse.milo.opcua.stack.core.types.structured.EUInformation;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.retrieveDataTypesFromServer;
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
+
+public class OpcUaNodeBrowser {
+
+  private OpcUaClient client;
+  private SpOpcUaConfig spOpcConfig;
+
+  private List<Map<String, Integer>> unitIDs = new ArrayList<>();
+
+  public OpcUaNodeBrowser(OpcUaClient client,
+                          SpOpcUaConfig spOpcUaClientConfig) {
+    this.client = client;
+    this.spOpcConfig = spOpcUaClientConfig;
+  }
+
+  /***
+   * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
+   * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
+   * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
+   * @throws AdapterException
+   */
+  public List<OpcNode> browseNode(boolean selectNodes) throws AdapterException {
+    NodeId originNodeId = spOpcConfig.getOriginNodeId();
+    List<OpcNode> discoveredNodes = browseNode(originNodeId, selectNodes);
+
+    if (discoveredNodes.size() == 0) {
+      discoveredNodes =  getRootNote(originNodeId);
+    } else if (selectNodes) { // only required for getSchema where the selectedNodes are already set.
+            /* In case a node with sub-nodes is queried, the data types are not detected appropriately.
+               This has to be performed separately.
+             */
+      retrieveDataTypesFromServer(client, discoveredNodes);
+    }
+
+    return discoveredNodes;
+  }
+
+  private List<OpcNode> getRootNote(NodeId browseRoot) {
+    List<OpcNode> result = new ArrayList<>();
+
+    try {
+//            VariableNode resultNode = client.getAddressSpace().getVariableNode(browseRoot).get();
+      String label = client.getAddressSpace().getVariableNode(browseRoot).getDisplayName().getText();
+      int opcDataTypeId = ((UInteger) client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier()).intValue();
+      Datatypes type = OpcUaTypes.getType((UInteger)client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier());
+      NodeId nodeId = client.getAddressSpace().getVariableNode(browseRoot).getNodeId();
+
+      // if rootNote is of type Property or EUInformation it does not deliver any data value,
+      // therefore return an empty list
+      if (opcDataTypeId == OpcUaNodeVariants.Property.getId() || opcDataTypeId == OpcUaNodeVariants.EUInformation.getId()){
+        return result;
+      }
+
+      // check whether a unitID is detected for this node
+      Integer unitID = null;
+      for (Map<String, Integer> unit: this.unitIDs) {
+        if (unit.get(nodeId) != null) {
+          unitID = unit.get(nodeId);
+        }
+      }
+
+      if (unitID != null){
+        result.add(new OpcNode(label,type, nodeId, unitID));
+      } else {
+        result.add(new OpcNode(label, type, nodeId));
+      }
+
+    } catch (UaException e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+
+  /***
+   * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
+   * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
+   * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
+   * @throws AdapterException
+   */
+  private List<OpcNode> browseNode(NodeId browseRoot, boolean selectNodes) throws AdapterException {
+    List<OpcNode> result = new ArrayList<>();
+
+    BrowseDescription browse = new BrowseDescription(
+            browseRoot,
+            BrowseDirection.Forward,
+            Identifiers.References,
+            true,
+            uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
+            uint(BrowseResultMask.All.getValue())
+    );
+
+    try {
+      BrowseResult browseResult = client.browse(browse).get();
+
+      if (browseResult.getStatusCode().isBad()) {
+        throw new AdapterException(browseResult.getStatusCode().toString());
+      }
+
+      List<ReferenceDescription> references = toList(browseResult.getReferences());
+
+      for (ReferenceDescription ref: references){
+        if (ref.getNodeClass().name().equals("Object")) {
+          NodeId brwRoot =  ref.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
+
+          BrowseDescription brw = new BrowseDescription(
+                  brwRoot,
+                  BrowseDirection.Both,
+                  Identifiers.HasComponent,
+                  true,
+                  uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
+                  uint(BrowseResultMask.All.getValue())
+          );
+          List<ReferenceDescription> subReferences = toList(client.browse(brw).get().getReferences());
+          references = Stream.concat(references.stream(), subReferences.stream()).collect(Collectors.toList());
+        }
+
+      }
+
+      for (ReferenceDescription rd : references) {
+        if (rd.getNodeClass() == NodeClass.Variable) {
+
+          EUInformation eu;
+
+          // check for whether the Node is of type Property
+          if (OpcUaNodeVariants.Property.getId() == ((UInteger) rd.getTypeDefinition().getIdentifier()).intValue()) {
+
+            ExpandedNodeId property = rd.getNodeId();
+            NodeId propertyNode = property.toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
+
+            // check node for EU Information
+
+            if (OpcUaNodeVariants.EUInformation.getId() == ((UInteger) client.getAddressSpace().getVariableNode(propertyNode).getDataType().getIdentifier()).intValue()){
+
+              ExtensionObject euExtension = (ExtensionObject) client.readValue(0, TimestampsToReturn.Both, propertyNode).get().getValue().getValue();
+
+              // save information about EngineeringUnit in list
+              eu = (EUInformation) euExtension.decode(client.getSerializationContext());
+              Map map = new HashMap();
+              map.put(browseRoot, eu.getUnitId());
+              this.unitIDs.add(map);
+
+            }
+
+          } else {
+
+            // check whether there exists an unitID for this node
+            Integer unitID = null;
+            for (Map<String, Integer> unit: this.unitIDs){
+              if (unit.get(browseRoot) != null){
+                unitID = unit.get(browseRoot);
+              }
+            }
+            OpcNode opcNode;
+            if (unitID != null){
+              opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new), unitID);
+            } else {
+              opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new));
+            }
+            rd.getNodeId();
+
+            result.add(opcNode);
+          }
+        }
+      }
+    } catch (InterruptedException | ExecutionException | UaException e) {
+      throw new AdapterException("Browsing nodeId=" + browse + " failed: " + e.getMessage());
+    }
+
+    if (selectNodes) {
+      // filter for nodes that were selected by the user during configuration
+//            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getLabel()))
+//                    .collect(Collectors.toList());
+      result = result.stream().filter(node -> this.spOpcConfig.getSelectedNodeNames().contains(node.getNodeId().getIdentifier().toString()))
+              .collect(Collectors.toList());
+    }
+
+    return result;
+  }
+}
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
index 0a947f7..45b5a51 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
@@ -19,39 +19,31 @@
 package org.apache.streampipes.connect.iiot.adapters.opcua;
 
 
-import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfig;
-import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaNodeVariants;
-import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaTypes;
-import org.apache.streampipes.sdk.utils.Datatypes;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
 import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
 import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
 import org.eclipse.milo.opcua.stack.core.AttributeId;
-import org.eclipse.milo.opcua.stack.core.Identifiers;
-import org.eclipse.milo.opcua.stack.core.UaException;
-import org.eclipse.milo.opcua.stack.core.types.builtin.*;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+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.builtin.unsigned.UInteger;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.*;
-import org.eclipse.milo.opcua.stack.core.types.structured.*;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
 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.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiConsumer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
-import static org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.retrieveDataTypesFromServer;
 import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
-import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
 
 /***
  * Wrapper class for all OPC UA specific stuff.
@@ -62,7 +54,6 @@ public class SpOpcUaClient {
 
     private OpcUaClient client;
     private final SpOpcUaConfig spOpcConfig;
-    private List<Map<String, Integer>> unitIDs = new ArrayList<>();
 
     private static final AtomicLong clientHandles = new AtomicLong(1L);
 
@@ -94,172 +85,6 @@ public class SpOpcUaClient {
     }
 
     /***
-     * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
-     * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
-     * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
-     * @throws AdapterException
-     */
-    public List<OpcNode> browseNode(boolean selectNodes) throws AdapterException {
-        NodeId originNodeId = spOpcConfig.getOriginNodeId();
-        List<OpcNode> discoveredNodes = browseNode(originNodeId, selectNodes);
-
-        if (discoveredNodes.size() == 0) {
-            discoveredNodes =  getRootNote(originNodeId);
-        } else if (selectNodes) { // only required for getSchema where the selectedNodes are already set.
-            /* In case a node with sub-nodes is queried, the data types are not detected appropriately.
-               This has to be performed separately.
-             */
-            retrieveDataTypesFromServer(client, discoveredNodes);
-        }
-
-        return discoveredNodes;
-    }
-
-    private List<OpcNode> getRootNote(NodeId browseRoot) {
-        List<OpcNode> result = new ArrayList<>();
-
-        try {
-//            VariableNode resultNode = client.getAddressSpace().getVariableNode(browseRoot).get();
-            String label = client.getAddressSpace().getVariableNode(browseRoot).getDisplayName().getText();
-            int opcDataTypeId = ((UInteger) client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier()).intValue();
-            Datatypes type = OpcUaTypes.getType((UInteger)client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier());
-            NodeId nodeId = client.getAddressSpace().getVariableNode(browseRoot).getNodeId();
-
-            // if rootNote is of type Property or EUInformation it does not deliver any data value,
-            // therefore return an empty list
-            if (opcDataTypeId == OpcUaNodeVariants.Property.getId() || opcDataTypeId == OpcUaNodeVariants.EUInformation.getId()){
-                return result;
-            }
-
-            // check whether a unitID is detected for this node
-            Integer unitID = null;
-            for (Map<String, Integer> unit: this.unitIDs) {
-                if (unit.get(nodeId) != null) {
-                    unitID = unit.get(nodeId);
-                }
-            }
-
-            if (unitID != null){
-                result.add(new OpcNode(label,type, nodeId, unitID));
-            } else {
-                result.add(new OpcNode(label, type, nodeId));
-            }
-
-        } catch (UaException e) {
-            e.printStackTrace();
-        }
-
-        return result;
-    }
-
-    /***
-     * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
-     * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
-     * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
-     * @throws AdapterException
-     */
-    private List<OpcNode> browseNode(NodeId browseRoot, boolean selectNodes) throws AdapterException {
-        List<OpcNode> result = new ArrayList<>();
-
-        BrowseDescription browse = new BrowseDescription(
-                browseRoot,
-                BrowseDirection.Forward,
-                Identifiers.References,
-                true,
-                uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
-                uint(BrowseResultMask.All.getValue())
-        );
-
-        try {
-            BrowseResult browseResult = client.browse(browse).get();
-
-            if (browseResult.getStatusCode().isBad()) {
-                throw new AdapterException(browseResult.getStatusCode().toString());
-            }
-
-            List<ReferenceDescription> references = toList(browseResult.getReferences());
-
-            for (ReferenceDescription ref: references){
-                if (ref.getNodeClass().name().equals("Object")) {
-                    NodeId brwRoot =  ref.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
-
-                    BrowseDescription brw = new BrowseDescription(
-                            brwRoot,
-                            BrowseDirection.Both,
-                            Identifiers.HasComponent,
-                            true,
-                            uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
-                            uint(BrowseResultMask.All.getValue())
-                    );
-                    List<ReferenceDescription> subReferences = toList(client.browse(brw).get().getReferences());
-                    references = Stream.concat(references.stream(), subReferences.stream()).collect(Collectors.toList());
-                }
-
-            }
-
-            for (ReferenceDescription rd : references) {
-                if (rd.getNodeClass() == NodeClass.Variable) {
-
-                    EUInformation eu;
-
-                    // check for whether the Node is of type Property
-                    if (OpcUaNodeVariants.Property.getId() == ((UInteger) rd.getTypeDefinition().getIdentifier()).intValue()) {
-
-                        ExpandedNodeId property = rd.getNodeId();
-                        NodeId propertyNode = property.toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
-
-                        // check node for EU Information
-
-                       if (OpcUaNodeVariants.EUInformation.getId() == ((UInteger) client.getAddressSpace().getVariableNode(propertyNode).getDataType().getIdentifier()).intValue()){
-
-                           ExtensionObject euExtension = (ExtensionObject) client.readValue(0, TimestampsToReturn.Both, propertyNode).get().getValue().getValue();
-
-                           // save information about EngineeringUnit in list
-                           eu = (EUInformation) euExtension.decode(client.getSerializationContext());
-                           Map map = new HashMap();
-                           map.put(browseRoot, eu.getUnitId());
-                           this.unitIDs.add(map);
-
-                       }
-
-                    } else {
-
-                        // check whether there exists an unitID for this node
-                        Integer unitID = null;
-                        for (Map<String, Integer> unit: this.unitIDs){
-                            if (unit.get(browseRoot) != null){
-                                unitID = unit.get(browseRoot);
-                            }
-                        }
-                        OpcNode opcNode;
-                        if (unitID != null){
-                            opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new), unitID);
-                        } else {
-                            opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new));
-                        }
-                        rd.getNodeId();
-
-                        result.add(opcNode);
-                    }
-                }
-            }
-        } catch (InterruptedException | ExecutionException | UaException e) {
-            throw new AdapterException("Browsing nodeId=" + browse + " failed: " + e.getMessage());
-        }
-
-        if (selectNodes) {
-            // filter for nodes that were selected by the user during configuration
-//            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getLabel()))
-//                    .collect(Collectors.toList());
-            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getNodeId().getIdentifier().toString()))
-                    .collect(Collectors.toList());
-        }
-
-        return result;
-    }
-
-
-    /***
      * Register subscriptions for given OPC UA nodes
      * @param nodes List of {@link org.eclipse.milo.opcua.stack.core.types.builtin.NodeId}
      * @param opcUaAdapter current instance of {@link OpcUaAdapter}
@@ -328,10 +153,6 @@ public class SpOpcUaClient {
         }
     }
 
-    private List<String> getSelectedNodeNames() {
-        return spOpcConfig.getSelectedNodeNames();
-    }
-
     public boolean inPullMode() {
         return !(spOpcConfig.getPullIntervalMilliSeconds() == null);
     }
@@ -339,4 +160,8 @@ public class SpOpcUaClient {
     public int getPullIntervalMilliSeconds() {
         return spOpcConfig.getPullIntervalMilliSeconds();
     }
+
+    public SpOpcUaConfig getSpOpcConfig() {
+        return spOpcConfig;
+    }
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
index a2a5c1e..fac67d8 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
@@ -50,7 +50,11 @@ public class SpOpcUaConfig {
    * @param pullIntervalMilliSeconds duration of pull interval in milliseconds, {@code null} if in subscription mode
    * @param selectedNodeNames list of node names provided from {@link OpcUaUtil#resolveConfiguration(String, StaticPropertyExtractor)} (String, StaticPropertyExtractor)}
    */
-  public SpOpcUaConfig(String opcServerURL, int namespaceIndex, String nodeId, int pullIntervalMilliSeconds, List<String> selectedNodeNames) {
+  public SpOpcUaConfig(String opcServerURL,
+                       int namespaceIndex,
+                       String nodeId,
+                       int pullIntervalMilliSeconds,
+                       List<String> selectedNodeNames) {
 
     this.opcServerURL = opcServerURL;
     this.unauthenticated = true;
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
index ab4c096..e846793 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
@@ -21,6 +21,7 @@ package org.apache.streampipes.connect.iiot.adapters.opcua.utils;
 import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.api.exception.ParseException;
 import org.apache.streampipes.connect.iiot.adapters.opcua.OpcNode;
+import org.apache.streampipes.connect.iiot.adapters.opcua.OpcUaNodeBrowser;
 import org.apache.streampipes.connect.iiot.adapters.opcua.SpOpcUaClient;
 import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfigBuilder;
 import org.apache.streampipes.model.connect.adapter.SpecificAdapterStreamDescription;
@@ -75,7 +76,8 @@ public class OpcUaUtil {
 
         try {
             spOpcUaClient.connect();
-            List<OpcNode> selectedNodes = spOpcUaClient.browseNode(true);
+            OpcUaNodeBrowser nodeBrowser = new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
+            List<OpcNode> selectedNodes = nodeBrowser.browseNode(true);
 
             if (!selectedNodes.isEmpty()) {
                 for (OpcNode opcNode : selectedNodes) {
@@ -133,8 +135,8 @@ public class OpcUaUtil {
         List<TreeInputNode> nodeOptions = new ArrayList<>();
         try{
             spOpcUaClient.connect();
-
-            for(OpcNode opcNode: spOpcUaClient.browseNode(false)) {
+            OpcUaNodeBrowser nodeBrowser = new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
+            for(OpcNode opcNode: nodeBrowser.browseNode(false)) {
                 TreeInputNode node = makeTreeInputNode(opcNode);
                 nodeOptions.add(node);
             }

[incubator-streampipes] 08/08: [STREAMPIPES-505] Improve OPC-UA adapter

Posted by ri...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-505
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git

commit 72d06760c2a916652b3c911ec10f4a761ccd2353
Author: Dominik Riemer <do...@gmail.com>
AuthorDate: Mon Jan 10 22:38:09 2022 +0100

    [STREAMPIPES-505] Improve OPC-UA adapter
---
 streampipes-extensions/pom.xml                     |   2 +-
 .../connect/iiot/adapters/opcua/OpcUaAdapter.java  |   3 +-
 .../iiot/adapters/opcua/OpcUaNodeBrowser.java      | 228 ++++++---------------
 .../connect/iiot/adapters/opcua/SpOpcUaClient.java |  18 +-
 .../opcua/configuration/SpOpcUaConfig.java         |   7 +-
 .../iiot/adapters/opcua/utils/OpcUaUtil.java       |  27 +--
 6 files changed, 75 insertions(+), 210 deletions(-)

diff --git a/streampipes-extensions/pom.xml b/streampipes-extensions/pom.xml
index 7c7ba82..882078d 100644
--- a/streampipes-extensions/pom.xml
+++ b/streampipes-extensions/pom.xml
@@ -124,7 +124,7 @@
         <jcl-over-slf4j.version>1.7.30</jcl-over-slf4j.version>
         <jsrosbridge.version>0.2.0</jsrosbridge.version>
         <influxdb.java.version>2.14</influxdb.java.version>
-        <eclipse.milo.version>0.5.3</eclipse.milo.version>
+        <eclipse.milo.version>0.6.3</eclipse.milo.version>
         <mysql-binlog-connector.version>0.18.1</mysql-binlog-connector.version>
         <mysql-connector-java.version>8.0.15</mysql-connector-java.version>
         <netty.version>4.1.39.Final</netty.version>
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
index 94fad42..525ca38 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaAdapter.java
@@ -78,7 +78,7 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
         try {
             this.spOpcUaClient.connect();
             OpcUaNodeBrowser browserClient = new OpcUaNodeBrowser(this.spOpcUaClient.getClient(), this.spOpcUaClient.getSpOpcConfig());
-            this.allNodes = browserClient.browseNode(true);
+            this.allNodes = browserClient.findNodes();
 
 
                 for (OpcNode node : this.allNodes) {
@@ -122,7 +122,6 @@ public class OpcUaAdapter extends PullAdapter implements SupportsRuntimeConfig {
 
     @Override
     protected void pullData() {
-
         CompletableFuture<List<DataValue>> response = this.spOpcUaClient.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
         try {
         List<DataValue> returnValues = response.get();
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java
index 10709b4..6429aa4 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/OpcUaNodeBrowser.java
@@ -18,45 +18,30 @@
 
 package org.apache.streampipes.connect.iiot.adapters.opcua;
 
-import org.apache.streampipes.connect.api.exception.AdapterException;
 import org.apache.streampipes.connect.iiot.adapters.opcua.configuration.SpOpcUaConfig;
-import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaNodeVariants;
 import org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaTypes;
-import org.apache.streampipes.sdk.utils.Datatypes;
+import org.apache.streampipes.model.staticproperty.TreeInputNode;
+import org.eclipse.milo.opcua.sdk.client.AddressSpace;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
-import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.sdk.client.model.nodes.variables.BaseDataVariableTypeNode;
+import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
 import org.eclipse.milo.opcua.stack.core.UaException;
-import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
-import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject;
 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.BrowseDirection;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
 import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
-import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
-import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
-import org.eclipse.milo.opcua.stack.core.types.structured.EUInformation;
-import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static org.apache.streampipes.connect.iiot.adapters.opcua.utils.OpcUaUtil.retrieveDataTypesFromServer;
-import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
-import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;
-
 public class OpcUaNodeBrowser {
 
-  private OpcUaClient client;
-  private SpOpcUaConfig spOpcConfig;
-
-  private List<Map<String, Integer>> unitIDs = new ArrayList<>();
+  private final OpcUaClient client;
+  private final SpOpcUaConfig spOpcConfig;
 
   public OpcUaNodeBrowser(OpcUaClient client,
                           SpOpcUaConfig spOpcUaClientConfig) {
@@ -64,168 +49,69 @@ public class OpcUaNodeBrowser {
     this.spOpcConfig = spOpcUaClientConfig;
   }
 
-  /***
-   * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
-   * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
-   * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
-   * @throws AdapterException
-   */
-  public List<OpcNode> browseNode(boolean selectNodes) throws AdapterException {
-    NodeId originNodeId = spOpcConfig.getOriginNodeId();
-    List<OpcNode> discoveredNodes = browseNode(originNodeId, selectNodes);
-
-    if (discoveredNodes.size() == 0) {
-      discoveredNodes =  getRootNote(originNodeId);
-    } else if (selectNodes) { // only required for getSchema where the selectedNodes are already set.
-            /* In case a node with sub-nodes is queried, the data types are not detected appropriately.
-               This has to be performed separately.
-             */
-      retrieveDataTypesFromServer(client, discoveredNodes);
-    }
-
-    return discoveredNodes;
+  public List<OpcNode> findNodes() {
+    return this.spOpcConfig.getSelectedNodeNames().stream().map(n -> {
+      try {
+        return toOpcNode(n);
+      } catch (UaException e) {
+        e.printStackTrace();
+        return null;
+      }
+    }).collect(Collectors.toList());
   }
 
-  private List<OpcNode> getRootNote(NodeId browseRoot) {
-    List<OpcNode> result = new ArrayList<>();
-
-    try {
-//            VariableNode resultNode = client.getAddressSpace().getVariableNode(browseRoot).get();
-      String label = client.getAddressSpace().getVariableNode(browseRoot).getDisplayName().getText();
-      int opcDataTypeId = ((UInteger) client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier()).intValue();
-      Datatypes type = OpcUaTypes.getType((UInteger)client.getAddressSpace().getVariableNode(browseRoot).getDataType().getIdentifier());
-      NodeId nodeId = client.getAddressSpace().getVariableNode(browseRoot).getNodeId();
+  public List<TreeInputNode> buildNodeTreeFromOrigin() throws UaException, ExecutionException, InterruptedException {
+    NodeId origin = spOpcConfig.getOriginNodeId();
 
-      // if rootNote is of type Property or EUInformation it does not deliver any data value,
-      // therefore return an empty list
-      if (opcDataTypeId == OpcUaNodeVariants.Property.getId() || opcDataTypeId == OpcUaNodeVariants.EUInformation.getId()){
-        return result;
-      }
+    UaNode parentNode = getAddressSpace().getNode(origin);
+    List<TreeInputNode> nodes = new ArrayList<>();
+    TreeInputNode parentInput = new TreeInputNode();
+    parentInput.setInternalNodeName(parentNode.getNodeId().toParseableString());
+    parentInput.setDataNode(isDataNode(parentNode));
+    parentInput.setNodeName(parentNode.getDisplayName().getText());
+    buildTreeAsync(client, parentInput).get();
+    nodes.add(parentInput);
 
-      // check whether a unitID is detected for this node
-      Integer unitID = null;
-      for (Map<String, Integer> unit: this.unitIDs) {
-        if (unit.get(nodeId) != null) {
-          unitID = unit.get(nodeId);
-        }
-      }
+    return nodes;
+  }
 
-      if (unitID != null){
-        result.add(new OpcNode(label,type, nodeId, unitID));
-      } else {
-        result.add(new OpcNode(label, type, nodeId));
-      }
+  private OpcNode toOpcNode(String nodeName) throws UaException {
+    AddressSpace addressSpace = getAddressSpace();
+    NodeId nodeId = NodeId.parse(nodeName);
+    UaNode node = addressSpace.getNode(nodeId);
 
-    } catch (UaException e) {
-      e.printStackTrace();
+    if (node instanceof BaseDataVariableTypeNode) {
+      UInteger value = UInteger.valueOf(((BaseDataVariableTypeNode) node).getDataType().getType().getValue());
+      return new OpcNode(node.getDisplayName().getText(), OpcUaTypes.getType(value), node.getNodeId());
     }
 
-    return result;
+    throw new UaException(StatusCode.BAD, "Node is not of type BaseDataVariableTypeNode");
   }
 
-  /***
-   * Search for related nodes relative to {@link SpOpcUaConfig#getOriginNodeId()}
-   * @param selectNodes indicates whether only nodes of {@link SpOpcUaConfig#getSelectedNodeNames()} should be returned
-   * @return List of {@link OpcNode}s related to {@link SpOpcUaConfig#getOriginNodeId()}
-   * @throws AdapterException
-   */
-  private List<OpcNode> browseNode(NodeId browseRoot, boolean selectNodes) throws AdapterException {
-    List<OpcNode> result = new ArrayList<>();
-
-    BrowseDescription browse = new BrowseDescription(
-            browseRoot,
-            BrowseDirection.Forward,
-            Identifiers.References,
-            true,
-            uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
-            uint(BrowseResultMask.All.getValue())
-    );
-
-    try {
-      BrowseResult browseResult = client.browse(browse).get();
-
-      if (browseResult.getStatusCode().isBad()) {
-        throw new AdapterException(browseResult.getStatusCode().toString());
-      }
-
-      List<ReferenceDescription> references = toList(browseResult.getReferences());
-
-      for (ReferenceDescription ref: references){
-        if (ref.getNodeClass().name().equals("Object")) {
-          NodeId brwRoot =  ref.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
-
-          BrowseDescription brw = new BrowseDescription(
-                  brwRoot,
-                  BrowseDirection.Both,
-                  Identifiers.HasComponent,
-                  true,
-                  uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
-                  uint(BrowseResultMask.All.getValue())
-          );
-          List<ReferenceDescription> subReferences = toList(client.browse(brw).get().getReferences());
-          references = Stream.concat(references.stream(), subReferences.stream()).collect(Collectors.toList());
-        }
-
-      }
-
-      for (ReferenceDescription rd : references) {
-        if (rd.getNodeClass() == NodeClass.Variable) {
-
-          EUInformation eu;
-
-          // check for whether the Node is of type Property
-          if (OpcUaNodeVariants.Property.getId() == ((UInteger) rd.getTypeDefinition().getIdentifier()).intValue()) {
-
-            ExpandedNodeId property = rd.getNodeId();
-            NodeId propertyNode = property.toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new);
-
-            // check node for EU Information
-
-            if (OpcUaNodeVariants.EUInformation.getId() == ((UInteger) client.getAddressSpace().getVariableNode(propertyNode).getDataType().getIdentifier()).intValue()){
-
-              ExtensionObject euExtension = (ExtensionObject) client.readValue(0, TimestampsToReturn.Both, propertyNode).get().getValue().getValue();
+  private CompletableFuture<Void> buildTreeAsync(OpcUaClient client, TreeInputNode tree) {
+    NodeId nodeId = NodeId.parse(tree.getInternalNodeName());
+    return client.getAddressSpace().browseNodesAsync(nodeId).thenCompose(nodes -> {
+      nodes.forEach(node -> {
+        TreeInputNode childNode = new TreeInputNode();
+        childNode.setNodeName(node.getDisplayName().getText());
+        childNode.setInternalNodeName(node.getNodeId().toParseableString());
+        childNode.setDataNode(isDataNode(node));
+        tree.addChild(childNode);
 
-              // save information about EngineeringUnit in list
-              eu = (EUInformation) euExtension.decode(client.getSerializationContext());
-              Map map = new HashMap();
-              map.put(browseRoot, eu.getUnitId());
-              this.unitIDs.add(map);
+      });
 
-            }
+      Stream<CompletableFuture<Void>> futures =
+              tree.getChildren().stream().map(child -> buildTreeAsync(client, child));
 
-          } else {
-
-            // check whether there exists an unitID for this node
-            Integer unitID = null;
-            for (Map<String, Integer> unit: this.unitIDs){
-              if (unit.get(browseRoot) != null){
-                unitID = unit.get(browseRoot);
-              }
-            }
-            OpcNode opcNode;
-            if (unitID != null){
-              opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new), unitID);
-            } else {
-              opcNode = new OpcNode(rd.getBrowseName().getName(), OpcUaTypes.getType((UInteger) rd.getTypeDefinition().getIdentifier()), rd.getNodeId().toNodeId(client.getNamespaceTable()).orElseThrow(AdapterException::new));
-            }
-            rd.getNodeId();
-
-            result.add(opcNode);
-          }
-        }
-      }
-    } catch (InterruptedException | ExecutionException | UaException e) {
-      throw new AdapterException("Browsing nodeId=" + browse + " failed: " + e.getMessage());
-    }
+      return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));
+    });
+  }
 
-    if (selectNodes) {
-      // filter for nodes that were selected by the user during configuration
-//            result = result.stream().filter(node -> this.getSelectedNodeNames().contains(node.getLabel()))
-//                    .collect(Collectors.toList());
-      result = result.stream().filter(node -> this.spOpcConfig.getSelectedNodeNames().contains(node.getNodeId().getIdentifier().toString()))
-              .collect(Collectors.toList());
-    }
+  private AddressSpace getAddressSpace() {
+    return client.getAddressSpace();
+  }
 
-    return result;
+  private boolean isDataNode(UaNode node) {
+    return node.getNodeClass().equals(NodeClass.Variable) && node instanceof BaseDataVariableTypeNode;
   }
 }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
index 45b5a51..da00cbf 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/SpOpcUaClient.java
@@ -41,7 +41,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.BiConsumer;
 
 import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
 
@@ -104,7 +103,7 @@ public class SpOpcUaClient {
 
         for (CompletableFuture<DataValue> value : values) {
             if (value.get().getValue().toString().contains("null")) {
-                System.out.println("Node has no value");
+                LOG.error("Node has no value");
             }
         }
 
@@ -132,11 +131,12 @@ public class SpOpcUaClient {
             requests.add(new MonitoredItemCreateRequest(readValue, MonitoringMode.Reporting, parameters));
         }
 
-        BiConsumer<UaMonitoredItem, Integer> onItemCreated =
-                (item, id) -> {
-                    item.setValueConsumer(opcUaAdapter::onSubscriptionValue);
-                };
-
+        UaSubscription.ItemCreationCallback onItemCreated = new UaSubscription.ItemCreationCallback() {
+            @Override
+            public void onItemCreated(UaMonitoredItem item, int i) {
+                item.setValueConsumer(opcUaAdapter::onSubscriptionValue);
+            }
+        };
         List<UaMonitoredItem> items = subscription.createMonitoredItems(
                 TimestampsToReturn.Both,
                 requests,
@@ -146,9 +146,9 @@ public class SpOpcUaClient {
         for (UaMonitoredItem item : items) {
             NodeId tagId = item.getReadValueId().getNodeId();
             if (item.getStatusCode().isGood()) {
-                System.out.println("item created for nodeId="+ tagId);
+                LOG.info("item created for nodeId="+ tagId);
             } else {
-                System.out.println("failed to create item for " + item.getReadValueId().getNodeId() + item.getStatusCode());
+                LOG.error("failed to create item for " + item.getReadValueId().getNodeId() + item.getStatusCode());
             }
         }
     }
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
index fac67d8..a17548e 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/configuration/SpOpcUaConfig.java
@@ -53,10 +53,11 @@ public class SpOpcUaConfig {
   public SpOpcUaConfig(String opcServerURL,
                        int namespaceIndex,
                        String nodeId,
-                       int pullIntervalMilliSeconds,
+                       Integer pullIntervalMilliSeconds,
                        List<String> selectedNodeNames) {
 
     this.opcServerURL = opcServerURL;
+    this.namespaceIndex = namespaceIndex;
     this.unauthenticated = true;
     this.pullIntervalMilliSeconds = pullIntervalMilliSeconds;
     this.selectedNodeNames = selectedNodeNames;
@@ -83,7 +84,7 @@ public class SpOpcUaConfig {
                        int opcServerPort,
                        int namespaceIndex,
                        String nodeId,
-                       int pullIntervalMilliSeconds,
+                       Integer pullIntervalMilliSeconds,
                        List<String> selectedNodeNames) {
     this( opcServer + ":" + opcServerPort, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
   }
@@ -104,7 +105,7 @@ public class SpOpcUaConfig {
                        String nodeId,
                        String username,
                        String password,
-                       int pullIntervalMilliSeconds,
+                       Integer pullIntervalMilliSeconds,
                        List<String> selectedNodeNames) {
     this(opcServerURL, namespaceIndex, nodeId, pullIntervalMilliSeconds, selectedNodeNames);
     this.unauthenticated = false;
diff --git a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
index e846793..7ad4573 100644
--- a/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
+++ b/streampipes-extensions/streampipes-connect-adapters-iiot/src/main/java/org/apache/streampipes/connect/iiot/adapters/opcua/utils/OpcUaUtil.java
@@ -29,7 +29,6 @@ import org.apache.streampipes.model.connect.guess.GuessSchema;
 import org.apache.streampipes.model.schema.EventProperty;
 import org.apache.streampipes.model.schema.EventSchema;
 import org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputStaticProperty;
-import org.apache.streampipes.model.staticproperty.TreeInputNode;
 import org.apache.streampipes.sdk.builder.PrimitivePropertyBuilder;
 import org.apache.streampipes.sdk.extractor.StaticPropertyExtractor;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
@@ -77,7 +76,7 @@ public class OpcUaUtil {
         try {
             spOpcUaClient.connect();
             OpcUaNodeBrowser nodeBrowser = new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
-            List<OpcNode> selectedNodes = nodeBrowser.browseNode(true);
+            List<OpcNode> selectedNodes = nodeBrowser.findNodes();
 
             if (!selectedNodes.isEmpty()) {
                 for (OpcNode opcNode : selectedNodes) {
@@ -107,7 +106,6 @@ public class OpcUaUtil {
         guessSchema.setEventSchema(eventSchema);
 
         return guessSchema;
-
     }
 
 
@@ -131,34 +129,18 @@ public class OpcUaUtil {
         }
 
         SpOpcUaClient spOpcUaClient = new SpOpcUaClient(SpOpcUaConfigBuilder.from(parameterExtractor));
-
-        List<TreeInputNode> nodeOptions = new ArrayList<>();
         try{
-            spOpcUaClient.connect();
+            spOpcUaClient.connect();;
             OpcUaNodeBrowser nodeBrowser = new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
-            for(OpcNode opcNode: nodeBrowser.browseNode(false)) {
-                TreeInputNode node = makeTreeInputNode(opcNode);
-                nodeOptions.add(node);
-            }
-
+            config.setNodes(nodeBrowser.buildNodeTreeFromOrigin());
             spOpcUaClient.disconnect();
         } catch (Exception e) {
             e.printStackTrace();
         }
 
-        config.setNodes(nodeOptions);
-
         return config;
     }
 
-    private static TreeInputNode makeTreeInputNode(OpcNode opcNode) {
-        TreeInputNode node = new TreeInputNode();
-        node.setNodeName(opcNode.getLabel());
-        node.setInternalNodeName(opcNode.getNodeId().getIdentifier().toString());
-        node.setDataNode(true);
-        return node;
-    }
-
     public static String getRuntimeNameOfNode(NodeId nodeId) {
         String[] keys = nodeId.getIdentifier().toString().split("\\.");
         String key;
@@ -187,9 +169,6 @@ public class OpcUaUtil {
                throw new AdapterException("Could not guess schema for opc node! " + e.getMessage());
             }
         }
-
-
-
     }
 
     /***