Repository: apache/streampipes
Pull request #3352 (2024-11-25 14:42:38)

Move default refresh interval settings to time selection service
<!--
  ~ 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.
  ~
  -->

  <!--
Thanks for contributing! Here are some tips you can follow to help us incorporate your contribution quickly and easily:
1. If this is your first time, please read our contributor guidelines:
    - https://streampipes.apache.org/community/get-involved/
    - https://cwiki.apache.org/confluence/display/STREAMPIPES/Getting+Started
2. Make sure the PR title is formatted like: `[#<GitHub issue id>] PR title ...`
3. If the PR is unfinished, add '[WIP]' in your PR title, e.g., `[WIP][#<GitHub issue id>] PR title ...`.
4. Please write your PR title to summarize what this PR proposes/fixes.
5. Link the PR to the corresponding GitHub issue (if present) in the `Development` section in the right menu bar.
6. Be sure to keep the PR description updated to reflect all changes.
7. If possible, provide a concise example to reproduce the issue for a faster review.
8. Make sure tests pass via `mvn clean install`.
9. (Optional) If the contribution is large, please file an Apache ICLA
    - http://apache.org/licenses/icla.pdf
-->

### Purpose
<!--
Please clarify what changes you are proposing and describe how those changes will address the issue.
Furthermore, describe potential consequences the changes might have.
--> The default options for the refresh interval settings are moved to the time selection service.

### Remarks
<!--
Is there anything left we need to pay attention on?
Are there some references that might be important? E.g. links to Confluence, or discussions
on the mailing list or GitHub.
-->
PR introduces (a) breaking change(s): no

PR introduces (a) deprecation(s): no



Patch:
diff --git a/pom.xml b/pom.xml
index c1285c0a95..8f0d8ffded 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
         <amqp-client.version>5.21.0</amqp-client.version>
         <apache-sis-referencing.version>1.2</apache-sis-referencing.version>
         <boofcv.version>1.1.0</boofcv.version>
+        <bcprov-jdk18on.version>1.78.1</bcprov-jdk18on.version>
         <classindex.version>3.9</classindex.version>
         <checker-qual.version>3.43.0</checker-qual.version>
         <commons-codec.version>1.17.0</commons-codec.version>
@@ -54,7 +55,7 @@
         <commons-pool2.version>2.12.0</commons-pool2.version>
         <commons-text.version>1.12.0</commons-text.version>
         <ditto-client.version>1.0.0</ditto-client.version>
-        <eclipse.milo.version>0.6.9</eclipse.milo.version>
+        <eclipse.milo.version>0.6.14</eclipse.milo.version>
         <file-management.version>3.1.0</file-management.version>
         <flink.version>1.13.5</flink.version>
         <fogsy-qudt.version>1.0</fogsy-qudt.version>
@@ -398,6 +399,16 @@
                     </exclusion>
                 </exclusions>
             </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcprov-jdk18on</artifactId>
+                <version>${bcprov-jdk18on.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.bouncycastle</groupId>
+                <artifactId>bcutil-jdk18on</artifactId>
+                <version>${bcprov-jdk18on.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.eclipse.rdf4j</groupId>
                 <artifactId>rdf4j-rio-turtle</artifactId>
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index 7ad56fe402..7a2d033e5a 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -108,7 +108,18 @@ public enum Envs {
 
   // expects a comma separated string of service names
   SP_SERVICE_TAGS("SP_SERVICE_TAGS", ""),
-  SP_ALLOWED_UPLOAD_FILETYPES("SP_ALLOWED_UPLOAD_FILETYPES", "", "");
+  SP_ALLOWED_UPLOAD_FILETYPES("SP_ALLOWED_UPLOAD_FILETYPES", "", ""),
+
+  // OPC-UA security
+  SP_OPCUA_SECURITY_DIR("SP_OPCUA_SECURITY_DIR", "/streampipes-security/opcua"),
+  SP_OPCUA_KEYSTORE_FILE("SP_OPCUA_KEYSTORE_FILE", "keystore.pfx"),
+  SP_OPCUA_KEYSTORE_PASSWORD("SP_OPCUA_KEYSTORE_PASSWORD", "password"),
+  SP_OPCUA_KEYSTORE_TYPE("SP_OPCUA_KEYSTORE_TYPE", "PKCS12"),
+  SP_OPCUA_KEYSTORE_ALIAS("SP_OPCUA_KEYSTORE_ALIAS", "apache-streampipes"),
+  SP_OPCUA_APPLICATION_URI(
+      "SP_OPCUA_APPLICATION_URI",
+      "urn:org:apache:streampipes:opcua:client"
+  );
 
   private final String envVariableName;
   private String defaultValue;
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
index 36ed402360..bb5bbac193 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/DefaultEnvironment.java
@@ -328,4 +328,33 @@ public class DefaultEnvironment implements Environment {
     return new StringEnvironmentVariable(Envs.SP_ALLOWED_UPLOAD_FILETYPES);
   }
 
+  @Override
+  public StringEnvironmentVariable getOpcUaSecurityDir() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_SECURITY_DIR);
+  }
+
+  @Override
+  public StringEnvironmentVariable getOpcUaKeystoreFile() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_KEYSTORE_FILE);
+  }
+
+  @Override
+  public StringEnvironmentVariable getOpcUaKeystorePassword() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_KEYSTORE_PASSWORD);
+  }
+
+  @Override
+  public StringEnvironmentVariable getOpcUaApplicationUri() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_APPLICATION_URI);
+  }
+
+  @Override
+  public StringEnvironmentVariable getOPcUaKeystoreType() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_KEYSTORE_TYPE);
+  }
+
+  @Override
+  public StringEnvironmentVariable getOpcUaKeystoreAlias() {
+    return new StringEnvironmentVariable(Envs.SP_OPCUA_KEYSTORE_ALIAS);
+  }
 }
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
index d1d6efab67..cb441f009b 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/environment/Environment.java
@@ -36,7 +36,9 @@ public interface Environment {
   IntEnvironmentVariable getServicePort();
 
   StringEnvironmentVariable getSpCoreScheme();
+
   StringEnvironmentVariable getSpCoreHost();
+
   IntEnvironmentVariable getSpCorePort();
 
   // Time series storage env variables
@@ -144,12 +146,15 @@ public interface Environment {
 
   // Broker defaults
   StringEnvironmentVariable getKafkaHost();
+
   IntEnvironmentVariable getKafkaPort();
 
   StringEnvironmentVariable getMqttHost();
+
   IntEnvironmentVariable getMqttPort();
 
   StringEnvironmentVariable getNatsHost();
+
   IntEnvironmentVariable getNatsPort();
 
   StringEnvironmentVariable getPulsarUrl();
@@ -158,4 +163,15 @@ public interface Environment {
 
   StringEnvironmentVariable getAllowedUploadFiletypes();
 
+  StringEnvironmentVariable getOpcUaSecurityDir();
+
+  StringEnvironmentVariable getOpcUaKeystoreFile();
+
+  StringEnvironmentVariable getOpcUaKeystorePassword();
+
+  StringEnvironmentVariable getOpcUaApplicationUri();
+
+  StringEnvironmentVariable getOPcUaKeystoreType();
+
+  StringEnvironmentVariable getOpcUaKeystoreAlias();
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/pom.xml b/streampipes-extensions/streampipes-connectors-opcua/pom.xml
index 71eaf09917..673e6858c6 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/pom.xml
+++ b/streampipes-extensions/streampipes-connectors-opcua/pom.xml
@@ -21,9 +21,9 @@
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.apache.streampipes</groupId>
-        <artifactId>streampipes-parent</artifactId>
+        <artifactId>streampipes-extensions</artifactId>
         <version>0.97.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
+        <relativePath>../pom.xml</relativePath>
     </parent>
 
     <artifactId>streampipes-connectors-opcua</artifactId>
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
index 63aacdab58..03fc8448a4 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/OpcUaConnectorsModuleExport.java
@@ -23,25 +23,35 @@ import org.apache.streampipes.extensions.api.declarer.IExtensionModuleExport;
 import org.apache.streampipes.extensions.api.migration.IModelMigrator;
 import org.apache.streampipes.extensions.api.pe.IStreamPipesPipelineElement;
 import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
 import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV1;
 import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV2;
 import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV3;
+import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaAdapterMigrationV4;
+import org.apache.streampipes.extensions.connectors.opcua.migration.OpcUaSinkMigrationV1;
 import org.apache.streampipes.extensions.connectors.opcua.sink.OpcUaSink;
 
 import java.util.List;
 
 public class OpcUaConnectorsModuleExport implements IExtensionModuleExport {
+
+  private final OpcUaClientProvider clientProvider;
+
+  public OpcUaConnectorsModuleExport() {
+    this.clientProvider = new OpcUaClientProvider();
+  }
+
   @Override
   public List<StreamPipesAdapter> adapters() {
     return List.of(
-        new OpcUaAdapter()
+        new OpcUaAdapter(clientProvider)
     );
   }
 
   @Override
   public List<IStreamPipesPipelineElement<?>> pipelineElements() {
     return List.of(
-        new OpcUaSink()
+        new OpcUaSink(clientProvider)
     );
   }
 
@@ -50,7 +60,9 @@ public class OpcUaConnectorsModuleExport implements IExtensionModuleExport {
     return List.of(
         new OpcUaAdapterMigrationV1(),
         new OpcUaAdapterMigrationV2(),
-        new OpcUaAdapterMigrationV3()
+        new OpcUaAdapterMigrationV3(),
+        new OpcUaAdapterMigrationV4(),
+        new OpcUaSinkMigrationV1()
     );
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
index baf5cfe95b..526ff7bb24 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaAdapter.java
@@ -29,7 +29,8 @@ import org.apache.streampipes.extensions.api.connect.context.IAdapterRuntimeCont
 import org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtractor;
 import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
 import org.apache.streampipes.extensions.api.runtime.SupportsRuntimeConfig;
-import org.apache.streampipes.extensions.connectors.opcua.client.SpOpcUaClient;
+import org.apache.streampipes.extensions.connectors.opcua.client.ConnectedOpcUaClient;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
 import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaAdapterConfig;
 import org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration;
 import org.apache.streampipes.extensions.connectors.opcua.config.SpOpcUaConfigExtractor;
@@ -42,7 +43,6 @@ import org.apache.streampipes.model.connect.guess.GuessSchema;
 import org.apache.streampipes.model.connect.rules.schema.DeleteRuleDescription;
 import org.apache.streampipes.model.extensions.ExtensionAssetType;
 import org.apache.streampipes.model.staticproperty.StaticProperty;
-import org.apache.streampipes.sdk.StaticProperties;
 import org.apache.streampipes.sdk.builder.adapter.AdapterConfigurationBuilder;
 import org.apache.streampipes.sdk.helpers.Alternatives;
 import org.apache.streampipes.sdk.helpers.Labels;
@@ -66,7 +66,6 @@ import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
 
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ADAPTER_TYPE;
-import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULLING_INTERVAL;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULL_MODE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.SUBSCRIPTION_MODE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaUtil.getSchema;
@@ -78,7 +77,9 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   private static final Logger LOG = LoggerFactory.getLogger(OpcUaAdapter.class);
 
   private int pullingIntervalMilliSeconds;
-  private SpOpcUaClient<OpcUaAdapterConfig> spOpcUaClient;
+  private final OpcUaClientProvider clientProvider;
+  private ConnectedOpcUaClient connectedClient;
+  private OpcUaAdapterConfig opcUaAdapterConfig;
   private List<OpcNode> allNodes;
   private List<NodeId> allNodeIds;
   private int numberProperties;
@@ -92,15 +93,14 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
    */
   private final Map<String, String> nodeIdToLabelMapping;
 
-  public OpcUaAdapter() {
-    super();
+  public OpcUaAdapter(OpcUaClientProvider clientProvider) {
+    this.clientProvider = clientProvider;
     this.numberProperties = 0;
     this.event = new HashMap<>();
     this.nodeIdToLabelMapping = new HashMap<>();
   }
 
   private void prepareAdapter(IAdapterParameterExtractor extractor) throws AdapterException {
-
     this.allNodeIds = new ArrayList<>();
     List<String> deleteKeys = extractor
         .getAdapterDescription()
@@ -111,9 +111,9 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
         .collect(Collectors.toList());
 
     try {
-      this.spOpcUaClient.connect();
+      this.connectedClient = clientProvider.getClient(this.opcUaAdapterConfig);
       OpcUaNodeBrowser browserClient =
-          new OpcUaNodeBrowser(this.spOpcUaClient.getClient(), this.spOpcUaClient.getSpOpcConfig());
+          new OpcUaNodeBrowser(this.connectedClient.getClient(), this.opcUaAdapterConfig);
       this.allNodes = browserClient.findNodes(deleteKeys);
 
 
@@ -121,11 +121,11 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
         this.allNodeIds.add(node.getNodeId());
       }
 
-      if (spOpcUaClient.getSpOpcConfig().inPullMode()) {
-        this.pullingIntervalMilliSeconds = spOpcUaClient.getSpOpcConfig().getPullIntervalMilliSeconds();
+      if (opcUaAdapterConfig.inPullMode()) {
+        this.pullingIntervalMilliSeconds = opcUaAdapterConfig.getPullIntervalMilliSeconds();
       } else {
         this.numberProperties = this.allNodeIds.size();
-        this.spOpcUaClient.createListSubscription(this.allNodeIds, this);
+        this.connectedClient.createListSubscription(this.allNodeIds, this);
       }
 
       this.allNodes.forEach(node -> this.nodeIdToLabelMapping.put(node.getNodeId().toString(), node.getLabel()));
@@ -139,7 +139,7 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   @Override
   public void pullData() throws ExecutionException, RuntimeException, InterruptedException, TimeoutException {
     var response =
-        this.spOpcUaClient.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
+        this.connectedClient.getClient().readValues(0, TimestampsToReturn.Both, this.allNodeIds);
     boolean badStatusCodeReceived = false;
     boolean emptyValueReceived = false;
     List<DataValue> returnValues =
@@ -168,7 +168,7 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
 
   private boolean shouldSkipEvent(boolean badStatusCodeReceived) {
     return badStatusCodeReceived
-        && this.spOpcUaClient.getSpOpcConfig().getIncompleteEventStrategy()
+        && this.opcUaAdapterConfig.getIncompleteEventStrategy()
         .equalsIgnoreCase(SharedUserConfiguration.INCOMPLETE_OPTION_IGNORE);
   }
 
@@ -208,13 +208,13 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   public void onAdapterStarted(IAdapterParameterExtractor extractor,
                                IEventCollector collector,
                                IAdapterRuntimeContext adapterRuntimeContext) throws AdapterException {
-    this.spOpcUaClient = new SpOpcUaClient<>(
-        SpOpcUaConfigExtractor.extractAdapterConfig(extractor.getStaticPropertyExtractor())
-    );
+    this.opcUaAdapterConfig =
+        SpOpcUaConfigExtractor.extractAdapterConfig(extractor.getStaticPropertyExtractor());
+    //this.connectedClient = clientProvider.getClient(this.opcUaAdapterConfig);
     this.collector = collector;
     this.prepareAdapter(extractor);
 
-    if (this.spOpcUaClient.getSpOpcConfig().inPullMode()) {
+    if (this.opcUaAdapterConfig.inPullMode()) {
       this.pullAdapterScheduler = new PullAdapterScheduler();
       this.pullAdapterScheduler.schedule(this, extractor.getAdapterDescription().getElementId());
     }
@@ -223,9 +223,9 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   @Override
   public void onAdapterStopped(IAdapterParameterExtractor extractor,
                                IAdapterRuntimeContext adapterRuntimeContext) throws AdapterException {
-    this.spOpcUaClient.disconnect();
+    clientProvider.releaseClient(this.opcUaAdapterConfig);
 
-    if (this.spOpcUaClient.getSpOpcConfig().inPullMode()) {
+    if (this.opcUaAdapterConfig.inPullMode()) {
       this.pullAdapterScheduler.shutdown();
     }
   }
@@ -233,12 +233,12 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   @Override
   public StaticProperty resolveConfiguration(String staticPropertyInternalName,
                                              IStaticPropertyExtractor extractor) throws SpConfigurationException {
-    return OpcUaUtil.resolveConfig(staticPropertyInternalName, extractor);
+    return OpcUaUtil.resolveConfig(clientProvider, staticPropertyInternalName, extractor);
   }
 
   @Override
   public IAdapterConfiguration declareConfig() {
-    var builder = AdapterConfigurationBuilder.create(ID, 3, OpcUaAdapter::new)
+    var builder = AdapterConfigurationBuilder.create(ID, 4, () -> new OpcUaAdapter(clientProvider))
         .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .withLocales(Locales.EN)
         .withCategory(AdapterType.Generic, AdapterType.Manufacturing)
@@ -255,6 +255,6 @@ public class OpcUaAdapter implements StreamPipesAdapter, IPullAdapter, SupportsR
   @Override
   public GuessSchema onSchemaRequested(IAdapterParameterExtractor extractor,
                                        IAdapterGuessSchemaContext adapterGuessSchemaContext) throws AdapterException {
-    return getSchema(extractor);
+    return getSchema(clientProvider, extractor);
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
index e7dde9d990..2a150fa355 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeBrowser.java
@@ -25,6 +25,7 @@ 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.sdk.client.api.UaClient;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
 import org.eclipse.milo.opcua.stack.core.Identifiers;
@@ -45,13 +46,13 @@ import java.util.stream.Collectors;
 
 public class OpcUaNodeBrowser {
 
-  private final OpcUaClient client;
+  private final UaClient client;
   private final OpcUaConfig spOpcConfig;
 
   private static final Logger LOG = LoggerFactory.getLogger(OpcUaNodeBrowser.class);
 
   public OpcUaNodeBrowser(
-      OpcUaClient client,
+      UaClient client,
       OpcUaConfig spOpcUaClientConfig
   ) {
     this.client = client;
@@ -127,7 +128,7 @@ public class OpcUaNodeBrowser {
   }
 
   private List<TreeInputNode> findChildren(
-      OpcUaClient client,
+      UaClient client,
       NodeId nodeId
   ) throws UaException {
     return client
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
index 04dda6eb62..bcaf11a476 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/adapter/OpcUaNodeMetadataExtractor.java
@@ -18,7 +18,7 @@
 
 package org.apache.streampipes.extensions.connectors.opcua.adapter;
 
-import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.UaClient;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
 import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
 import org.eclipse.milo.opcua.stack.core.StatusCodes;
@@ -32,12 +32,12 @@ import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
 public class OpcUaNodeMetadataExtractor {
-  private final OpcUaClient client;
+  private final UaClient client;
   private final UaNode node;
 
   private final Map<String, Object> metadata;
 
-  public OpcUaNodeMetadataExtractor(OpcUaClient client, UaNode node) {
+  public OpcUaNodeMetadataExtractor(UaClient client, UaNode node) {
     this.client = client;
     this.node = node;
     this.metadata = new HashMap<>();
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/ConnectedOpcUaClient.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/ConnectedOpcUaClient.java
new file mode 100644
index 0000000000..d71d85646f
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/ConnectedOpcUaClient.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.client;
+
+import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+
+import org.eclipse.milo.opcua.sdk.client.api.UaClient;
+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.sdk.client.api.subscriptions.UaSubscriptionManager;
+import org.eclipse.milo.opcua.stack.core.AttributeId;
+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.StatusCode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+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.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+
+public class ConnectedOpcUaClient {
+
+  private static final Logger LOG = LoggerFactory.getLogger(ConnectedOpcUaClient.class);
+  private final UaClient client;
+  private static final AtomicLong clientHandles = new AtomicLong(1L);
+
+  public ConnectedOpcUaClient(UaClient client) {
+    this.client = client;
+  }
+
+  /***
+   * 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}
+   * @throws Exception
+   */
+  public void createListSubscription(List<NodeId> nodes,
+                                     OpcUaAdapter opcUaAdapter) throws Exception {
+    client.getSubscriptionManager().addSubscriptionListener(new UaSubscriptionManager.SubscriptionListener() {
+      @Override
+      public void onSubscriptionTransferFailed(UaSubscription subscription, StatusCode statusCode) {
+        LOG.warn("Transfer for subscriptionId={} failed: {}", subscription.getSubscriptionId(), statusCode);
+        try {
+          initSubscription(nodes, opcUaAdapter);
+        } catch (Exception e) {
+          LOG.error("Re-creating the subscription failed", e);
+        }
+      }
+    });
+
+    initSubscription(nodes, opcUaAdapter);
+  }
+
+
+  public void initSubscription(List<NodeId> nodes,
+                               OpcUaAdapter opcUaAdapter) throws Exception {
+    /*
+     * create a subscription @ 1000ms
+     */
+    UaSubscription subscription = this.client.getSubscriptionManager().createSubscription(1000.0).get();
+
+    List<CompletableFuture<DataValue>> values = new ArrayList<>();
+
+    for (NodeId node : nodes) {
+      values.add(this.client.readValue(0, TimestampsToReturn.Both, node));
+    }
+
+    for (CompletableFuture<DataValue> value : values) {
+      if (value.get().getValue().toString().contains("null")) {
+        LOG.error("Node has no value");
+      }
+    }
+
+
+    List<ReadValueId> readValues = new ArrayList<>();
+    // Read a specific value attribute
+    for (NodeId node : nodes) {
+      readValues.add(new ReadValueId(node, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE));
+    }
+
+    List<MonitoredItemCreateRequest> requests = new ArrayList<>();
+
+    for (ReadValueId readValue : readValues) {
+      // important: client handle must be unique per item
+      UInteger clientHandle = uint(clientHandles.getAndIncrement());
+
+      MonitoringParameters parameters = new MonitoringParameters(
+          clientHandle,
+          1000.0,     // sampling interval
+          null,      // filter, null means use default
+          uint(10),   // queue size
+          true         // discard oldest
+      );
+
+      requests.add(new MonitoredItemCreateRequest(readValue, MonitoringMode.Reporting, parameters));
+    }
+
+    UaSubscription.ItemCreationCallback onItemCreated =
+        (item, i) -> item.setValueConsumer(opcUaAdapter::onSubscriptionValue);
+    List<UaMonitoredItem> items = subscription.createMonitoredItems(
+        TimestampsToReturn.Both,
+        requests,
+        onItemCreated
+    ).get();
+
+    for (UaMonitoredItem item : items) {
+      NodeId tagId = item.getReadValueId().getNodeId();
+      if (item.getStatusCode().isGood()) {
+        LOG.info("item created for nodeId=" + tagId);
+      } else {
+        LOG.error("failed to create item for " + item.getReadValueId().getNodeId() + item.getStatusCode());
+      }
+    }
+  }
+
+  /***
+   *
+   * @return current {@link org.eclipse.milo.opcua.sdk.client.OpcUaClient}
+   */
+  public UaClient getClient() {
+    return this.client;
+  }
+
+  public void disconnect() {
+    client.disconnect();
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
new file mode 100644
index 0000000000..b3f907bd4e
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/OpcUaClientProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.client;
+
+import org.apache.streampipes.commons.exceptions.SpConfigurationException;
+import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
+
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+
+public class OpcUaClientProvider {
+
+  private static final Logger LOG = LoggerFactory.getLogger(OpcUaClientProvider.class);
+
+  private final Map<String, ConnectedOpcUaClient> clients = new ConcurrentHashMap<>();
+  private final Map<String, Integer> consumers = new ConcurrentHashMap<>();
+
+  public synchronized <T extends OpcUaConfig> ConnectedOpcUaClient getClient(T config)
+      throws UaException, SpConfigurationException, URISyntaxException, ExecutionException, InterruptedException {
+    var serverId = config.getUniqueServerId();
+    if (clients.containsKey(serverId)) {
+      LOG.debug("Adding new consumer to client {}", serverId);
+      consumers.put(serverId, consumers.get(config.getUniqueServerId()) + 1);
+      return clients.get(serverId);
+    } else {
+      LOG.debug("Creating new client {}", serverId);
+      var connectedClient = new SpOpcUaClient<>(config).connect();
+      clients.put(serverId, connectedClient);
+      consumers.put(serverId, 1);
+      return connectedClient;
+    }
+  }
+
+  public <T extends OpcUaConfig> void releaseClient(T config) {
+    String serverId = config.getUniqueServerId();
+    LOG.debug("Releasing client {}", serverId);
+
+    synchronized (this) {
+      consumers.computeIfPresent(serverId, (key, count) -> {
+        int updatedCount = count - 1;
+        if (updatedCount <= 0) {
+          LOG.debug("Disconnecting client {}", serverId);
+          if (clients.containsKey(serverId)) {
+            clients.get(serverId).disconnect();
+            clients.remove(serverId);
+          }
+          return null;
+        }
+        return updatedCount;
+      });
+    }
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/SpOpcUaClient.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/SpOpcUaClient.java
index 95fda81dbd..a17d373b66 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/SpOpcUaClient.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/client/SpOpcUaClient.java
@@ -20,38 +20,17 @@ package org.apache.streampipes.extensions.connectors.opcua.client;
 
 
 import org.apache.streampipes.commons.exceptions.SpConfigurationException;
-import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
 import org.apache.streampipes.extensions.connectors.opcua.config.MiloOpcUaConfigurationProvider;
 import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
 
 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.sdk.client.api.subscriptions.UaSubscriptionManager;
-import org.eclipse.milo.opcua.stack.core.AttributeId;
 import org.eclipse.milo.opcua.stack.core.UaException;
-import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
-import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
-import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
-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.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.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.atomic.AtomicLong;
-
-import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
 
 /***
  * Wrapper class for all OPC UA specific stuff.
@@ -63,120 +42,26 @@ public class SpOpcUaClient<T extends OpcUaConfig> {
   private OpcUaClient client;
   private final T spOpcConfig;
 
-  private static final AtomicLong clientHandles = new AtomicLong(1L);
-
   public SpOpcUaClient(T config) {
     this.spOpcConfig = config;
   }
 
-  /***
-   *
-   * @return current {@link org.eclipse.milo.opcua.sdk.client.OpcUaClient}
-   */
-  public OpcUaClient getClient() {
-    return this.client;
-  }
-
   /***
    * Establishes appropriate connection to OPC UA endpoint depending on the {@link SpOpcUaClient} instance
    *
    * @throws UaException An exception occurring during OPC connection
    */
-  public void connect()
+  public ConnectedOpcUaClient connect()
       throws UaException, ExecutionException, InterruptedException, SpConfigurationException, URISyntaxException {
     OpcUaClientConfig clientConfig = new MiloOpcUaConfigurationProvider().makeClientConfig(spOpcConfig);
-    this.client = OpcUaClient.create(clientConfig);
+    var client = OpcUaClient.create(clientConfig);
     client.connect().get();
+    return new ConnectedOpcUaClient(client);
   }
 
-  public void disconnect() {
-    client.disconnect();
-  }
-
-  /***
-   * 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}
-   * @throws Exception
-   */
-  public void createListSubscription(List<NodeId> nodes,
-                                     OpcUaAdapter opcUaAdapter) throws Exception {
-    client.getSubscriptionManager().addSubscriptionListener(new UaSubscriptionManager.SubscriptionListener() {
-      @Override
-      public void onSubscriptionTransferFailed(UaSubscription subscription, StatusCode statusCode) {
-        LOG.warn("Transfer for subscriptionId={} failed: {}", subscription.getSubscriptionId(), statusCode);
-        try {
-          initSubscription(nodes, opcUaAdapter);
-        } catch (Exception e) {
-          LOG.error("Re-creating the subscription failed", e);
-        }
-      }
-    });
-
-    initSubscription(nodes, opcUaAdapter);
-  }
-
-
-  public void initSubscription(List<NodeId> nodes,
-                               OpcUaAdapter opcUaAdapter) throws Exception {
-    /*
-     * create a subscription @ 1000ms
-     */
-    UaSubscription subscription = this.client.getSubscriptionManager().createSubscription(1000.0).get();
-
-    List<CompletableFuture<DataValue>> values = new ArrayList<>();
-
-    for (NodeId node : nodes) {
-      values.add(this.client.readValue(0, TimestampsToReturn.Both, node));
-    }
-
-    for (CompletableFuture<DataValue> value : values) {
-      if (value.get().getValue().toString().contains("null")) {
-        LOG.error("Node has no value");
-      }
-    }
 
 
-    List<ReadValueId> readValues = new ArrayList<>();
-    // Read a specific value attribute
-    for (NodeId node : nodes) {
-      readValues.add(new ReadValueId(node, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE));
-    }
 
-    List<MonitoredItemCreateRequest> requests = new ArrayList<>();
-
-    for (ReadValueId readValue : readValues) {
-      // important: client handle must be unique per item
-      UInteger clientHandle = uint(clientHandles.getAndIncrement());
-
-      MonitoringParameters parameters = new MonitoringParameters(
-          clientHandle,
-          1000.0,     // sampling interval
-          null,      // filter, null means use default
-          uint(10),   // queue size
-          true         // discard oldest
-      );
-
-      requests.add(new MonitoredItemCreateRequest(readValue, MonitoringMode.Reporting, parameters));
-    }
-
-    UaSubscription.ItemCreationCallback onItemCreated =
-        (item, i) -> item.setValueConsumer(opcUaAdapter::onSubscriptionValue);
-    List<UaMonitoredItem> items = subscription.createMonitoredItems(
-        TimestampsToReturn.Both,
-        requests,
-        onItemCreated
-    ).get();
-
-    for (UaMonitoredItem item : items) {
-      NodeId tagId = item.getReadValueId().getNodeId();
-      if (item.getStatusCode().isGood()) {
-        LOG.info("item created for nodeId=" + tagId);
-      } else {
-        LOG.error("failed to create item for " + item.getReadValueId().getNodeId() + item.getStatusCode());
-      }
-    }
-  }
 
   public T getSpOpcConfig() {
     return spOpcConfig;
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
index 9813d52fbb..8db41a8e11 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/MiloOpcUaConfigurationProvider.java
@@ -18,19 +18,15 @@
 
 package org.apache.streampipes.extensions.connectors.opcua.config;
 
+import org.apache.streampipes.commons.environment.Environments;
 import org.apache.streampipes.commons.exceptions.SpConfigurationException;
 
 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;
 import java.util.concurrent.ExecutionException;
 
@@ -39,65 +35,16 @@ public class MiloOpcUaConfigurationProvider {
   public OpcUaClientConfig makeClientConfig(OpcUaConfig spOpcConfig)
       throws ExecutionException, InterruptedException, SpConfigurationException, URISyntaxException {
     String opcServerUrl = spOpcConfig.getOpcServerURL();
+    String applicationUri = Environments.getEnvironment().getOpcUaApplicationUri().getValueOrDefault();
     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 SpConfigurationException("No endpoint with security policy none"));
+    var builder = OpcUaClientConfig.builder()
+        .setApplicationName(LocalizedText.english("Apache StreamPipes"))
+        .setApplicationUri(applicationUri);
 
-    tmpEndpoint = updateEndpointUrl(tmpEndpoint, host);
-    endpoints = Collections.singletonList(tmpEndpoint);
+    spOpcConfig.getSecurityConfig().configureSecurityPolicy(opcServerUrl, endpoints, builder);
+    spOpcConfig.getIdentityConfig().configureIdentity(builder);
 
-    EndpointDescription endpoint = endpoints
-        .stream()
-        .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
-        .findFirst().orElseThrow(() -> new SpConfigurationException("no desired endpoints returned"));
-
-    return buildConfig(endpoint, spOpcConfig);
-  }
-
-  private OpcUaClientConfig buildConfig(EndpointDescription endpoint,
-                                        OpcUaConfig 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-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
index 6db027d861..128f1bc1f9 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/OpcUaConfig.java
@@ -18,15 +18,21 @@
 
 package org.apache.streampipes.extensions.connectors.opcua.config;
 
+import org.apache.streampipes.extensions.connectors.opcua.config.identity.IdentityConfig;
+import org.apache.streampipes.extensions.connectors.opcua.config.security.SecurityConfig;
+
 import java.util.List;
 
 public class OpcUaConfig {
 
   private String opcServerURL;
-  private boolean unauthenticated;
-  private String username;
-  private String password;
   private List<String> selectedNodeNames;
+  private IdentityConfig identityConfig;
+  private SecurityConfig securityPolicyConfig;
+
+  public OpcUaConfig() {
+
+  }
 
   public String getOpcServerURL() {
     return opcServerURL;
@@ -36,35 +42,31 @@ public class OpcUaConfig {
     this.opcServerURL = opcServerURL;
   }
 
-  public boolean isUnauthenticated() {
-    return unauthenticated;
-  }
-
-  public void setUnauthenticated(boolean unauthenticated) {
-    this.unauthenticated = unauthenticated;
+  public List<String> getSelectedNodeNames() {
+    return selectedNodeNames;
   }
 
-  public String getUsername() {
-    return username;
+  public void setSelectedNodeNames(List<String> selectedNodeNames) {
+    this.selectedNodeNames = selectedNodeNames;
   }
 
-  public void setUsername(String username) {
-    this.username = username;
+  public IdentityConfig getIdentityConfig() {
+    return identityConfig;
   }
 
-  public String getPassword() {
-    return password;
+  public void setIdentityConfig(IdentityConfig identityConfig) {
+    this.identityConfig = identityConfig;
   }
 
-  public void setPassword(String password) {
-    this.password = password;
+  public SecurityConfig getSecurityConfig() {
+    return securityPolicyConfig;
   }
 
-  public List<String> getSelectedNodeNames() {
-    return selectedNodeNames;
+  public void setSecurityConfig(SecurityConfig securityPolicyConfig) {
+    this.securityPolicyConfig = securityPolicyConfig;
   }
 
-  public void setSelectedNodeNames(List<String> selectedNodeNames) {
-    this.selectedNodeNames = selectedNodeNames;
+  public String getUniqueServerId() {
+    return String.format("%s-%s-%s", opcServerURL, securityPolicyConfig, identityConfig);
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
index 9b5f541071..1059ce7e38 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SharedUserConfiguration.java
@@ -18,6 +18,7 @@
 
 package org.apache.streampipes.extensions.connectors.opcua.config;
 
+import org.apache.streampipes.extensions.connectors.opcua.utils.SecurityUtils;
 import org.apache.streampipes.model.staticproperty.OneOfStaticProperty;
 import org.apache.streampipes.model.staticproperty.Option;
 import org.apache.streampipes.model.staticproperty.StaticPropertyGroup;
@@ -29,7 +30,6 @@ import org.apache.streampipes.sdk.helpers.Labels;
 import java.util.List;
 
 import static org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter.PULL_GROUP;
-import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ACCESS_MODE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ADAPTER_TYPE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.AVAILABLE_NODES;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.HOST_PORT;
@@ -41,7 +41,6 @@ import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_URL;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PASSWORD;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULLING_INTERVAL;
-import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.UNAUTHENTICATED;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME_GROUP;
 
@@ -51,14 +50,27 @@ public class SharedUserConfiguration {
   public static final String INCOMPLETE_OPTION_IGNORE = "ignore-event";
   public static final String INCOMPLETE_OPTION_SEND = "send-event";
 
+  public static final String SECURITY_MODE = "securityMode";
+  public static final String SECURITY_POLICY = "securityPolicy";
+  public static final String USER_AUTHENTICATION = "userAuthentication";
+  public static final String USER_AUTHENTICATION_ANONYMOUS = "anonymous";
+
   public static void appendSharedOpcUaConfig(AbstractConfigurablePipelineElementBuilder<?, ?> builder,
                                              boolean adapterConfig) {
 
     var dependsOn = getDependsOn(adapterConfig);
 
     builder
-        .requiredAlternatives(Labels.withId(ACCESS_MODE),
-            Alternatives.from(Labels.withId(UNAUTHENTICATED)),
+        .requiredSingleValueSelection(
+            Labels.withId(SECURITY_MODE),
+            SecurityUtils.getAvailableSecurityModes().stream().map(mode -> new Option(mode.k, mode.v)).toList()
+        )
+        .requiredSingleValueSelection(
+            Labels.withId(SECURITY_POLICY),
+            SecurityUtils.getAvailableSecurityPolicies().stream().map(p -> new Option(p.name())).toList()
+        )
+        .requiredAlternatives(Labels.withId(USER_AUTHENTICATION),
+            Alternatives.from(Labels.withId(USER_AUTHENTICATION_ANONYMOUS)),
             Alternatives.from(Labels.withId(USERNAME_GROUP),
                 StaticProperties.group(
                     Labels.withId(USERNAME_GROUP),
@@ -104,7 +116,7 @@ public class SharedUserConfiguration {
 
   public static OneOfStaticProperty getIncompleteEventConfig() {
     return StaticProperties.singleValueSelection(
-      Labels.withId(INCOMPLETE_EVENT_HANDLING_KEY),
+        Labels.withId(INCOMPLETE_EVENT_HANDLING_KEY),
         List.of(
             new Option("Ignore (only complete messages are sent)", INCOMPLETE_OPTION_IGNORE),
             new Option("Send (incomplete messages are sent)", INCOMPLETE_OPTION_SEND)
@@ -115,10 +127,12 @@ public class SharedUserConfiguration {
   public static List<String> getDependsOn(boolean adapterConfig) {
     return adapterConfig ? List.of(
         ADAPTER_TYPE.name(),
-        ACCESS_MODE.name(),
+        SECURITY_MODE,
+        SECURITY_POLICY,
         OPC_HOST_OR_URL.name()
     ) : List.of(
-        ACCESS_MODE.name(),
+        SECURITY_MODE,
+        SECURITY_POLICY,
         OPC_HOST_OR_URL.name());
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
index 6741a3e6f8..333dbadc06 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/SpOpcUaConfigExtractor.java
@@ -20,11 +20,16 @@ package org.apache.streampipes.extensions.connectors.opcua.config;
 
 import org.apache.streampipes.extensions.api.extractor.IParameterExtractor;
 import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.connectors.opcua.config.identity.AnonymousIdentityConfig;
+import org.apache.streampipes.extensions.connectors.opcua.config.identity.UsernamePasswordIdentityConfig;
+import org.apache.streampipes.extensions.connectors.opcua.config.security.SecurityConfig;
 import org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaUtil;
 
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
+
 import java.util.List;
 
-import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ACCESS_MODE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.ADAPTER_TYPE;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.AVAILABLE_NODES;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.OPC_HOST_OR_URL;
@@ -35,7 +40,6 @@ import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PASSWORD;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULLING_INTERVAL;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PULL_MODE;
-import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.UNAUTHENTICATED;
 import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME;
 
 public class SpOpcUaConfigExtractor {
@@ -71,20 +75,31 @@ public class SpOpcUaConfigExtractor {
   }
 
   public static <T extends OpcUaConfig> T extractSharedConfig(IParameterExtractor extractor,
-                                                               T config) {
+                                                              T config) {
 
     String selectedAlternativeConnection =
         extractor.selectedAlternativeInternalId(OPC_HOST_OR_URL.name());
+
     String selectedAlternativeAuthentication =
-        extractor.selectedAlternativeInternalId(ACCESS_MODE.name());
+        extractor.selectedAlternativeInternalId(SharedUserConfiguration.USER_AUTHENTICATION);
+
     List<String> selectedNodeNames =
         extractor.selectedTreeNodesInternalNames(AVAILABLE_NODES.name(), String.class);
-
     config.setSelectedNodeNames(selectedNodeNames);
 
-    boolean useURL = selectedAlternativeConnection.equals(OPC_URL.name());
-    boolean unauthenticated = selectedAlternativeAuthentication.equals(UNAUTHENTICATED.name());
+    String selectedSecurityMode = extractor.selectedSingleValueInternalName(
+        SharedUserConfiguration.SECURITY_MODE,
+        String.class
+    );
+    String selectedSecurityPolicy = extractor.selectedSingleValue(
+        SharedUserConfiguration.SECURITY_POLICY,
+        String.class
+    );
+    config.setSecurityConfig(new SecurityConfig(
+        MessageSecurityMode.valueOf(selectedSecurityMode),
+        SecurityPolicy.valueOf(selectedSecurityPolicy)));
 
+    boolean useURL = selectedAlternativeConnection.equals(OPC_URL.name());
     if (useURL) {
       String serverAddress =
           extractor.singleValueParameter(OPC_SERVER_URL.name(), String.class);
@@ -97,15 +112,15 @@ public class SpOpcUaConfigExtractor {
       config.setOpcServerURL(serverAddress + ":" + port);
     }
 
+    boolean unauthenticated = selectedAlternativeAuthentication.equals(
+        SharedUserConfiguration.USER_AUTHENTICATION_ANONYMOUS
+    );
     if (unauthenticated) {
-      config.setUnauthenticated(true);
+      config.setIdentityConfig(new AnonymousIdentityConfig());
     } else {
       String username = extractor.singleValueParameter(USERNAME.name(), String.class);
       String password = extractor.secretValue(PASSWORD.name());
-
-      config.setUsername(username);
-      config.setPassword(password);
-      config.setUnauthenticated(false);
+      config.setIdentityConfig(new UsernamePasswordIdentityConfig(username, password));
     }
 
     return config;
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/AnonymousIdentityConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/AnonymousIdentityConfig.java
new file mode 100644
index 0000000000..ecaad661df
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/AnonymousIdentityConfig.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.config.identity;
+
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
+
+public class AnonymousIdentityConfig implements IdentityConfig {
+
+  @Override
+  public void configureIdentity(OpcUaClientConfigBuilder builder) {
+    builder.setIdentityProvider(new AnonymousProvider());
+  }
+
+  @Override
+  public String toString() {
+    return "anonymous";
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/IdentityConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/IdentityConfig.java
new file mode 100644
index 0000000000..db51fd3449
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/IdentityConfig.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.config.identity;
+
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+
+public interface IdentityConfig {
+
+  void configureIdentity(OpcUaClientConfigBuilder builder);
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/UsernamePasswordIdentityConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/UsernamePasswordIdentityConfig.java
new file mode 100644
index 0000000000..825fe1fd9a
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/identity/UsernamePasswordIdentityConfig.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.config.identity;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
+
+public class UsernamePasswordIdentityConfig implements IdentityConfig {
+
+  private final String username;
+  private final String password;
+
+  public UsernamePasswordIdentityConfig(String username, String password) {
+    this.username = username;
+    this.password = password;
+  }
+
+  @Override
+  public void configureIdentity(OpcUaClientConfigBuilder builder) {
+    builder.setIdentityProvider(new UsernameProvider(username, password));
+  }
+
+  @Override
+  public String toString() {
+    return String.format("%s-%S", username, DigestUtils.sha256Hex(password));
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/KeyStoreLoader.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/KeyStoreLoader.java
new file mode 100644
index 0000000000..4b132d19f8
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/KeyStoreLoader.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.config.security;
+
+import org.apache.streampipes.commons.environment.Environment;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class KeyStoreLoader {
+
+  private static final Logger LOG = LoggerFactory.getLogger(KeyStoreLoader.class);
+
+  private X509Certificate[] clientCertificateChain;
+  private X509Certificate clientCertificate;
+  private KeyPair clientKeyPair;
+
+  public KeyStoreLoader load(Environment env,
+                             Path securityDir) throws Exception {
+    var keystore = KeyStore.getInstance(env.getOPcUaKeystoreType().getValueOrDefault());
+    var keystoreFile = env.getOpcUaKeystoreFile().getValueOrDefault();
+    var keystorePassword = env.getOpcUaKeystorePassword().getValueOrDefault();
+    var keystoreAlias = env.getOpcUaKeystoreAlias().getValueOrDefault();
+    Path serverKeystore = securityDir.resolve(keystoreFile);
+    char[] serverKeyStorePassword = keystorePassword.toCharArray();
+
+    LOG.info("Loading KeyStore at {}", serverKeystore);
+
+    try (InputStream in = Files.newInputStream(serverKeystore)) {
+      keystore.load(in, serverKeyStorePassword);
+    }
+
+    Key clientPrivateKey = keystore.getKey(keystoreAlias, serverKeyStorePassword);
+    if (clientPrivateKey instanceof PrivateKey) {
+      clientCertificate = (X509Certificate) keystore.getCertificate(keystoreAlias);
+
+      clientCertificateChain = Arrays.stream(keystore.getCertificateChain(keystoreAlias))
+          .map(X509Certificate.class::cast)
+          .toArray(X509Certificate[]::new);
+
+      PublicKey serverPublicKey = clientCertificate.getPublicKey();
+      clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) clientPrivateKey);
+    }
+
+    return this;
+  }
+
+  public X509Certificate getClientCertificate() {
+    return clientCertificate;
+  }
+
+  public X509Certificate[] getClientCertificateChain() {
+    return clientCertificateChain;
+  }
+
+  public KeyPair getClientKeyPair() {
+    return clientKeyPair;
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
new file mode 100644
index 0000000000..b64660ebe9
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/config/security/SecurityConfig.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.config.security;
+
+import org.apache.streampipes.commons.environment.Environments;
+import org.apache.streampipes.commons.exceptions.SpConfigurationException;
+
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
+import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator;
+import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Paths;
+import java.util.List;
+
+public class SecurityConfig {
+
+  private final MessageSecurityMode securityMode;
+  private final SecurityPolicy securityPolicy;
+
+  public SecurityConfig(MessageSecurityMode securityMode,
+                        SecurityPolicy securityPolicy) {
+    this.securityMode = securityMode;
+    this.securityPolicy = securityPolicy;
+  }
+
+  public void configureSecurityPolicy(String opcServerUrl,
+                                      List<EndpointDescription> endpoints,
+                                      OpcUaClientConfigBuilder builder)
+      throws SpConfigurationException, URISyntaxException {
+    String host = opcServerUrl.split("://")[1].split(":")[0];
+
+    EndpointDescription tmpEndpoint = endpoints
+        .stream()
+        .filter(e -> e.getSecurityMode() == securityMode)
+        .filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()))
+        .findFirst()
+        .orElseThrow(() ->
+            new SpConfigurationException("No endpoint available with security mode {} and security policy {}")
+        );
+
+    tmpEndpoint = updateEndpointUrl(tmpEndpoint, host);
+
+    if (securityMode != MessageSecurityMode.None) {
+      try {
+        var env = Environments.getEnvironment();
+        var securityDir = Paths.get(env.getOpcUaSecurityDir().getValueOrDefault());
+        var trustListManager = new DefaultTrustListManager(securityDir.resolve("pki").toFile());
+
+        var certificateValidator = new DefaultClientCertificateValidator(trustListManager);
+        var loader = new KeyStoreLoader().load(env, securityDir);
+        builder.setKeyPair(loader.getClientKeyPair());
+        builder.setCertificate(loader.getClientCertificate());
+        builder.setCertificateChain(loader.getClientCertificateChain());
+        builder.setCertificateValidator(certificateValidator);
+      } catch (Exception e) {
+        throw new SpConfigurationException(
+            "Failed to load keystore - check that all required environment variables "
+                + "are defined and the keystore exists",
+            e
+        );
+      }
+    }
+
+    builder.setEndpoint(tmpEndpoint);
+  }
+
+  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());
+  }
+
+  @Override
+  public String toString() {
+    return String.format("%s-%s", securityMode, securityPolicy);
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV4.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV4.java
new file mode 100644
index 0000000000..d4311bf6d5
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaAdapterMigrationV4.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IParameterExtractor;
+import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
+import org.apache.streampipes.extensions.api.migration.IAdapterMigrator;
+import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+import org.apache.streampipes.extensions.connectors.opcua.utils.SecurityUtils;
+import org.apache.streampipes.model.connect.adapter.AdapterDescription;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+import org.apache.streampipes.model.staticproperty.FreeTextStaticProperty;
+import org.apache.streampipes.model.staticproperty.Option;
+import org.apache.streampipes.model.staticproperty.SecretStaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticProperty;
+import org.apache.streampipes.model.staticproperty.StaticPropertyGroup;
+import org.apache.streampipes.sdk.StaticProperties;
+import org.apache.streampipes.sdk.helpers.Alternatives;
+import org.apache.streampipes.sdk.helpers.Labels;
+
+import java.util.List;
+
+import static org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.SECURITY_MODE;
+import static org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.SECURITY_POLICY;
+import static org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.USER_AUTHENTICATION;
+import static org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration.USER_AUTHENTICATION_ANONYMOUS;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.PASSWORD;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME;
+import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabels.USERNAME_GROUP;
+
+public class OpcUaAdapterMigrationV4 implements IAdapterMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+        OpcUaAdapter.ID,
+        SpServiceTagPrefix.ADAPTER,
+        3,
+        4
+    );
+  }
+
+  @Override
+  public MigrationResult<AdapterDescription> migrate(AdapterDescription element,
+                                                     IStaticPropertyExtractor extractor) throws RuntimeException {
+    var config = element.getConfig();
+    element.setConfig(migrate(config, extractor));
+
+    return MigrationResult.success(element);
+  }
+
+  public List<StaticProperty> migrate(List<StaticProperty> staticProperties,
+                                      IParameterExtractor extractor) {
+    var securityMode =
+        StaticProperties.singleValueSelection(
+            Labels.withId(SECURITY_MODE),
+            SecurityUtils.getAvailableSecurityModes().stream().map(mode -> new Option(mode.k, mode.v)).toList()
+        );
+    securityMode.getOptions().get(0).setSelected(true);
+
+    var securityPolicy = StaticProperties.singleValueSelection(
+        Labels.withId(SECURITY_POLICY),
+        SecurityUtils.getAvailableSecurityPolicies().stream().map(p -> new Option(p.name())).toList()
+    );
+    securityPolicy.getOptions().get(0).setSelected(true);
+
+    boolean anonymous = true;
+    var currentAuthSettings = extractor.selectedAlternativeInternalId(
+        "ACCESS_MODE"
+    );
+    if (currentAuthSettings.equals("USERNAME_GROUP")) {
+      anonymous = false;
+    }
+    var authentication = StaticProperties.alternatives(Labels.withId(USER_AUTHENTICATION),
+        Alternatives.from(Labels.withId(USER_AUTHENTICATION_ANONYMOUS)),
+        Alternatives.from(Labels.withId(USERNAME_GROUP),
+            StaticProperties.group(
+                Labels.withId(USERNAME_GROUP),
+                StaticProperties.stringFreeTextProperty(
+                    Labels.withId(USERNAME)),
+                StaticProperties.secretValue(Labels.withId(PASSWORD))
+            ))
+    );
+    if (anonymous) {
+      authentication.getAlternatives().get(0).setSelected(true);
+    } else {
+      authentication.getAlternatives().get(1).setSelected(true);
+      var username = extractor.singleValueParameter("USERNAME", String.class);
+      var password = extractor.secretValue("PASSWORD");
+      var group = (StaticPropertyGroup) authentication.getAlternatives().get(1).getStaticProperty();
+      ((FreeTextStaticProperty) group.getStaticProperties().get(0)).setValue(username);
+      ((SecretStaticProperty) group.getStaticProperties().get(1)).setValue(password);
+      ((SecretStaticProperty) group.getStaticProperties().get(1)).setEncrypted(false);
+    }
+
+    // remove old authentication property, add new properties for securityMode, policy and authentication options
+    staticProperties.remove(1);
+    staticProperties.add(1, securityMode);
+    staticProperties.add(2, securityPolicy);
+    staticProperties.add(3, authentication);
+
+    return staticProperties;
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV1.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV1.java
new file mode 100644
index 0000000000..bc24f4d88d
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/migration/OpcUaSinkMigrationV1.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.migration;
+
+import org.apache.streampipes.extensions.api.extractor.IDataSinkParameterExtractor;
+import org.apache.streampipes.extensions.api.migration.IDataSinkMigrator;
+import org.apache.streampipes.extensions.connectors.opcua.sink.OpcUaSink;
+import org.apache.streampipes.model.extensions.svcdiscovery.SpServiceTagPrefix;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.migration.MigrationResult;
+import org.apache.streampipes.model.migration.ModelMigratorConfig;
+
+public class OpcUaSinkMigrationV1 implements IDataSinkMigrator {
+  @Override
+  public ModelMigratorConfig config() {
+    return new ModelMigratorConfig(
+        OpcUaSink.ID,
+        SpServiceTagPrefix.DATA_SINK,
+        0,
+        1
+    );
+  }
+
+  @Override
+  public MigrationResult<DataSinkInvocation> migrate(DataSinkInvocation element,
+                                                     IDataSinkParameterExtractor extractor) throws RuntimeException {
+    var config = element.getStaticProperties();
+    var migratedConfigs = new OpcUaAdapterMigrationV4().migrate(config, extractor);
+    element.setStaticProperties(migratedConfigs);
+    return MigrationResult.success(element);
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUa.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUa.java
index 78babe0027..265fd805e5 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUa.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUa.java
@@ -19,12 +19,13 @@
 package org.apache.streampipes.extensions.connectors.opcua.sink;
 
 import org.apache.streampipes.commons.exceptions.SpRuntimeException;
-import org.apache.streampipes.extensions.connectors.opcua.client.SpOpcUaClient;
+import org.apache.streampipes.extensions.connectors.opcua.client.ConnectedOpcUaClient;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
+import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
 import org.apache.streampipes.model.runtime.Event;
 import org.apache.streampipes.model.runtime.field.PrimitiveField;
 import org.apache.streampipes.vocabulary.XSD;
 
-import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import org.eclipse.milo.opcua.stack.core.UaException;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
 import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
@@ -42,7 +43,8 @@ public class OpcUa {
 
   private static final Logger LOG = LoggerFactory.getLogger(OpcUa.class);
 
-  private OpcUaClient opcUaClient;
+  private ConnectedOpcUaClient connectedClient;
+  private final OpcUaConfig opcUaConfig;
   private OpcUaParameters params;
 
   private NodeId node;
@@ -73,24 +75,31 @@ public class OpcUa {
     compatibleDataTypes.put(String.class, new Class[]{String.class});
   }
 
-  public void onInvocation(OpcUaParameters params) throws
+  private final OpcUaClientProvider clientProvider;
+
+  public OpcUa(OpcUaClientProvider clientProvider,
+               OpcUaParameters params) {
+    this.clientProvider = clientProvider;
+    this.params = params;
+    this.opcUaConfig = params.config();
+  }
+
+  public void onInvocation() throws
       SpRuntimeException {
 
     try {
-      this.params = params;
-      this.node = NodeId.parse(params.getSelectedNode());
-      opcUaClient = new SpOpcUaClient<>(params.getConfig()).getClient();
-      opcUaClient.connect().get();
+      this.node = NodeId.parse(params.selectedNode());
+      this.connectedClient = clientProvider.getClient(opcUaConfig);
 
     } catch (Exception e) {
-      throw new SpRuntimeException("Could not connect to OPC-UA server: " + params.getConfig().getOpcServerURL());
+      throw new SpRuntimeException("Could not connect to OPC-UA server: " + params.config().getOpcServerURL());
     }
 
     // check whether input data type and target data type are compatible
     try {
-      Variant value = opcUaClient.getAddressSpace().getVariableNode(node).readValue().getValue();
+      Variant value = this.connectedClient.getClient().getAddressSpace().getVariableNode(node).readValue().getValue();
       targetDataType = value.getValue().getClass();
-      sourceDataType = XSDMatchings.get(params.getMappingPropertyType());
+      sourceDataType = XSDMatchings.get(params.mappingPropertyType());
       if (!sourceDataType.equals(targetDataType)) {
         if (Arrays.stream(compatibleDataTypes.get(sourceDataType)).noneMatch(dt -> dt.equals(targetDataType))) {
           throw new SpRuntimeException("Data Type of event of target node are not compatible");
@@ -107,40 +116,45 @@ public class OpcUa {
     Variant v = getValue(inputEvent);
 
     if (v == null) {
-      LOG.error("Mapping property type: " + this.params.getMappingPropertyType() + " is not supported");
+      LOG.error("Mapping property type: " + this.params.mappingPropertyType() + " is not supported");
     } else {
 
       DataValue value = new DataValue(v);
-      CompletableFuture<StatusCode> f = opcUaClient.writeValue(node, value);
+      CompletableFuture<StatusCode> f = this.connectedClient.getClient().writeValue(node, value);
 
       try {
         StatusCode status = f.get();
         if (status.isBad()) {
           if (status.getValue() == 0x80740000L) {
-            LOG.error("Type missmatch! Tried to write value of type: " + this.params.getMappingPropertyType()
+            LOG.error("Type missmatch! Tried to write value of type {} ", this.params.mappingPropertyType()
                 + " but server did not accept this");
           } else if (status.getValue() == 0x803B0000L) {
             LOG.error("Wrong access level. Not allowed to write to nodes");
           }
           LOG.error(
-              "Value: " + value.getValue().toString() + " could not be written to node Id: "
-                  + node.getIdentifier() + " on " + "OPC-UA server: " + params.getConfig().getOpcServerURL());
+              "Value: {} could not be written to node Id {} on OPC-UA server {}",
+              value.getValue().toString(),
+              node.getIdentifier(),
+              params.config().getOpcServerURL());
         }
       } catch (InterruptedException | ExecutionException e) {
-        LOG.error("Exception: Value: " + value.getValue().toString() + " could not be written to node Id: "
-            + node.getIdentifier() + " on " + "OPC-UA server: " + params.getConfig().getOpcServerURL());
+        LOG.error(
+            "Exception: Value {} could not be written to node Id {} on OPC_UA server {}",
+            value.getValue().toString(),
+            node.getIdentifier(),
+            params.config().getOpcServerURL());
       }
     }
   }
 
   public void onDetach() throws SpRuntimeException {
-    opcUaClient.disconnect();
+    clientProvider.releaseClient(opcUaConfig);
   }
 
   private Variant getValue(Event inputEvent) {
     Variant result = null;
     PrimitiveField propertyPrimitive =
-        inputEvent.getFieldBySelector(this.params.getMappingPropertySelector()).getAsPrimitive();
+        inputEvent.getFieldBySelector(this.params.mappingPropertySelector()).getAsPrimitive();
 
     if (targetDataType.equals(Integer.class)) {
       result = new Variant(propertyPrimitive.getAsInt());
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaParameters.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaParameters.java
index 15906af4aa..438bf154e8 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaParameters.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaParameters.java
@@ -20,36 +20,8 @@ package org.apache.streampipes.extensions.connectors.opcua.sink;
 
 import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
 
-public final class OpcUaParameters {
-  private final String selectedNode;
-  private final String mappingPropertySelector;
-  private final String mappingPropertyType;
-
-  private final OpcUaConfig config;
-
-  public OpcUaParameters(OpcUaConfig config,
-                         String mappingPropertySelector,
-                         String mappingPropertyType,
-                         String selectedNode) {
-    this.config = config;
-    this.mappingPropertySelector = mappingPropertySelector;
-    this.mappingPropertyType = mappingPropertyType;
-    this.selectedNode = selectedNode;
-  }
-
-  public String getSelectedNode() {
-    return selectedNode;
-  }
-
-  public String getMappingPropertySelector() {
-    return mappingPropertySelector;
-  }
-
-  public String getMappingPropertyType() {
-    return mappingPropertyType;
-  }
-
-  public OpcUaConfig getConfig() {
-    return config;
-  }
+public record OpcUaParameters(OpcUaConfig config,
+                              String mappingPropertySelector,
+                              String mappingPropertyType,
+                              String selectedNode) {
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
index 50a6069cdf..b81f20d49c 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/sink/OpcUaSink.java
@@ -26,6 +26,7 @@ import org.apache.streampipes.extensions.api.pe.config.IDataSinkConfiguration;
 import org.apache.streampipes.extensions.api.pe.context.EventSinkRuntimeContext;
 import org.apache.streampipes.extensions.api.pe.param.IDataSinkParameters;
 import org.apache.streampipes.extensions.api.runtime.SupportsRuntimeConfig;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
 import org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration;
 import org.apache.streampipes.extensions.connectors.opcua.config.SpOpcUaConfigExtractor;
 import org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaUtil;
@@ -45,11 +46,18 @@ import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
 
 public class OpcUaSink implements IStreamPipesDataSink, SupportsRuntimeConfig {
 
+  public static final String ID = "org.apache.streampipes.sinks.databases.jvm.opcua";
+
   private OpcUa opcUa;
+  private final OpcUaClientProvider clientProvider;
+
+  public OpcUaSink(OpcUaClientProvider clientProvider) {
+    this.clientProvider = clientProvider;
+  }
 
   @Override
   public IDataSinkConfiguration declareConfig() {
-    var builder = DataSinkBuilder.create("org.apache.streampipes.sinks.databases.jvm.opcua", 0)
+    var builder = DataSinkBuilder.create(ID, 0)
         .withLocales(Locales.EN)
         .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .category(DataSinkType.FORWARD)
@@ -62,7 +70,7 @@ public class OpcUaSink implements IStreamPipesDataSink, SupportsRuntimeConfig {
     SharedUserConfiguration.appendSharedOpcUaConfig(builder, false);
 
     return DataSinkConfiguration.create(
-        OpcUaSink::new,
+        () -> new OpcUaSink(clientProvider),
         builder.build()
     );
   }
@@ -89,8 +97,8 @@ public class OpcUaSink implements IStreamPipesDataSink, SupportsRuntimeConfig {
         config.getSelectedNodeNames().get(0)
     );
 
-    this.opcUa = new OpcUa();
-    this.opcUa.onInvocation(params);
+    this.opcUa = new OpcUa(clientProvider, params);
+    this.opcUa.onInvocation();
   }
 
   @Override
@@ -106,6 +114,6 @@ public class OpcUaSink implements IStreamPipesDataSink, SupportsRuntimeConfig {
   @Override
   public StaticProperty resolveConfiguration(String staticPropertyInternalName,
                                              IStaticPropertyExtractor extractor) throws SpConfigurationException {
-    return OpcUaUtil.resolveConfig(staticPropertyInternalName, extractor);
+    return OpcUaUtil.resolveConfig(clientProvider, staticPropertyInternalName, extractor);
   }
 }
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtil.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtil.java
index aa49a7a5f8..8cd76401f9 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtil.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/OpcUaUtil.java
@@ -25,8 +25,9 @@ import org.apache.streampipes.extensions.api.extractor.IAdapterParameterExtracto
 import org.apache.streampipes.extensions.api.extractor.IStaticPropertyExtractor;
 import org.apache.streampipes.extensions.api.runtime.ResolvesContainerProvidedOptions;
 import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaNodeBrowser;
-import org.apache.streampipes.extensions.connectors.opcua.client.SpOpcUaClient;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
 import org.apache.streampipes.extensions.connectors.opcua.config.OpcUaConfig;
+import org.apache.streampipes.extensions.connectors.opcua.config.SharedUserConfiguration;
 import org.apache.streampipes.extensions.connectors.opcua.config.SpOpcUaConfigExtractor;
 import org.apache.streampipes.extensions.connectors.opcua.model.OpcNode;
 import org.apache.streampipes.model.connect.guess.FieldStatusInfo;
@@ -37,7 +38,7 @@ import org.apache.streampipes.model.staticproperty.RuntimeResolvableTreeInputSta
 import org.apache.streampipes.sdk.builder.PrimitivePropertyBuilder;
 import org.apache.streampipes.sdk.builder.adapter.GuessSchemaBuilder;
 
-import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
+import org.eclipse.milo.opcua.sdk.client.api.UaClient;
 import org.eclipse.milo.opcua.stack.core.AttributeId;
 import org.eclipse.milo.opcua.stack.core.UaException;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
@@ -78,7 +79,8 @@ public class OpcUaUtil {
    * @throws AdapterException
    * @throws ParseException
    */
-  public static GuessSchema getSchema(IAdapterParameterExtractor extractor)
+  public static GuessSchema getSchema(OpcUaClientProvider clientProvider,
+                                      IAdapterParameterExtractor extractor)
       throws AdapterException, ParseException {
     var builder = GuessSchemaBuilder.create();
     EventSchema eventSchema = new EventSchema();
@@ -86,14 +88,13 @@ public class OpcUaUtil {
     Map<String, FieldStatusInfo> fieldStatusInfos = new HashMap<>();
     List<EventProperty> allProperties = new ArrayList<>();
 
-    SpOpcUaClient<OpcUaConfig> spOpcUaClient = new SpOpcUaClient<>(
-        SpOpcUaConfigExtractor.extractSharedConfig(extractor.getStaticPropertyExtractor(), new OpcUaConfig())
+    var opcUaConfig = SpOpcUaConfigExtractor.extractSharedConfig(
+        extractor.getStaticPropertyExtractor(), new OpcUaConfig()
     );
-
     try {
-      spOpcUaClient.connect();
+      var connectedClient = clientProvider.getClient(opcUaConfig);
       OpcUaNodeBrowser nodeBrowser =
-          new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
+          new OpcUaNodeBrowser(connectedClient.getClient(), opcUaConfig);
       List<OpcNode> selectedNodes = nodeBrowser.findNodes();
 
       if (!selectedNodes.isEmpty()) {
@@ -116,12 +117,12 @@ public class OpcUaUtil {
       var nodeIds = selectedNodes.stream()
                                  .map(OpcNode::getNodeId)
                                  .collect(Collectors.toList());
-      var response = spOpcUaClient.getClient()
+      var response = connectedClient.getClient()
                                   .readValues(0, TimestampsToReturn.Both, nodeIds);
 
       var returnValues = response.get();
 
-      spOpcUaClient.disconnect();
+      //clientProvider.releaseClient(opcUaConfig);
 
       makeEventPreview(selectedNodes, eventPreview, fieldStatusInfos, returnValues);
 
@@ -129,7 +130,9 @@ public class OpcUaUtil {
     } catch (Exception e) {
       throw new AdapterException("Could not guess schema for opc node:  " + e.getMessage(), e);
     } finally {
-      spOpcUaClient.disconnect();
+      // TODO
+      //spOpcUaClient.disconnect();
+      clientProvider.releaseClient(opcUaConfig);
     }
 
     eventSchema.setEventProperties(allProperties);
@@ -172,7 +175,8 @@ public class OpcUaUtil {
    * @param parameterExtractor to extract parameters from the OPC UA config
    * @return {@code List<Option>} with available node names for the given OPC UA configuration
    */
-  public static RuntimeResolvableTreeInputStaticProperty resolveConfig(String internalName,
+  public static RuntimeResolvableTreeInputStaticProperty resolveConfig(OpcUaClientProvider clientProvider,
+                                                                       String internalName,
                                                                        IStaticPropertyExtractor parameterExtractor)
       throws SpConfigurationException {
 
@@ -181,19 +185,18 @@ public class OpcUaUtil {
     // access mode and host/url have to be selected
     try {
       parameterExtractor.selectedAlternativeInternalId(OpcUaLabels.OPC_HOST_OR_URL.name());
-      parameterExtractor.selectedAlternativeInternalId(OpcUaLabels.ACCESS_MODE.name());
+      parameterExtractor.selectedSingleValueInternalName(SharedUserConfiguration.SECURITY_MODE, String.class);
+      parameterExtractor.selectedSingleValue(SharedUserConfiguration.SECURITY_POLICY, String.class);
     } catch (NullPointerException nullPointerException) {
       return config;
     }
 
-    SpOpcUaClient spOpcUaClient = new SpOpcUaClient(
-        SpOpcUaConfigExtractor.extractSharedConfig(parameterExtractor, new OpcUaConfig())
-    );
+    var opcUaConfig = SpOpcUaConfigExtractor.extractSharedConfig(parameterExtractor, new OpcUaConfig());
 
     try {
-      spOpcUaClient.connect();
+      var connectedClient = clientProvider.getClient(opcUaConfig);
       OpcUaNodeBrowser nodeBrowser =
-          new OpcUaNodeBrowser(spOpcUaClient.getClient(), spOpcUaClient.getSpOpcConfig());
+          new OpcUaNodeBrowser(connectedClient.getClient(), opcUaConfig);
 
       var nodes = nodeBrowser.buildNodeTreeFromOrigin(config.getNextBaseNodeToResolve());
       if (Objects.isNull(config.getNextBaseNodeToResolve())) {
@@ -204,7 +207,7 @@ public class OpcUaUtil {
 
       if (!config.getSelectedNodesInternalNames().isEmpty()) {
         config.setSelectedNodesInternalNames(
-            filterMissingNodes(spOpcUaClient.getClient(), config.getSelectedNodesInternalNames())
+            filterMissingNodes(connectedClient.getClient(), config.getSelectedNodesInternalNames())
         );
       }
 
@@ -215,13 +218,15 @@ public class OpcUaUtil {
     } catch (ExecutionException | InterruptedException | URISyntaxException e) {
       throw new SpConfigurationException("Could not connect to the OPC UA server with the provided settings", e);
     } finally {
-      if (spOpcUaClient.getClient() != null) {
-        spOpcUaClient.disconnect();
-      }
+      clientProvider.releaseClient(opcUaConfig);
+      // TODO
+//      if (spOpcUaClient.getClient() != null) {
+//        spOpcUaClient.disconnect();
+//      }
     }
   }
 
-  public static List<String> filterMissingNodes(OpcUaClient opcUaClient,
+  public static List<String> filterMissingNodes(UaClient opcUaClient,
                                                 List<String> selectedNodes) {
     return selectedNodes.stream().filter(selectedNode -> {
       try {
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/SecurityUtils.java b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/SecurityUtils.java
new file mode 100644
index 0000000000..576f3eeb6f
--- /dev/null
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/java/org/apache/streampipes/extensions/connectors/opcua/utils/SecurityUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.extensions.connectors.opcua.utils;
+
+import org.apache.streampipes.model.Tuple2;
+
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
+
+import java.util.List;
+
+public class SecurityUtils {
+
+  public static List<Tuple2<String, String>> getAvailableSecurityModes() {
+    return List.of(
+        new Tuple2<>("None", MessageSecurityMode.None.name()),
+        new Tuple2<>("Sign", MessageSecurityMode.Sign.name()),
+        new Tuple2<>("Sign & Encrypt", MessageSecurityMode.SignAndEncrypt.name())
+    );
+  }
+
+  public static List<SecurityPolicy> getAvailableSecurityPolicies() {
+    return List.of(
+        SecurityPolicy.None,
+        SecurityPolicy.Basic128Rsa15,
+        SecurityPolicy.Basic256,
+        SecurityPolicy.Basic256Sha256,
+        SecurityPolicy.Aes128_Sha256_RsaOaep,
+        SecurityPolicy.Aes256_Sha256_RsaPss
+    );
+  }
+}
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/documentation.md b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/documentation.md
index 079243ab0f..185776e43a 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/documentation.md
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/documentation.md
@@ -26,7 +26,40 @@
 
 ## Description
 
-Reads values from an OPC-UA server repeatedly
+This adapter reads node values from an OPC-UA server.
+The adapter supports both signed/encrypted and unencrypted communication.
+
+Certificates must be provided directly to the service and cannot be added from the UI or REST APIs.
+To establish connections using a `Sign` or `Sign & Encrypt` security mode, 
+the following environment variables must be provided to the extension service:
+
+* SP_OPCUA_SECURITY_DIR the directory where the keystore and trusted certificates are located
+* SP_OPCUA_KEYSTORE_FILE the keystore file (e.g., keystore.pfx, must be of type PKCS12)
+* SP_OPCUA_KEYSTORE_PASSWORD the password to the keystore
+* SP_OPCUA_APPLICATION_URI the application URI used by the client to identify itself
+
+Certificate requirements:
+
+The X509 certificate must provide the following extras:
+* Key Usage: Certificate Sign
+* Subject Alternative Name: Application URI
+* Basic Constraints: Must provide CA:FALSE when using a self-signed certificate
+* Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication
+
+The directory layout of the `SP_OPCUA_SECURITY_DIR` look as follows:
+
+```
+SP_OPC_SECURITY_DIR/
+├─ pki/
+│  ├─ issuers/
+│  ├─ rejected/
+│  ├─ trusted/
+│  │  ├─ certs/
+│  │  ├─ crl/
+```
+
+Trusted certs need to be present in the `pki/trusted/certs` folder.
+Rejected certificates are stored in the `rejected` folder.
 
 ***
 
@@ -40,7 +73,15 @@ Reads values from an OPC-UA server repeatedly
 
 Duration of the polling interval in seconds
 
-### Anonymous vs. Username/Password
+### Security Mode
+
+Can be either None, Signed or Signed & Encrypt
+
+### Security Policy
+
+Choose one of the OPC-UA security policies or `None`
+
+### User Authentication
 
 Choose whether you want to connect anonymously or authenticate using your credentials.
 
@@ -54,14 +95,6 @@ Where can the OPC UA server be found?
 &nbsp;&nbsp;&nbsp;&nbsp; **URL**: Specify the server's full `URL` (including port), can be with our without leading `opc.tcp://`<br/>
 &nbsp;&nbsp;&nbsp;&nbsp; **Host/Port**: Insert the `host` address (with or without leading `opc.tcp://`) and the `port`<br/>
 
-### Namespace Index
-
-Requires the index of the namespace you want to connect to.
-
-### Node ID
-
-The identifier of the node you want to read from, numbers and strings are both valid.
-
 ### Available Nodes
 
 Shows all available nodes once namespace index and node ID are given.
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
index 995045ec7a..15f54c5768 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.connect.iiot.adapters.opcua/strings.en
@@ -38,10 +38,22 @@ OPC_SERVER_HOST.description=Example: test-server.com, opc.tcp://test-server.com)
 OPC_SERVER_PORT.title=Port
 OPC_SERVER_PORT.description=Example: 4840
 
+securityMode.title=Security Mode
+securityMode.description=The OPC-UA security mode
+
+securityPolicy.title=Security Policy
+securityPolicy.description=The OPC-UA security policy. Choose "None" if security mode is "None"
+
+userAuthentication.title=User Authentication
+userAuthentication.description=Choose an authentication method for the user
+
+anonymous.title=Anonymous
+anonymous.description=
+
 ACCESS_MODE.title=Security Level
 ACCESS_MODE.description=Select the OPC UA security level for the connection
 
-USERNAME_GROUP.title=Sign (username & password)
+USERNAME_GROUP.title=Username & Password
 USERNAME_GROUP.description=
 
 UNAUTHENTICATED.title=None
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/documentation.md b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/documentation.md
index 27221467c9..ee28512b27 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/documentation.md
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/documentation.md
@@ -26,7 +26,40 @@
 
 ## Description
 
-Allows to write events to an OPC-UA server.
+This data sink can be used to write values to an OPC-UA server.
+The sink supports both signed/encrypted and unencrypted communication.
+
+Certificates must be provided directly to the service and cannot be added from the UI or REST APIs.
+To establish connections using a `Sign` or `Sign & Encrypt` security mode,
+the following environment variables must be provided to the extension service:
+
+* SP_OPCUA_SECURITY_DIR the directory where the keystore and trusted certificates are located
+* SP_OPCUA_KEYSTORE_FILE the keystore file (e.g., keystore.pfx, must be of type PKCS12)
+* SP_OPCUA_KEYSTORE_PASSWORD the password to the keystore
+* SP_OPCUA_APPLICATION_URI the application URI used by the client to identify itself
+
+Certificate requirements:
+
+The X509 certificate must provide the following extras:
+* Key Usage: Certificate Sign
+* Subject Alternative Name: Application URI
+* Basic Constraints: Must provide CA:FALSE when using a self-signed certificate
+* Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication
+
+The directory layout of the `SP_OPCUA_SECURITY_DIR` look as follows:
+
+```
+SP_OPC_SECURITY_DIR/
+├─ pki/
+│  ├─ issuers/
+│  ├─ rejected/
+│  ├─ trusted/
+│  │  ├─ certs/
+│  │  ├─ crl/
+```
+
+Trusted certs need to be present in the `pki/trusted/certs` folder.
+Rejected certificates are stored in the `rejected` folder.
 
 ***
 
@@ -46,6 +79,21 @@ The hostname of the OPC-UA server.
 
 The port of the OPC-UA server.
 
+### Security Mode
+
+Can be either None, Signed or Signed & Encrypt
+
+### Security Policy
+
+Choose one of the OPC-UA security policies or `None`
+
+### User Authentication
+
+Choose whether you want to connect anonymously or authenticate using your credentials.
+
+&nbsp;&nbsp;&nbsp;&nbsp; **Anonymous**: No further information required <br/>
+&nbsp;&nbsp;&nbsp;&nbsp; **Username/Password**: Insert your `username` and `password` to access the OPC UA server
+
 ### Namespace Index
 
 The namespace index in which the node should be written
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
index 7e78d16a1a..b26c09c598 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/main/resources/org.apache.streampipes.sinks.databases.jvm.opcua/strings.en
@@ -56,3 +56,15 @@ AVAILABLE_NODES.description=Select the nodes that are relevant for you. Please e
 
 MAPPING_PROPERY.title=Field to write
 MAPPING_PROPERY.description=The field that should be written to the OPC-UA server
+
+securityMode.title=Security Mode
+securityMode.description=The OPC-UA security mode
+
+securityPolicy.title=Security Policy
+securityPolicy.description=The OPC-UA security policy. Choose "None" if security mode is "None"
+
+userAuthentication.title=User Authentication
+userAuthentication.description=Choose an authentication method for the user
+
+anonymous.title=Anonymous
+anonymous.description=
diff --git a/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml b/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
index b7730e28e1..d2aabf0380 100644
--- a/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
+++ b/streampipes-extensions/streampipes-extensions-all-iiot/pom.xml
@@ -197,6 +197,14 @@
             <groupId>org.atteo.classindex</groupId>
             <artifactId>classindex</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcutil-jdk18on</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.checkerframework</groupId>
             <artifactId>checker-qual</artifactId>
diff --git a/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml b/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
index 88cede6913..e487c1aaaa 100644
--- a/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
+++ b/streampipes-extensions/streampipes-extensions-all-jvm/pom.xml
@@ -228,6 +228,10 @@
             <groupId>org.atteo.classindex</groupId>
             <artifactId>classindex</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.checkerframework</groupId>
             <artifactId>checker-qual</artifactId>
diff --git a/streampipes-extensions/streampipes-extensions-iiot-minimal/pom.xml b/streampipes-extensions/streampipes-extensions-iiot-minimal/pom.xml
index b616216bc3..5e93d4eaf7 100644
--- a/streampipes-extensions/streampipes-extensions-iiot-minimal/pom.xml
+++ b/streampipes-extensions/streampipes-extensions-iiot-minimal/pom.xml
@@ -162,6 +162,10 @@
             <groupId>org.atteo.classindex</groupId>
             <artifactId>classindex</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk18on</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.checkerframework</groupId>
             <artifactId>checker-qual</artifactId>
diff --git a/ui/cypress/support/utils/connect/OpcUaUtils.ts b/ui/cypress/support/utils/connect/OpcUaUtils.ts
index 066cacf4cd..31d3f7f326 100644
--- a/ui/cypress/support/utils/connect/OpcUaUtils.ts
+++ b/ui/cypress/support/utils/connect/OpcUaUtils.ts
@@ -89,7 +89,8 @@ export class OpcUaUtils {
         }
 
         builder
-            .addInput('radio', 'access_mode-none', '')
+            .addInput('radio', 'securitymode-none', '')
+            .addInput('radio', 'userauthentication-anonymous', '')
             .addInput('radio', 'opc_host_or_url-url', '')
             .addInput(
                 'input',
diff --git a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
index f061e5daff..c6e6a589c7 100644
--- a/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
+++ b/ui/projects/streampipes/platform-services/src/lib/model/datalake/DateRange.ts
@@ -24,6 +24,11 @@ export interface TimeSettings {
     timeSelectionId?: string;
 }
 
+export interface ExtendedTimeSettings {
+    timeSettings: TimeSettings;
+    supportsLiveRefresh: boolean;
+}
+
 export class TimeSelectionConstants {
     static CUSTOM = 'custom';
     static LAST_15_MINUTES = 'last-15-minutes';
@@ -101,6 +106,7 @@ export interface QuickTimeSelection {
     startTime: (now: Date) => Date;
     endTime: (now: Date) => Date;
     addDividerAfter?: boolean;
+    supportsLiveRefresh?: boolean;
 }
 
 export class DateRange {
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.html
index 36b2b76cfe..84f316bcda 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.html
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.html
@@ -23,6 +23,7 @@
         [matMenuTriggerFor]="menu"
         aria-label="Refresh interval"
         matTooltip="Refresh interval"
+        [disabled]="!liveRefreshEnabled"
     >
         <mat-icon>autorenew</mat-icon>
         {{ liveSettings.label }}
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.ts
index 38327d1cb5..b60e4e1c78 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/refresh-interval-settings/refresh-interval-settings.component.ts
@@ -30,52 +30,10 @@ export class DataExplorerRefreshIntervalSettingsComponent implements OnInit {
     intervalSettingsChangedEmitter: EventEmitter<DashboardLiveSettings> =
         new EventEmitter<DashboardLiveSettings>();
 
-    availableOptions: DashboardLiveSettings[] = [
-        {
-            label: 'Off',
-            refreshModeActive: false,
-        },
-        {
-            label: '1 sec',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 1,
-        },
-        {
-            label: '2 sec',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 2,
-        },
-        {
-            label: '5 sec',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 5,
-        },
-        {
-            label: '10 sec',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 10,
-        },
-        {
-            label: '30 sec',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 30,
-        },
-        {
-            label: '1 min',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 60,
-        },
-        {
-            label: '5 min',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 300,
-        },
-        {
-            label: '30 min',
-            refreshModeActive: true,
-            refreshIntervalInSeconds: 60 * 30,
-        },
-    ];
+    @Input()
+    availableOptions: DashboardLiveSettings[];
+
+    liveRefreshEnabled: boolean;
 
     ngOnInit() {
         if (!this.liveSettings?.label) {
@@ -87,4 +45,11 @@ export class DataExplorerRefreshIntervalSettingsComponent implements OnInit {
         this.liveSettings = option;
         this.intervalSettingsChangedEmitter.emit(option);
     }
+
+    handleEnableLiveRefresh(liveRefreshEnabled: boolean) {
+        if (this.liveRefreshEnabled === true && liveRefreshEnabled === false) {
+            this.intervalSettingsChangedEmitter.emit(this.availableOptions[0]);
+        }
+        this.liveRefreshEnabled = liveRefreshEnabled;
+    }
 }
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.html
index 74d0a04ec4..97aa766d30 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.html
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.html
@@ -88,11 +88,13 @@
     </div>
     <div fxLayoutAlign="end center">
         <sp-data-explorer-refresh-interval-settings-component
-            *ngIf="liveSettings"
+            #refreshIntervalSettings
+            *ngIf="liveSettings && showTimeSelector && showIntervalSettings"
             [liveSettings]="liveSettings"
             (intervalSettingsChangedEmitter)="
                 intervalSettingsChangedEmitter.emit($event)
             "
+            [availableOptions]="availableOptions"
         >
         </sp-data-explorer-refresh-interval-settings-component>
     </div>
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.ts
index 465ae91373..d07fff080e 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-range-selector.component.ts
@@ -17,6 +17,7 @@
  */
 
 import {
+    AfterViewInit,
     Component,
     EventEmitter,
     Input,
@@ -33,12 +34,14 @@ import {
     TimeSelectionConstants,
     TimeSettings,
     TimeString,
+    ExtendedTimeSettings,
 } from '@streampipes/platform-services';
 import { MatMenuTrigger } from '@angular/material/menu';
 import { TimeSelectionService } from '../../services/time-selection.service';
 import { TimeRangeSelectorMenuComponent } from './time-selector-menu/time-selector-menu.component';
 import { TimeSelectorLabel } from './time-selector.model';
 import { differenceInMilliseconds, isSameDay } from 'date-fns';
+import { DataExplorerRefreshIntervalSettingsComponent } from './refresh-interval-settings/refresh-interval-settings.component';
 
 @Component({
     selector: 'sp-time-range-selector',
@@ -46,10 +49,14 @@ import { differenceInMilliseconds, isSameDay } from 'date-fns';
     styleUrls: ['./time-range-selector.component.scss'],
     encapsulation: ViewEncapsulation.None,
 })
-export class TimeRangeSelectorComponent implements OnInit, OnChanges {
+export class TimeRangeSelectorComponent
+    implements OnInit, OnChanges, AfterViewInit
+{
     @ViewChild('menuTrigger') menu: MatMenuTrigger;
     @ViewChild('timeSelectorMenu')
     timeSelectorMenu: TimeRangeSelectorMenuComponent;
+    @ViewChild('refreshIntervalSettings')
+    refreshIntervalSettingsComponent: DataExplorerRefreshIntervalSettingsComponent;
 
     @Output() dateRangeEmitter = new EventEmitter<TimeSettings>();
 
@@ -68,12 +75,18 @@ export class TimeRangeSelectorComponent implements OnInit, OnChanges {
     @Input()
     enableTimeChange = true;
 
+    @Input()
+    showIntervalSettings = true;
+
     @Input()
     maxDayRange = 0;
 
     @Input()
     quickSelections: QuickTimeSelection[];
 
+    @Input()
+    availableOptions: DashboardLiveSettings[];
+
     @Input()
     labels: TimeSelectorLabel;
 
@@ -93,6 +106,8 @@ export class TimeRangeSelectorComponent implements OnInit, OnChanges {
         this.quickSelections ??=
             this.timeSelectionService.defaultQuickTimeSelections;
         this.labels ??= this.timeSelectionService.defaultLabels;
+        this.availableOptions ??=
+            this.timeSelectionService.defaultAvailableLiveSettingsOptions;
         this.createDateString();
     }
 
@@ -102,12 +117,44 @@ export class TimeRangeSelectorComponent implements OnInit, OnChanges {
         }
     }
 
+    ngAfterViewInit(): void {
+        this.changeLiveRefreshEnabled();
+    }
+
+    changeLiveRefreshEnabled(): void {
+        if (
+            this.timeSettings.timeSelectionId ===
+                TimeSelectionConstants.CUSTOM ||
+            this.hasLiveRefreshEnabled(this.timeSettings.timeSelectionId) ===
+                false
+        ) {
+            this.refreshIntervalSettingsComponent?.handleEnableLiveRefresh(
+                false,
+            );
+        } else {
+            this.refreshIntervalSettingsComponent?.handleEnableLiveRefresh(
+                true,
+            );
+        }
+    }
+
+    hasLiveRefreshEnabled(timeSelectionId: string) {
+        const selectedQuickTimeSelection = this.quickSelections.find(
+            qs => timeSelectionId === qs.timeSelectionId,
+        );
+        return selectedQuickTimeSelection.supportsLiveRefresh;
+    }
+
     applyPreviousInterval(): void {
+        this.timeSettings.timeSelectionId = TimeSelectionConstants.CUSTOM;
         this.changeTimeByInterval((a, b) => a - b);
+        this.changeLiveRefreshEnabled();
     }
 
     applyNextInterval(): void {
+        this.timeSettings.timeSelectionId = TimeSelectionConstants.CUSTOM;
         this.changeTimeByInterval((a, b) => a + b);
+        this.changeLiveRefreshEnabled();
     }
 
     compare(newDateRange: TimeSettings, oldDateRange: TimeSettings): boolean {
@@ -155,8 +202,11 @@ export class TimeRangeSelectorComponent implements OnInit, OnChanges {
         this.reloadData();
     }
 
-    applyCurrentDateRange(timeSettings: TimeSettings) {
-        this.timeSettings = timeSettings;
+    applyCurrentDateRange(extendedTimeSettings: ExtendedTimeSettings) {
+        this.timeSettings = extendedTimeSettings.timeSettings;
+        this.refreshIntervalSettingsComponent?.handleEnableLiveRefresh(
+            extendedTimeSettings.supportsLiveRefresh,
+        );
         this.createDateString();
         this.menu.closeMenu();
         this.reloadData();
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.html b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.html
index 2bfda05f10..1dfc7f7119 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.html
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.html
@@ -46,7 +46,12 @@
                     #timeRangeSelection
                     [timeSettings]="timeSettings"
                     [labels]="labels"
-                    (timeSettingsEmitter)="timeSettingsEmitter.emit($event)"
+                    (timeSettingsEmitter)="
+                        timeSettingsEmitter.emit({
+                            supportsLiveRefresh: false,
+                            timeSettings: $event
+                        })
+                    "
                     [enableTimeChange]="enableTimeChange"
                     [maxDayRange]="maxDayRange"
                     class="w-100"
diff --git a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.ts b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.ts
index a2748d9a47..318ce9ff79 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/components/time-selector/time-selector-menu/time-selector-menu.component.ts
@@ -27,6 +27,7 @@ import {
 import {
     QuickTimeSelection,
     TimeSettings,
+    ExtendedTimeSettings,
 } from '@streampipes/platform-services';
 import { TimeSelectionService } from '../../../services/time-selection.service';
 import { CustomTimeRangeSelectionComponent } from './custom-time-range-selection/custom-time-range-selection.component';
@@ -51,8 +52,8 @@ export class TimeRangeSelectorMenuComponent implements OnInit {
     maxDayRange: number;
 
     @Output()
-    timeSettingsEmitter: EventEmitter<TimeSettings> =
-        new EventEmitter<TimeSettings>();
+    timeSettingsEmitter: EventEmitter<ExtendedTimeSettings> =
+        new EventEmitter<ExtendedTimeSettings>();
 
     @Input()
     quickSelections: QuickTimeSelection[];
@@ -72,7 +73,10 @@ export class TimeRangeSelectorMenuComponent implements OnInit {
         this.timeSettings.endTime = selectedDateRange.endDate.getTime();
         this.timeRangeSelection.initializeDateRange();
         this.triggerDisplayUpdate();
-        this.timeSettingsEmitter.emit(this.timeSettings);
+        this.timeSettingsEmitter.emit({
+            supportsLiveRefresh: quickSelection.supportsLiveRefresh,
+            timeSettings: this.timeSettings,
+        });
     }
 
     triggerDisplayUpdate(): void {
diff --git a/ui/projects/streampipes/shared-ui/src/lib/services/time-selection.service.ts b/ui/projects/streampipes/shared-ui/src/lib/services/time-selection.service.ts
index bc23977e7f..cca6465323 100644
--- a/ui/projects/streampipes/shared-ui/src/lib/services/time-selection.service.ts
+++ b/ui/projects/streampipes/shared-ui/src/lib/services/time-selection.service.ts
@@ -19,6 +19,7 @@
 import { Injectable } from '@angular/core';
 import { Subject } from 'rxjs';
 import {
+    DashboardLiveSettings,
     DateRange,
     QuickTimeSelection,
     TimeSelectionConstants,
@@ -65,66 +66,124 @@ export class TimeSelectionService {
             timeSelectionId: TimeSelectionConstants.LAST_15_MINUTES,
             startTime: now => subMinutes(now, 15),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Last 1 hour',
             timeSelectionId: TimeSelectionConstants.LAST_HOUR,
             startTime: now => subHours(now, 1),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Last 1 day',
             timeSelectionId: TimeSelectionConstants.LAST_DAY,
             startTime: now => subDays(now, 1),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Last 1 week',
             timeSelectionId: TimeSelectionConstants.LAST_WEEK,
             startTime: now => subWeeks(now, 1),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Last 1 month',
             timeSelectionId: TimeSelectionConstants.LAST_MONTH,
             startTime: now => subMonths(now, 1),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Last 1 year',
             timeSelectionId: TimeSelectionConstants.LAST_YEAR,
             startTime: now => subYears(now, 1),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Current day',
             timeSelectionId: TimeSelectionConstants.CURRENT_DAY,
             startTime: now => startOfDay(now),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Current hour',
             timeSelectionId: TimeSelectionConstants.CURRENT_HOUR,
             startTime: now => startOfHour(now),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Current week',
             timeSelectionId: TimeSelectionConstants.CURRENT_WEEK,
             startTime: now => startOfWeek(now),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Current month',
             timeSelectionId: TimeSelectionConstants.CURRENT_MONTH,
             startTime: now => startOfMonth(now),
             endTime: now => now,
+            supportsLiveRefresh: true,
         },
         {
             label: 'Current year',
             timeSelectionId: TimeSelectionConstants.CURRENT_YEAR,
             startTime: now => startOfYear(now),
             endTime: now => now,
+            supportsLiveRefresh: true,
+        },
+    ];
+
+    defaultAvailableLiveSettingsOptions: DashboardLiveSettings[] = [
+        {
+            label: 'Off',
+            refreshModeActive: false,
+        },
+        {
+            label: '1 sec',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 1,
+        },
+        {
+            label: '2 sec',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 2,
+        },
+        {
+            label: '5 sec',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 5,
+        },
+        {
+            label: '10 sec',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 10,
+        },
+        {
+            label: '30 sec',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 30,
+        },
+        {
+            label: '1 min',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 60,
+        },
+        {
+            label: '5 min',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 300,
+        },
+        {
+            label: '30 min',
+            refreshModeActive: true,
+            refreshIntervalInSeconds: 60 * 30,
         },
     ];
 
diff --git a/ui/projects/streampipes/shared-ui/src/public-api.ts b/ui/projects/streampipes/shared-ui/src/public-api.ts
index 40fe20fa0a..ab70e0b106 100644
--- a/ui/projects/streampipes/shared-ui/src/public-api.ts
+++ b/ui/projects/streampipes/shared-ui/src/public-api.ts
@@ -39,6 +39,7 @@ export * from './lib/components/sp-exception-message/exception-details/exception
 export * from './lib/components/sp-label/sp-label.component';
 export * from './lib/components/sp-table/sp-table.component';
 export * from './lib/components/warning-box/warning-box.component';
+export * from './lib/components/time-selector/time-selector.model';
 export * from './lib/components/time-selector/time-range-selector.component';
 export * from './lib/components/time-selector/time-selector-menu/time-selector-menu.component';
 export * from './lib/components/time-selector/time-selector-menu/custom-time-range-selection/custom-time-range-selection.component';
diff --git a/ui/src/app/connect/components/configuration-group/configuration-group.component.html b/ui/src/app/connect/components/configuration-group/configuration-group.component.html
index 68569ade6d..282912a4ea 100644
--- a/ui/src/app/connect/components/configuration-group/configuration-group.component.html
+++ b/ui/src/app/connect/components/configuration-group/configuration-group.component.html
@@ -31,8 +31,10 @@
                 [adapterId]="adapterId"
                 [parentForm]="configurationGroup"
                 [fieldName]="config.internalName"
-                (updateEmitter)="triggerUpdate($event)"
-                [completedStaticProperty]="completedStaticProperty"
+                [completedConfigurations]="completedConfigurations"
+                (completedConfigurationsEmitter)="
+                    updateCompletedConfiguration($event)
+                "
             >
             </sp-app-static-property>
         </div>
diff --git a/ui/src/app/connect/components/configuration-group/configuration-group.component.ts b/ui/src/app/connect/components/configuration-group/configuration-group.component.ts
index 1e109beab6..e87c47c255 100644
--- a/ui/src/app/connect/components/configuration-group/configuration-group.component.ts
+++ b/ui/src/app/connect/components/configuration-group/configuration-group.component.ts
@@ -16,20 +16,21 @@
  *
  */
 
-import { Component, Input } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
 import { UntypedFormGroup } from '@angular/forms';
 import {
     ExtensionDeploymentConfiguration,
     StaticPropertyUnion,
 } from '@streampipes/platform-services';
 import { ConfigurationInfo } from '../../model/ConfigurationInfo';
+import { StaticPropertyUtilService } from '../../../core-ui/static-properties/static-property-util.service';
 
 @Component({
     selector: 'sp-configuration-group',
     templateUrl: './configuration-group.component.html',
     styleUrls: ['./configuration-group.component.scss'],
 })
-export class ConfigurationGroupComponent {
+export class ConfigurationGroupComponent implements OnInit {
     @Input() configurationGroup: UntypedFormGroup;
 
     @Input() adapterId: string;
@@ -38,11 +39,22 @@ export class ConfigurationGroupComponent {
 
     @Input() deploymentConfiguration: ExtensionDeploymentConfiguration;
 
-    completedStaticProperty: ConfigurationInfo;
+    completedConfigurations: ConfigurationInfo[] = [];
 
-    constructor() {}
+    constructor(private staticPropertyUtils: StaticPropertyUtilService) {}
 
-    triggerUpdate(configurationInfo: ConfigurationInfo) {
-        this.completedStaticProperty = { ...configurationInfo };
+    ngOnInit() {
+        this.completedConfigurations =
+            this.staticPropertyUtils.initializeCompletedConfigurations(
+                this.configuration,
+            );
+    }
+
+    updateCompletedConfiguration(configurationInfo: ConfigurationInfo) {
+        this.staticPropertyUtils.updateCompletedConfiguration(
+            configurationInfo,
+            this.completedConfigurations,
+        );
+        this.completedConfigurations = [...this.completedConfigurations];
     }
 }
diff --git a/ui/src/app/core-ui/static-properties/base/abstract-static-property.ts b/ui/src/app/core-ui/static-properties/base/abstract-static-property.ts
index bc61dcd448..2fc1164b2d 100644
--- a/ui/src/app/core-ui/static-properties/base/abstract-static-property.ts
+++ b/ui/src/app/core-ui/static-properties/base/abstract-static-property.ts
@@ -21,10 +21,11 @@ import {
     StaticProperty,
     StaticPropertyUnion,
 } from '@streampipes/platform-services';
-import { Directive, EventEmitter, Input, Output } from '@angular/core';
+import { Directive, EventEmitter, inject, Input, Output } from '@angular/core';
 import { UntypedFormGroup } from '@angular/forms';
 import { ConfigurationInfo } from '../../../connect/model/ConfigurationInfo';
 import { InvocablePipelineElementUnion } from '../../../editor/model/editor.model';
+import { StaticPropertyUtilService } from '../static-property-util.service';
 
 @Directive()
 // eslint-disable-next-line @angular-eslint/directive-class-suffix
@@ -53,14 +54,21 @@ export abstract class AbstractStaticPropertyRenderer<T extends StaticProperty> {
     @Input()
     displayRecommended: boolean;
 
-    @Output() updateEmitter: EventEmitter<ConfigurationInfo> =
+    @Input()
+    completedConfigurations: ConfigurationInfo[];
+
+    @Output()
+    completedConfigurationsEmitter: EventEmitter<ConfigurationInfo> =
         new EventEmitter();
 
+    staticPropertyUtils = inject(StaticPropertyUtilService);
+
     constructor() {}
 
-    emitUpdate(valid?: boolean) {
-        this.updateEmitter.emit(
-            new ConfigurationInfo(this.staticProperty.internalName, valid),
-        );
+    applyCompletedConfiguration(valid?: boolean) {
+        this.completedConfigurationsEmitter.emit({
+            staticPropertyInternalName: this.staticProperty.internalName,
+            configured: valid,
+        });
     }
 }
diff --git a/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.html b/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.html
index 7ae838b3b4..a68765e69c 100644
--- a/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.html
+++ b/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.html
@@ -61,6 +61,9 @@
                 <div style="padding: 0 10px">
                     <sp-app-static-property
                         [adapterId]="adapterId"
+                        [completedConfigurations]="
+                            completedAlternativeConfigurations
+                        "
                         [deploymentConfiguration]="deploymentConfiguration"
                         [eventSchemas]="eventSchemas"
                         [parentForm]="parentForm"
@@ -74,7 +77,9 @@
                         "
                         [staticProperty]="alternative.staticProperty"
                         [displayRecommended]="displayRecommended"
-                        (updateEmitter)="handleConfigurationUpdate($event)"
+                        (completedConfigurationsEmitter)="
+                            handleConfigurationUpdate($event)
+                        "
                         class="test fullWidth"
                     >
                     </sp-app-static-property>
diff --git a/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.ts b/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.ts
index 6e4c668984..886b67efc9 100644
--- a/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-alternatives/static-alternatives.component.ts
@@ -44,13 +44,12 @@ export class StaticAlternativesComponent
     @Input()
     deploymentConfiguration: ExtensionDeploymentConfiguration;
 
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
+    // dependentStaticPropertyIds: Map<string, boolean> = new Map<
+    //     string,
+    //     boolean
+    // >();
 
-    completedStaticProperty: ConfigurationInfo;
-    dependentStaticPropertyIds: Map<string, boolean> = new Map<
-        string,
-        boolean
-    >();
+    completedAlternativeConfigurations: ConfigurationInfo[] = [];
 
     constructor(private changeDetectorRef: ChangeDetectorRef) {
         super();
@@ -59,10 +58,16 @@ export class StaticAlternativesComponent
     ngOnInit() {
         this.staticProperty.alternatives.forEach(al => {
             if (al.staticProperty) {
-                const configuration = al.staticProperty.internalName;
-                this.dependentStaticPropertyIds.set(configuration, false);
+                this.completedAlternativeConfigurations.push({
+                    staticPropertyInternalName: al.staticProperty.internalName,
+                    configured: false,
+                });
             }
         });
+        if (!this.staticProperty.alternatives.some(a => a.selected)) {
+            this.staticProperty.alternatives[0].selected = true;
+            this.checkFireCompleted(this.staticProperty.alternatives[0]);
+        }
     }
 
     radioSelectionChange(event) {
@@ -75,21 +80,23 @@ export class StaticAlternativesComponent
     }
 
     handleConfigurationUpdate(configurationInfo: ConfigurationInfo) {
-        this.dependentStaticPropertyIds.set(
-            configurationInfo.staticPropertyInternalName,
-            configurationInfo.configured,
+        this.staticPropertyUtils.updateCompletedConfiguration(
+            configurationInfo,
+            this.completedAlternativeConfigurations,
         );
         if (this.alternativeCompleted()) {
-            this.completedStaticProperty = { ...configurationInfo };
-            this.emitUpdate(true);
+            this.completedAlternativeConfigurations = [
+                ...this.completedAlternativeConfigurations,
+            ];
+            this.applyCompletedConfiguration(true);
         } else {
-            this.emitUpdate();
+            this.applyCompletedConfiguration(false);
         }
     }
 
     checkFireCompleted(alternative: StaticPropertyAlternative) {
         if (alternative.selected && alternative.staticProperty === null) {
-            this.emitUpdate(true);
+            this.applyCompletedConfiguration(true);
         }
     }
 
@@ -102,9 +109,11 @@ export class StaticAlternativesComponent
                     if (al.staticProperty === null) {
                         return false;
                     } else {
-                        return this.dependentStaticPropertyIds.get(
-                            al.staticProperty.internalName,
-                        );
+                        return this.completedAlternativeConfigurations.find(
+                            c =>
+                                c.staticPropertyInternalName ===
+                                al.staticProperty.internalName,
+                        ).configured;
                     }
                 }
             }) !== undefined
diff --git a/ui/src/app/core-ui/static-properties/static-any-input/static-any-input.component.ts b/ui/src/app/core-ui/static-properties/static-any-input/static-any-input.component.ts
index c28ebac882..53d3de8b88 100644
--- a/ui/src/app/core-ui/static-properties/static-any-input/static-any-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-any-input/static-any-input.component.ts
@@ -25,16 +25,7 @@ import { AnyStaticProperty } from '@streampipes/platform-services';
     templateUrl: './static-any-input.component.html',
     styleUrls: ['./static-any-input.component.scss'],
 })
-export class StaticAnyInputComponent
-    extends AbstractStaticPropertyRenderer<AnyStaticProperty>
-    implements OnInit
-{
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
-
-    ngOnInit() {
-        this.inputEmitter.emit(true);
-    }
-
+export class StaticAnyInputComponent extends AbstractStaticPropertyRenderer<AnyStaticProperty> {
     select(elementId: string) {
         this.staticProperty.options
             .filter(option => option.elementId === elementId)
diff --git a/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.html b/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.html
index 21df7192d8..ab8e1041ac 100644
--- a/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.html
+++ b/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.html
@@ -27,7 +27,7 @@
             formControlName="{{ fieldName }}"
             [style.background]="staticProperty.selectedColor"
             required
-            (blur)="emitUpdate()"
+            (blur)="checkCompleted()"
             [cpPresetColors]="presetColors"
         />
     </div>
diff --git a/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.ts b/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.ts
index c2ddd319ec..8717ea8230 100644
--- a/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-color-picker/static-color-picker.component.ts
@@ -36,10 +36,6 @@ export class StaticColorPickerComponent
         super();
     }
 
-    inputValue: String;
-    hasInput: Boolean;
-    colorPickerForm: UntypedFormGroup;
-
     presetColors: any[] = [
         '#39B54A',
         '#1B1464',
@@ -58,17 +54,14 @@ export class StaticColorPickerComponent
         this.enableValidators();
     }
 
-    emitUpdate() {
-        this.updateEmitter.emit(
-            new ConfigurationInfo(
-                this.staticProperty.internalName,
+    checkCompleted() {
+        this.applyCompletedConfiguration(
+            this.staticPropertyUtil.asColorPickerStaticProperty(
+                this.staticProperty,
+            ).selectedColor &&
                 this.staticPropertyUtil.asColorPickerStaticProperty(
                     this.staticProperty,
-                ).selectedColor &&
-                    this.staticPropertyUtil.asColorPickerStaticProperty(
-                        this.staticProperty,
-                    ).selectedColor !== '',
-            ),
+                ).selectedColor !== '',
         );
     }
 
diff --git a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
index 600058ad46..69ed38be1d 100644
--- a/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-file-input/static-file-input.component.ts
@@ -38,8 +38,6 @@ export class StaticFileInputComponent
     extends AbstractValidatedStaticPropertyRenderer<FileStaticProperty>
     implements OnInit
 {
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
-
     public chooseExistingFileControl = new UntypedFormControl();
 
     dialogRef: MatDialogRef<FileRenameDialogComponent>;
@@ -99,7 +97,7 @@ export class StaticFileInputComponent
                         fmi => fmi.filename === filenameToSelect,
                     );
                     this.selectOption(this.selectedFile);
-                    this.emitUpdate(true);
+                    this.applyCompletedConfiguration(true);
                     this.parentForm.controls[this.fieldName].setValue(
                         this.selectedFile,
                     );
@@ -114,7 +112,7 @@ export class StaticFileInputComponent
                     if (this.fileMetadata.length > 0) {
                         this.selectedFile = this.fileMetadata[0];
                         this.selectOption(this.selectedFile);
-                        this.emitUpdate(true);
+                        this.applyCompletedConfiguration(true);
                         this.parentForm.controls[this.fieldName].setValue(
                             this.selectedFile,
                         );
@@ -172,9 +170,7 @@ export class StaticFileInputComponent
         this.staticProperty.locationPath = fileMetadata.filename;
         const valid: boolean =
             fileMetadata.filename !== '' || fileMetadata.filename !== undefined;
-        this.updateEmitter.emit(
-            new ConfigurationInfo(this.staticProperty.internalName, valid),
-        );
+        this.applyCompletedConfiguration(valid);
     }
 
     displayFn(fileMetadata: FileMetadata) {
diff --git a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
index 13a7b14326..a17386325c 100644
--- a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
+++ b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.html
@@ -73,6 +73,7 @@
                         (click)="applyPlaceholder(property.runtimeName)"
                         color="accent"
                         *ngFor="let property of eventSchemas[0].eventProperties"
+                        style="margin-right: 5px; margin-bottom: 5px"
                         >#{{ property.runtimeName }}#
                     </mat-chip>
                 </mat-chip-list>
@@ -80,12 +81,14 @@
         </div>
         <div fxFlex="100" *ngIf="staticProperty.multiLine">
             <quill-editor
+                fxFlex="100"
                 *ngIf="staticProperty.htmlFontFormat"
                 #textEditor
                 formControlName="{{ fieldName }}"
                 [modules]="quillModulesFontFormat"
             ></quill-editor>
             <quill-editor
+                fxFlex="100"
                 *ngIf="!staticProperty.htmlFontFormat"
                 #textEditor
                 formControlName="{{ fieldName }}"
diff --git a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.scss b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.scss
index a2c4055066..d3f5d770fd 100644
--- a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.scss
+++ b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.scss
@@ -29,6 +29,10 @@ form {
     float: left;
 }
 
+tags {
+    margin-bottom: 50px;
+}
+
 ::ng-deep.mat-accent ::ng-deep.mat-slider-track-fill,
 .mat-accent ::ng-deep.mat-slider-thumb,
 .mat-accent ::ng-deep.mat-slider-thumb-label {
diff --git a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
index 78be373e65..e71552b7ea 100644
--- a/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-free-input/static-free-input.component.ts
@@ -97,9 +97,7 @@ export class StaticFreeInputComponent
             this.staticProperty.value !== undefined &&
             this.staticProperty.value !== '' &&
             this.staticProperty.value !== null;
-        this.updateEmitter.emit(
-            new ConfigurationInfo(this.staticProperty.internalName, valid),
-        );
+        this.applyCompletedConfiguration(valid);
     }
 
     onStatusChange(status: any) {}
diff --git a/ui/src/app/core-ui/static-properties/static-group/static-group.component.html b/ui/src/app/core-ui/static-properties/static-group/static-group.component.html
index fb77d388b8..d321f4c904 100644
--- a/ui/src/app/core-ui/static-properties/static-group/static-group.component.html
+++ b/ui/src/app/core-ui/static-properties/static-group/static-group.component.html
@@ -28,7 +28,7 @@
         [eventSchemas]="eventSchemas"
         [staticProperty]="property"
         [displayRecommended]="displayRecommended"
-        (updateEmitter)="handleConfigurationUpdate($event)"
+        (completedConfigurationsEmitter)="handleConfigurationUpdate($event)"
         class="test fullWidth"
     >
     </sp-app-static-property>
diff --git a/ui/src/app/core-ui/static-properties/static-group/static-group.component.ts b/ui/src/app/core-ui/static-properties/static-group/static-group.component.ts
index 45da778749..73141eadb9 100644
--- a/ui/src/app/core-ui/static-properties/static-group/static-group.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-group/static-group.component.ts
@@ -36,8 +36,6 @@ export class StaticGroupComponent
     @Input()
     deploymentConfiguration: ExtensionDeploymentConfiguration;
 
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
-
     dependentStaticProperties: Map<string, boolean> = new Map<
         string,
         boolean
@@ -53,9 +51,9 @@ export class StaticGroupComponent
                 v => v === true,
             )
         ) {
-            this.emitUpdate(true);
+            this.applyCompletedConfiguration(true);
         } else {
-            this.emitUpdate(false);
+            this.applyCompletedConfiguration(false);
         }
     }
 
diff --git a/ui/src/app/core-ui/static-properties/static-mapping-nary/static-mapping-nary.component.ts b/ui/src/app/core-ui/static-properties/static-mapping-nary/static-mapping-nary.component.ts
index bd1e95d7fc..c67a1e84c0 100644
--- a/ui/src/app/core-ui/static-properties/static-mapping-nary/static-mapping-nary.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-mapping-nary/static-mapping-nary.component.ts
@@ -30,8 +30,6 @@ export class StaticMappingNaryComponent
     extends StaticMappingComponent<MappingPropertyNary>
     implements OnInit
 {
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
-
     constructor(private displayRecommendedPipe: DisplayRecommendedPipe) {
         super();
     }
@@ -56,7 +54,6 @@ export class StaticMappingNaryComponent
                 }
             });
         }
-        this.inputEmitter.emit(true);
     }
 
     selectOption(property: any, $event) {
@@ -112,6 +109,6 @@ export class StaticMappingNaryComponent
     onStatusChange(status: any) {}
 
     onValueChange(value: any) {
-        this.emitUpdate();
+        this.applyCompletedConfiguration();
     }
 }
diff --git a/ui/src/app/core-ui/static-properties/static-mapping-unary/static-mapping-unary.component.ts b/ui/src/app/core-ui/static-properties/static-mapping-unary/static-mapping-unary.component.ts
index d414502634..db5cf86d4b 100644
--- a/ui/src/app/core-ui/static-properties/static-mapping-unary/static-mapping-unary.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-mapping-unary/static-mapping-unary.component.ts
@@ -30,8 +30,6 @@ export class StaticMappingUnaryComponent
     extends StaticMappingComponent<MappingPropertyUnary>
     implements OnInit
 {
-    @Output() inputEmitter: EventEmitter<boolean> = new EventEmitter<boolean>();
-
     constructor() {
         super();
     }
@@ -41,7 +39,7 @@ export class StaticMappingUnaryComponent
         if (!this.staticProperty.selectedProperty) {
             this.staticProperty.selectedProperty =
                 this.availableProperties[0].propertySelector;
-            this.emitUpdate(true);
+            this.applyCompletedConfiguration(true);
         }
         this.addValidator(
             this.staticProperty.selectedProperty,
@@ -54,6 +52,6 @@ export class StaticMappingUnaryComponent
 
     onValueChange(value: any) {
         this.staticProperty.selectedProperty = value;
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 }
diff --git a/ui/src/app/core-ui/static-properties/static-one-of-input/static-one-of-input.component.ts b/ui/src/app/core-ui/static-properties/static-one-of-input/static-one-of-input.component.ts
index 1d1b835b98..f05537f9a3 100644
--- a/ui/src/app/core-ui/static-properties/static-one-of-input/static-one-of-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-one-of-input/static-one-of-input.component.ts
@@ -52,7 +52,7 @@ export class StaticOneOfInputComponent
             ).elementId;
         }
         this.inputEmitter.emit(true);
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
         this.parentForm.updateValueAndValidity();
     }
 
@@ -71,6 +71,6 @@ export class StaticOneOfInputComponent
             option => option.elementId === id,
         ).selected = true;
         this.inputEmitter.emit(true);
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 }
diff --git a/ui/src/app/core-ui/static-properties/static-property-util.service.ts b/ui/src/app/core-ui/static-properties/static-property-util.service.ts
index b85882cd26..533cff0256 100644
--- a/ui/src/app/core-ui/static-properties/static-property-util.service.ts
+++ b/ui/src/app/core-ui/static-properties/static-property-util.service.ts
@@ -37,11 +37,53 @@ import {
     StaticPropertyGroup,
 } from '@streampipes/platform-services';
 import { IdGeneratorService } from '../../core-services/id-generator/id-generator.service';
+import { ConfigurationInfo } from '../../connect/model/ConfigurationInfo';
 
 @Injectable({ providedIn: 'root' })
 export class StaticPropertyUtilService {
     constructor(private idGeneratorService: IdGeneratorService) {}
 
+    public initializeCompletedConfigurations(
+        configs: StaticProperty[],
+    ): ConfigurationInfo[] {
+        return configs
+            .filter(config => !config.optional)
+            .map(config => {
+                return {
+                    staticPropertyInternalName: config.internalName,
+                    configured: false,
+                };
+            });
+    }
+
+    public allDependenciesSatisfied(
+        dependsOn: string[],
+        completedConfigs: ConfigurationInfo[],
+    ) {
+        if (dependsOn?.length > 0) {
+            return dependsOn.every(dependency =>
+                completedConfigs.some(
+                    config =>
+                        config.staticPropertyInternalName === dependency &&
+                        config.configured,
+                ),
+            );
+        } else {
+            return true;
+        }
+    }
+
+    public updateCompletedConfiguration(
+        completedConfig: ConfigurationInfo,
+        completedConfigs: ConfigurationInfo[],
+    ) {
+        completedConfigs.find(
+            c =>
+                c.staticPropertyInternalName ===
+                completedConfig.staticPropertyInternalName,
+        ).configured = completedConfig.configured;
+    }
+
     public clone(val: StaticProperty) {
         let clone;
         const id = this.idGeneratorService.generatePrefixedId();
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 3f43c9bd03..fe6c2bfc68 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
@@ -37,78 +37,96 @@
         <div fxFlex="100">
             <sp-static-code-input
                 *ngIf="isCodeInputStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [eventSchemas]="eventSchemas"
                 [fieldName]="fieldName"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-static-code-input>
 
             <sp-app-static-secret-input
                 *ngIf="isSecretStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-secret-input>
 
             <sp-app-static-free-input
                 *ngIf="isFreeTextStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [eventSchemas]="eventSchemas"
                 [fieldName]="fieldName"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-free-input>
 
             <sp-static-file-input
                 *ngIf="isFileStaticProperty(staticProperty)"
-                (inputEmitter)="valueChange($event)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
-                (updateEmitter)="emitUpdate($event)"
                 [adapterId]="adapterId"
             >
             </sp-static-file-input>
 
             <sp-app-static-color-picker
                 *ngIf="isColorPickerStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-color-picker>
 
             <sp-app-static-runtime-resolvable-any-input
                 *ngIf="isRuntimeResolvableAnyStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [staticProperty]="staticProperty"
                 [staticProperties]="staticProperties"
                 [eventSchemas]="eventSchemas"
                 [pipelineElement]="pipelineElement"
                 [parentForm]="parentForm"
-                [completedStaticProperty]="completedStaticProperty"
                 [adapterId]="adapterId"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-runtime-resolvable-any-input>
 
             <sp-app-static-runtime-resolvable-oneof-input
                 *ngIf="isRuntimeResolvableOneOfStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [deploymentConfiguration]="deploymentConfiguration"
-                [completedStaticProperty]="completedStaticProperty"
                 [pipelineElement]="pipelineElement"
                 [eventSchemas]="eventSchemas"
                 [staticProperty]="staticProperty"
                 [parentForm]="parentForm"
                 [staticProperties]="staticProperties"
                 [adapterId]="adapterId"
-                (updateEmitter)="emitUpdate($event)"
             ></sp-app-static-runtime-resolvable-oneof-input>
 
             <sp-app-static-any-input
@@ -116,7 +134,10 @@
                     isAnyStaticProperty(staticProperty) &&
                     !isRuntimeResolvableAnyStaticProperty(staticProperty)
                 "
-                (inputEmitter)="valueChange($event)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [fieldName]="fieldName"
                 [staticProperty]="staticProperty"
             >
@@ -127,8 +148,10 @@
                     isOneOfStaticProperty(staticProperty) &&
                     !isRuntimeResolvableOneOfStaticProperty(staticProperty)
                 "
-                (inputEmitter)="valueChange($event)"
-                (updateEmitter)="emitUpdate($event)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [parentForm]="parentForm"
                 [staticProperty]="staticProperty"
             >
@@ -136,30 +159,38 @@
 
             <sp-app-static-mapping-unary
                 *ngIf="isMappingPropertyUnary(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [eventSchemas]="eventSchemas"
                 [staticProperty]="staticProperty"
                 [displayRecommended]="displayRecommended"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
-                (inputEmitter)="valueChange($event)"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-mapping-unary>
 
             <sp-app-static-mapping-nary
                 *ngIf="isMappingNaryProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [eventSchemas]="eventSchemas"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
                 [staticProperty]="staticProperty"
                 [displayRecommended]="displayRecommended"
-                (inputEmitter)="valueChange($event)"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-mapping-nary>
 
             <sp-app-static-alternatives
                 *ngIf="isAlternativesStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [adapterId]="adapterId"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [eventSchemas]="eventSchemas"
@@ -168,8 +199,6 @@
                 [staticProperties]="staticProperties"
                 [displayRecommended]="displayRecommended"
                 class="test fullWidth"
-                (inputEmitter)="valueChange($event)"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-alternatives>
 
@@ -178,6 +207,10 @@
                     isGroupStaticProperty(staticProperty) &&
                     !isRuntimeResolvableGroupStaticProperty(staticProperty)
                 "
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [adapterId]="adapterId"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [eventSchemas]="eventSchemas"
@@ -187,12 +220,15 @@
                 [staticProperties]="staticProperties"
                 [displayRecommended]="displayRecommended"
                 class="test fullWidth"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-group>
 
             <sp-app-static-runtime-resolvable-group
                 *ngIf="isRuntimeResolvableGroupStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [adapterId]="adapterId"
                 [eventSchemas]="eventSchemas"
@@ -200,15 +236,17 @@
                 [fieldName]="fieldName"
                 [staticProperties]="staticProperties"
                 [staticProperty]="staticProperty"
-                [completedStaticProperty]="completedStaticProperty"
                 [displayRecommended]="displayRecommended"
                 class="test fullWidth"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-app-static-runtime-resolvable-group>
 
             <sp-static-collection
                 *ngIf="isCollectionStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [adapterId]="adapterId"
                 [eventSchemas]="eventSchemas"
@@ -217,27 +255,32 @@
                 [displayRecommended]="displayRecommended"
                 [staticProperty]="staticProperty"
                 class="test fullWidth"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-static-collection>
 
             <sp-static-slide-toggle
                 *ngIf="isSlideToggleStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [adapterId]="adapterId"
                 [parentForm]="parentForm"
                 [fieldName]="fieldName"
                 [displayRecommended]="displayRecommended"
                 [staticProperty]="staticProperty"
                 class="test fullWidth"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-static-slide-toggle>
             <sp-static-runtime-resolvable-tree-input
                 *ngIf="isTreeInputStaticProperty(staticProperty)"
+                (completedConfigurationsEmitter)="
+                    completedConfigurationsEmitter.emit($event)
+                "
+                [completedConfigurations]="completedConfigurations"
                 [deploymentConfiguration]="deploymentConfiguration"
                 [adapterId]="adapterId"
                 [parentForm]="parentForm"
-                [completedStaticProperty]="completedStaticProperty"
                 [pipelineElement]="pipelineElement"
                 [eventSchemas]="eventSchemas"
                 [staticProperties]="staticProperties"
@@ -245,7 +288,6 @@
                 [displayRecommended]="displayRecommended"
                 [staticProperty]="staticProperty"
                 class="test fullWidth"
-                (updateEmitter)="emitUpdate($event)"
             >
             </sp-static-runtime-resolvable-tree-input>
         </div>
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 ef3ddc98eb..5d4248d13d 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
@@ -59,18 +59,9 @@ export class StaticPropertyComponent implements OnInit {
     @Input()
     adapterId: string;
 
-    @Output()
-    validateEmitter: EventEmitter<any> = new EventEmitter<any>();
-
-    @Output()
-    updateEmitter: EventEmitter<ConfigurationInfo> = new EventEmitter();
-
     @Input()
     eventSchemas: EventSchema[];
 
-    @Input()
-    completedStaticProperty: ConfigurationInfo;
-
     @Input()
     parentForm: UntypedFormGroup;
 
@@ -86,6 +77,13 @@ export class StaticPropertyComponent implements OnInit {
     @Input()
     deploymentConfiguration: ExtensionDeploymentConfiguration;
 
+    @Input()
+    completedConfigurations: ConfigurationInfo[];
+
+    @Output()
+    completedConfigurationsEmitter: EventEmitter<ConfigurationInfo> =
+        new EventEmitter();
+
     showLabel = true;
 
     @Input()
@@ -167,12 +165,4 @@ export class StaticPropertyComponent implements OnInit {
     isTreeInputStaticProperty(val) {
         return val instanceof RuntimeResolvableTreeInputStaticProperty;
     }
-
-    valueChange(hasInput) {
-        this.validateEmitter.emit();
-    }
-
-    emitUpdate(configurationInfo: ConfigurationInfo) {
-        this.updateEmitter.emit(configurationInfo);
-    }
 }
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 6d84538535..7b210ff517 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
@@ -51,7 +51,7 @@ export class StaticRuntimeResolvableAnyInputComponent
     selectAll(select: boolean): void {
         this.staticProperty.options.forEach(o => (o.selected = select));
         this.selectedOptions = select ? this.staticProperty.options : [];
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 
     onSelectionChange(): void {
@@ -61,7 +61,7 @@ export class StaticRuntimeResolvableAnyInputComponent
     }
 
     checkEmitUpdate(): void {
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 
     afterOptionsLoaded(staticProperty: RuntimeResolvableAnyStaticProperty) {
diff --git a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-group/static-runtime-resolvable-group.component.ts b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-group/static-runtime-resolvable-group.component.ts
index 936c1c0232..ec8fef59b3 100644
--- a/ui/src/app/core-ui/static-properties/static-runtime-resolvable-group/static-runtime-resolvable-group.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-runtime-resolvable-group/static-runtime-resolvable-group.component.ts
@@ -52,7 +52,7 @@ export class StaticRuntimeResolvableGroupComponent
         if (this.staticProperty.staticProperties.length === 0) {
             this.loadOptionsFromRestApi();
         }
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 
     afterErrorReceived() {}
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 19e9a9cda6..564f2c2d06 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
@@ -47,34 +47,18 @@ export abstract class BaseRuntimeResolvableInput<
     extends AbstractStaticPropertyRenderer<T>
     implements OnChanges
 {
-    @Input()
-    completedStaticProperty: ConfigurationInfo;
-
     @Input() deploymentConfiguration: ExtensionDeploymentConfiguration;
 
     showOptions = false;
     loading = false;
     error = false;
     errorMessage: SpLogMessage;
-    dependentStaticProperties: Map<string, boolean> = new Map<
-        string,
-        boolean
-    >();
 
     constructor(private runtimeResolvableService: RuntimeResolvableService) {
         super();
     }
 
-    onInit() {
-        if (
-            this.staticProperty.dependsOn &&
-            this.staticProperty.dependsOn.length > 0
-        ) {
-            this.staticProperty.dependsOn.forEach(dp => {
-                this.dependentStaticProperties.set(dp, false);
-            });
-        }
-    }
+    onInit() {}
 
     loadOptionsFromRestApi(node?: TreeInputNode) {
         const resolvableOptionsParameterRequest = new RuntimeOptionsRequest();
@@ -137,31 +121,14 @@ export abstract class BaseRuntimeResolvableInput<
     }
 
     ngOnChanges(changes: SimpleChanges): void {
-        if (changes['completedStaticProperty']) {
+        if (changes['completedConfigurations']) {
             if (
-                this.completedStaticProperty !== undefined &&
-                !(
-                    this.completedStaticProperty.staticPropertyInternalName ===
-                    this.staticProperty.internalName
+                this.staticPropertyUtils.allDependenciesSatisfied(
+                    this.staticProperty.dependsOn,
+                    this.completedConfigurations,
                 )
             ) {
-                if (
-                    this.dependentStaticProperties.has(
-                        this.completedStaticProperty.staticPropertyInternalName,
-                    )
-                ) {
-                    this.dependentStaticProperties.set(
-                        this.completedStaticProperty.staticPropertyInternalName,
-                        this.completedStaticProperty.configured,
-                    );
-                }
-                if (
-                    Array.from(this.dependentStaticProperties.values()).every(
-                        v => v === true,
-                    )
-                ) {
-                    this.loadOptionsFromRestApi();
-                }
+                this.loadOptionsFromRestApi();
             }
         }
     }
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 88f4dd40b1..eb6ffcc10c 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
@@ -54,7 +54,6 @@ export class StaticRuntimeResolvableOneOfInputComponent
             this.staticProperty.options.length > 0
         ) {
             this.staticProperty.options[0].selected = true;
-            this.emitUpdate(true);
         }
     }
 
@@ -66,7 +65,7 @@ export class StaticRuntimeResolvableOneOfInputComponent
             option => option.elementId === id,
         ).selected = true;
         this.performValidation();
-        this.emitUpdate(true);
+        this.applyCompletedConfiguration(true);
     }
 
     parse(
diff --git a/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.ts b/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.ts
index 2caa19c70d..f085755c3e 100644
--- a/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-secret-input/static-secret-input.component.ts
@@ -36,25 +36,19 @@ export class StaticSecretInputComponent
         super();
     }
 
-    @Output() updateEmitter: EventEmitter<ConfigurationInfo> =
-        new EventEmitter();
-
     ngOnInit() {
         this.addValidator(this.staticProperty.value, Validators.required);
         this.enableValidators();
     }
 
     emitUpdate() {
-        this.updateEmitter.emit(
-            new ConfigurationInfo(
-                this.staticProperty.internalName,
+        this.applyCompletedConfiguration(
+            this.staticPropertyUtil.asFreeTextStaticProperty(
+                this.staticProperty,
+            ).value &&
                 this.staticPropertyUtil.asFreeTextStaticProperty(
                     this.staticProperty,
-                ).value &&
-                    this.staticPropertyUtil.asFreeTextStaticProperty(
-                        this.staticProperty,
-                    ).value !== '',
-            ),
+                ).value !== '',
         );
     }
 
diff --git a/ui/src/app/core-ui/static-properties/static-slide-toggle/static-slide-toggle.component.ts b/ui/src/app/core-ui/static-properties/static-slide-toggle/static-slide-toggle.component.ts
index e74274ae7f..0b0dccee23 100644
--- a/ui/src/app/core-ui/static-properties/static-slide-toggle/static-slide-toggle.component.ts
+++ b/ui/src/app/core-ui/static-properties/static-slide-toggle/static-slide-toggle.component.ts
@@ -37,9 +37,7 @@ export class StaticSlideToggleComponent
     }
 
     emitUpdate() {
-        this.updateEmitter.emit(
-            new ConfigurationInfo(this.staticProperty.internalName, true),
-        );
+        this.applyCompletedConfiguration(true);
     }
 
     onStatusChange(status: any) {}
diff --git a/ui/src/app/dashboard/components/overview/dashboard-overview.component.html b/ui/src/app/dashboard/components/overview/dashboard-overview.component.html
index e51199a6cd..4e5aeee8b7 100644
--- a/ui/src/app/dashboard/components/overview/dashboard-overview.component.html
+++ b/ui/src/app/dashboard/components/overview/dashboard-overview.component.html
@@ -43,6 +43,17 @@
             </button>
         </div>
 
+        <div fxLayout="column">
+            <div class="warning" fxLayout="row" fxLayoutAlign="start center">
+                <i class="material-icons warning-icon">warning</i>
+                <span
+                    >Warning: The Live Dashboard will be removed in future
+                    versions. The live refresh feature has been integrated into
+                    the Data Explorer. Please migrate your current dashboards to
+                    the Data Explorer!</span
+                >
+            </div>
+        </div>
         <div fxFlex="100" fxLayout="column">
             <sp-basic-header-title-component
                 title="Dashboards"
diff --git a/ui/src/app/dashboard/components/overview/dashboard-overview.component.scss b/ui/src/app/dashboard/components/overview/dashboard-overview.component.scss
index 2384389599..74f138c8b1 100644
--- a/ui/src/app/dashboard/components/overview/dashboard-overview.component.scss
+++ b/ui/src/app/dashboard/components/overview/dashboard-overview.component.scss
@@ -35,3 +35,18 @@
     display: inline;
     margin-right: 10px;
 }
+
+.warning {
+    border: 1px solid #dea843;
+    background: #ececc9;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    box-sizing: border-box;
+}
+
+.warning-icon {
+    color: #dea843;
+    font-size: 24px;
+}
diff --git a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/data-explorer-widget-data-settings.component.ts b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/data-explorer-widget-data-settings.component.ts
index c7c1a4bfe7..8823d519b3 100644
--- a/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/data-explorer-widget-data-settings.component.ts
+++ b/ui/src/app/data-explorer/components/data-view/data-view-designer-panel/data-settings/data-explorer-widget-data-settings.component.ts
@@ -83,7 +83,11 @@ export class DataExplorerWidgetDataSettingsComponent implements OnInit {
             this.dataExplorerService.getAllPersistedDataStreams(),
             this.datalakeRestService.getAllMeasurementSeries(),
         ).subscribe(response => {
-            this.availablePipelines = response[0];
+            this.availablePipelines = response[0].filter(
+                p =>
+                    response[1].find(m => m.measureName === p.measureName) !==
+                    undefined,
+            );
             this.availableMeasurements = response[1];
 
             // replace pipeline event schemas. Reason: Available measures do not contain field for timestamp
diff --git a/ui/src/app/data-explorer/components/widget/data-explorer-dashboard-widget.component.html b/ui/src/app/data-explorer/components/widget/data-explorer-dashboard-widget.component.html
index 54e23fe4f9..5bdfed7df1 100644
--- a/ui/src/app/data-explorer/components/widget/data-explorer-dashboard-widget.component.html
+++ b/ui/src/app/data-explorer/components/widget/data-explorer-dashboard-widget.component.html
@@ -65,7 +65,7 @@
                     [matMenuTriggerFor]="menu"
                     aria-label="More options"
                     matTooltip="More options"
-                    *ngIf="!editMode && !dataViewMode"
+                    *ngIf="!dataViewMode"
                     [attr.data-cy]="
                         'more-options-' +
                         configuredWidget.baseAppearanceConfig.widgetTitle.replaceAll(
@@ -97,20 +97,6 @@
                         <span>Edit Widget</span>
                     </button>
                 </mat-menu>
-                <button
-                    mat-icon-button
-                    *ngIf="editMode"
-                    (click)="downloadDataAsFile()"
-                    [attr.data-cy]="
-                        'download-' +
-                        configuredWidget.baseAppearanceConfig.widgetTitle.replaceAll(
-                            ' ',
-                            ''
-                        )
-                    "
-                >
-                    <mat-icon>get_app</mat-icon>
-                </button>
                 <button
                     mat-icon-button
                     [matMenuTriggerFor]="optMenu"
diff --git a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.html b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.html
index 85f43b9f27..059b5d3597 100644
--- a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.html
+++ b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.html
@@ -44,7 +44,12 @@
         </div>
     </div>
     <div fxFlex fxLayoutAlign="start start" fxLayout="column">
-        <div class="element-name" fxLayoutAlign="start start">
+        <div
+            [ngClass]="{
+                'element-name-hover': currentMouseOver,
+                'element-name': !currentMouseOver
+            }"
+        >
             {{ element.name }}
         </div>
         <div
diff --git a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.scss b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.scss
index 170b40e0da..3cc6731de3 100644
--- a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.scss
+++ b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand-row/pipeline-element-icon-stand-row.component.scss
@@ -51,6 +51,19 @@
 .element-name {
     font-size: 11pt;
     font-weight: 500;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: inline-block;
+    white-space: nowrap;
+    max-width: 170px;
+}
+
+.element-name-hover {
+    font-size: 11pt;
+    font-weight: 500;
+    word-wrap: break-word;
+    overflow-wrap: anywhere;
+    word-break: break-word;
 }
 
 .element-description {
diff --git a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.scss b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.scss
index 561556ba11..f9e694eb23 100644
--- a/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.scss
+++ b/ui/src/app/editor/components/pipeline-element-icon-stand/pipeline-element-icon-stand.component.scss
@@ -93,7 +93,7 @@
 }
 
 .search-field {
-    width: 180px;
+    width: 230px;
 }
 
 .group-outer {
diff --git a/ui/src/app/editor/components/pipeline/dropped-pipeline-element/dropped-pipeline-element.component.html b/ui/src/app/editor/components/pipeline/dropped-pipeline-element/dropped-pipeline-element.component.html
index e32ce03c46..b5fe3391bc 100644
--- a/ui/src/app/editor/components/pipeline/dropped-pipeline-element/dropped-pipeline-element.component.html
+++ b/ui/src/app/editor/components/pipeline/dropped-pipeline-element/dropped-pipeline-element.component.html
@@ -62,7 +62,11 @@
         [pipelineElement]="pipelineElementConfig.payload"
     ></sp-pipeline-element>
 </div>
-<div class="editor-pe-info" [ngClass]="'pe-info-' + pipelineElementConfig.type">
+<div
+    class="editor-pe-info"
+    [ngClass]="'pe-info-' + pipelineElementConfig.type"
+    matTooltip="{{ pipelineElementConfig.payload.name }}"
+>
     {{ pipelineElementConfig.payload.name }}
 </div>
 <sp-pipeline-element-statistics
diff --git a/ui/src/app/editor/dialog/customize/customize.component.html b/ui/src/app/editor/dialog/customize/customize.component.html
index 151d88217c..4bd6f2936a 100644
--- a/ui/src/app/editor/dialog/customize/customize.component.html
+++ b/ui/src/app/editor/dialog/customize/customize.component.html
@@ -93,12 +93,11 @@
                                     [eventSchemas]="eventSchemas"
                                     [parentForm]="parentForm"
                                     [fieldName]="config.internalName"
-                                    [completedStaticProperty]="
-                                        completedStaticProperty
+                                    [completedConfigurations]="
+                                        completedConfigurations
                                     "
-                                    (updateEmitter)="triggerUpdate($event)"
-                                    (validateEmitter)="
-                                        validConfiguration($event)
+                                    (completedConfigurationsEmitter)="
+                                        updateCompletedConfiguration($event)
                                     "
                                 >
                                 </sp-app-static-property>
diff --git a/ui/src/app/editor/dialog/customize/customize.component.ts b/ui/src/app/editor/dialog/customize/customize.component.ts
index ae2d73e9ea..5b45b9383f 100644
--- a/ui/src/app/editor/dialog/customize/customize.component.ts
+++ b/ui/src/app/editor/dialog/customize/customize.component.ts
@@ -42,6 +42,7 @@ import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
 import { ShepherdService } from '../../../services/tour/shepherd.service';
 import { ConfigurationInfo } from '../../../connect/model/ConfigurationInfo';
 import { PipelineStyleService } from '../../services/pipeline-style.service';
+import { StaticPropertyUtilService } from '../../../core-ui/static-properties/static-property-util.service';
 
 @Component({
     selector: 'sp-customize-pipeline-element',
@@ -60,13 +61,7 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
     _showDocumentation = false;
 
     selection: any;
-    matchingSelectionLeft: any;
-    matchingSelectionRight: any;
     invalid: any;
-    helpDialogVisible: any;
-    validationErrors: any;
-
-    sourceEndpoint: any;
     sepa: any;
 
     parentForm: UntypedFormGroup;
@@ -82,6 +77,7 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
     templateMode = false;
     template: PipelineElementTemplate;
     templateConfigs: Map<string, any>[] = [];
+    completedConfigurations: ConfigurationInfo[] = [];
 
     constructor(
         private dialogRef: DialogRef<CustomizeComponent>,
@@ -91,6 +87,7 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
         private changeDetectorRef: ChangeDetectorRef,
         private pipelineElementTemplateService: PipelineElementTemplateService,
         private pipelineStyleService: PipelineStyleService,
+        private staticPropertyUtils: StaticPropertyUtilService,
     ) {}
 
     ngOnInit(): void {
@@ -98,6 +95,10 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
         this.cachedPipelineElement = this.jsPlumbService.clone(
             this.pipelineElement.payload,
         ) as InvocablePipelineElementUnion;
+        this.completedConfigurations =
+            this.staticPropertyUtils.initializeCompletedConfigurations(
+                this.cachedPipelineElement.staticProperties,
+            );
         this.isDataProcessor =
             this.cachedPipelineElement instanceof DataProcessorInvocation;
         this.cachedPipelineElement.inputStreams.forEach(is => {
@@ -147,8 +148,6 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
         this.dialogRef.close(this.pipelineElement);
     }
 
-    validConfiguration(event: any) {}
-
     set showDocumentation(value: boolean) {
         if (value) {
             this.dialogRef.changeDialogSize({ width: '90vw' });
@@ -174,6 +173,14 @@ export class CustomizeComponent implements OnInit, AfterViewInit {
         this.completedStaticProperty = { ...configurationInfo };
     }
 
+    updateCompletedConfiguration(configurationInfo: ConfigurationInfo) {
+        this.staticPropertyUtils.updateCompletedConfiguration(
+            configurationInfo,
+            this.completedConfigurations,
+        );
+        this.completedConfigurations = [...this.completedConfigurations];
+    }
+
     triggerTemplateMode() {
         this.template = new PipelineElementTemplate();
         this.templateMode = true;
diff --git a/ui/src/scss/sp/pipeline-element-options.scss b/ui/src/scss/sp/pipeline-element-options.scss
index b5c8999b8e..d1337878e0 100644
--- a/ui/src/scss/sp/pipeline-element-options.scss
+++ b/ui/src/scss/sp/pipeline-element-options.scss
@@ -41,6 +41,9 @@
     text-align: center;
     box-shadow: 0 0 2px #555;
     -webkit-animation: slidein 0.5s;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: block;
 }
 
 @keyframes slidein {


Test patch:
diff --git a/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java
index 4dd462b81b..6d82bcfff8 100644
--- a/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java
+++ b/streampipes-extensions/streampipes-connectors-opcua/src/test/java/org/apache/streampipes/extensions/connectors/opcua/migration/config/OpcUaAdapterVersionedConfig.java
@@ -19,6 +19,7 @@
 package org.apache.streampipes.extensions.connectors.opcua.migration.config;
 
 import org.apache.streampipes.extensions.connectors.opcua.adapter.OpcUaAdapter;
+import org.apache.streampipes.extensions.connectors.opcua.client.OpcUaClientProvider;
 import org.apache.streampipes.model.AdapterType;
 import org.apache.streampipes.model.connect.adapter.AdapterDescription;
 import org.apache.streampipes.model.extensions.ExtensionAssetType;
@@ -52,7 +53,7 @@ import static org.apache.streampipes.extensions.connectors.opcua.utils.OpcUaLabe
 public class OpcUaAdapterVersionedConfig {
 
   public static AdapterDescription getOpcUaAdapterDescriptionV1(){
-    var builder = AdapterConfigurationBuilder.create(ID, 1, OpcUaAdapter::new)
+    var builder = AdapterConfigurationBuilder.create(ID, 1, () -> new OpcUaAdapter(new OpcUaClientProvider()))
         .withAssets(ExtensionAssetType.DOCUMENTATION, ExtensionAssetType.ICON)
         .withLocales(Locales.EN)
         .withCategory(AdapterType.Generic, AdapterType.Manufacturing)
diff --git a/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts b/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.smoke.spec.ts
similarity index 98%
rename from ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts
rename to ui/cypress/tests/connect/opcua/opcAdapterConfiguration.smoke.spec.ts
index 96d2656b4d..fc5aa64745 100644
--- a/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.spec.ts
+++ b/ui/cypress/tests/connect/opcua/opcAdapterConfiguration.smoke.spec.ts
@@ -160,7 +160,8 @@ const getAdapterBuilder = () => {
             'undefined-pull-mode-group-0-PULLING_INTERVAL-0',
             '1000',
         )
-        .addInput('radio', 'access_mode-none', '')
+        .addInput('radio', 'securitymode-none', '')
+        .addInput('radio', 'userauthentication-anonymous', '')
         .addInput('radio', 'opc_host_or_url-url', '')
         .addInput(
             'input',