You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2018/03/23 10:13:48 UTC

[camel] branch master updated (fc44f03 -> 3b56e64)

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

acosentino pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git.


    from fc44f03  CAMEL-12400 - Camel-Git: Add Merge operation
     new 2a466cb  CAMEL-12288: Initial implementation of method calls in the client
     new 73ca068  Use "overrideHost" to lock the host to "localhost" for unit tests
     new 3b56e64  CAMEL-12288 - Fixed CS

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


Summary of changes:
 components/camel-milo/pom.xml                      |   5 +-
 .../src/main/docs/milo-client-component.adoc       |  29 +++-
 .../src/main/docs/milo-server-component.adoc       |   3 +-
 .../apache/camel/component/milo/NamespaceId.java   |  83 ---------
 .../org/apache/camel/component/milo/NodeIds.java   |  30 ++++
 .../apache/camel/component/milo/PartialNodeId.java | 120 -------------
 .../component/milo/client/MiloClientComponent.java |  91 +---------
 .../milo/client/MiloClientConfiguration.java       | 124 ++++++++++++-
 .../milo/client/MiloClientConnection.java          |  56 ++++--
 .../component/milo/client/MiloClientConsumer.java  |  22 ++-
 .../component/milo/client/MiloClientEndpoint.java  |  55 +++---
 .../milo/client/MiloClientItemConfiguration.java   |  28 ---
 .../component/milo/client/MiloClientProducer.java  |  41 +++--
 .../milo/client/internal/SubscriptionManager.java  | 192 +++++++++++++--------
 .../component/milo/server/MiloServerComponent.java |  37 +++-
 .../component/milo/server/MiloServerProducer.java  |   7 +
 .../milo/server/internal/CamelNamespace.java       |   1 -
 .../milo/server/internal/CamelServerItem.java      |  13 +-
 .../component/milo/AbstractMiloServerTest.java     |  13 +-
 .../milo/MonitorItemMultiConnectionsCertTest.java  |  16 +-
 .../milo/MonitorItemMultiConnectionsTest.java      |   6 +-
 .../camel/component/milo/MonitorItemTest.java      |   5 +-
 .../camel/component/milo/WriteClientTest.java      |  16 +-
 .../camel/component/milo/call/CallClientTest.java  | 126 ++++++++++++++
 .../apache/camel/component/milo/call/MockCall.java |  72 ++++++++
 .../camel/component/milo/call/MockNamespace.java   | 177 +++++++++++++++++++
 .../component/milo/{ => client}/NodeIdTest.java    |  15 +-
 .../MiloClientComponentConfiguration.java          |  24 +++
 .../MiloServerComponentConfiguration.java          |  13 ++
 29 files changed, 927 insertions(+), 493 deletions(-)
 delete mode 100644 components/camel-milo/src/main/java/org/apache/camel/component/milo/NamespaceId.java
 delete mode 100644 components/camel-milo/src/main/java/org/apache/camel/component/milo/PartialNodeId.java
 delete mode 100644 components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientItemConfiguration.java
 create mode 100644 components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
 create mode 100644 components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java
 create mode 100644 components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java
 rename components/camel-milo/src/test/java/org/apache/camel/component/milo/{ => client}/NodeIdTest.java (88%)

-- 
To stop receiving notification emails like this one, please contact
acosentino@apache.org.

[camel] 03/03: CAMEL-12288 - Fixed CS

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

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 3b56e64b9b1654c84e2c226e6837122fc17ed708
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Fri Mar 23 11:10:57 2018 +0100

    CAMEL-12288 - Fixed CS
---
 .../camel/component/milo/MonitorItemMultiConnectionsCertTest.java      | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
index 8145f0d..08ee2f8 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
@@ -45,7 +45,8 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest
     // with key
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
                                                         + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
-                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1&discoveryEndpointSuffix=/discovery&overrideHost=true";
+                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1"
+                                                        + "&discoveryEndpointSuffix=/discovery&overrideHost=true";
 
     // with wrong password
     private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node="

-- 
To stop receiving notification emails like this one, please contact
acosentino@apache.org.

[camel] 01/03: CAMEL-12288: Initial implementation of method calls in the client

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

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 2a466cbaf1c32c7b6a2a566add3871831275eedb
Author: Jens Reimann <jr...@redhat.com>
AuthorDate: Wed Mar 21 13:06:13 2018 +0100

    CAMEL-12288: Initial implementation of method calls in the client
    
    This change refactors the code and adds the ability to perform method
    calls instead of write operations.
---
 components/camel-milo/pom.xml                      |   5 +-
 .../src/main/docs/milo-client-component.adoc       |  29 +++-
 .../src/main/docs/milo-server-component.adoc       |   3 +-
 .../apache/camel/component/milo/NamespaceId.java   |  83 ---------
 .../org/apache/camel/component/milo/NodeIds.java   |  30 ++++
 .../apache/camel/component/milo/PartialNodeId.java | 120 -------------
 .../component/milo/client/MiloClientComponent.java |  91 +---------
 .../milo/client/MiloClientConfiguration.java       | 124 ++++++++++++-
 .../milo/client/MiloClientConnection.java          |  56 ++++--
 .../component/milo/client/MiloClientConsumer.java  |  22 ++-
 .../component/milo/client/MiloClientEndpoint.java  |  55 +++---
 .../milo/client/MiloClientItemConfiguration.java   |  28 ---
 .../component/milo/client/MiloClientProducer.java  |  41 +++--
 .../milo/client/internal/SubscriptionManager.java  | 192 +++++++++++++--------
 .../component/milo/server/MiloServerComponent.java |  37 +++-
 .../component/milo/server/MiloServerProducer.java  |   7 +
 .../milo/server/internal/CamelNamespace.java       |   1 -
 .../milo/server/internal/CamelServerItem.java      |  13 +-
 .../component/milo/AbstractMiloServerTest.java     |  13 +-
 .../milo/MonitorItemMultiConnectionsCertTest.java  |  13 +-
 .../camel/component/milo/MonitorItemTest.java      |   4 +-
 .../camel/component/milo/WriteClientTest.java      |   4 +-
 .../camel/component/milo/call/CallClientTest.java  | 126 ++++++++++++++
 .../apache/camel/component/milo/call/MockCall.java |  72 ++++++++
 .../camel/component/milo/call/MockNamespace.java   | 177 +++++++++++++++++++
 .../component/milo/{ => client}/NodeIdTest.java    |  15 +-
 .../MiloClientComponentConfiguration.java          |  24 +++
 .../MiloServerComponentConfiguration.java          |  13 ++
 28 files changed, 912 insertions(+), 486 deletions(-)

diff --git a/components/camel-milo/pom.xml b/components/camel-milo/pom.xml
index 96b42aa..56f9c8a 100644
--- a/components/camel-milo/pom.xml
+++ b/components/camel-milo/pom.xml
@@ -103,8 +103,11 @@
           <reuseForks>true</reuseForks>
           <forkedProcessTimeoutInSeconds>300</forkedProcessTimeoutInSeconds>
           <includes>
-            <!-- Here we only run one test -->
+            <!-- Here we only run a few tests -->
             <include>**/ConverterTest.java</include>
+            <include>**/NodeIdTest.java</include>
+            <include>**/ServerSetCertificateManagerTest</include>
+            <include>**/ServerSetSecurityPoliciesTest.java</include>
           </includes>
         </configuration>
       </plugin>
diff --git a/components/camel-milo/src/main/docs/milo-client-component.adoc b/components/camel-milo/src/main/docs/milo-client-component.adoc
index 097e4c4..b7dfad1 100644
--- a/components/camel-milo/src/main/docs/milo-client-component.adoc
+++ b/components/camel-milo/src/main/docs/milo-client-component.adoc
@@ -97,7 +97,7 @@ with the following path and query parameters:
 |===
 
 
-==== Query Parameters (24 parameters):
+==== Query Parameters (27 parameters):
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
@@ -105,6 +105,9 @@ with the following path and query parameters:
 | Name | Description | Default | Type
 | *clientId* (common) | A virtual client id to force the creation of a new connection instance |  | String
 | *defaultAwaitWrites* (common) | Default await setting for writes | false | boolean
+| *discoveryEndpointSuffix* (common) | A suffix for endpoint URI when discovering |  | String
+| *discoveryEndpointUri* (common) | An alternative discovery URI |  | String
+| *method* (common) | The method definition (see Method ID) |  | ExpandedNodeId
 | *node* (common) | The node definition (see Node ID) |  | ExpandedNodeId
 | *samplingInterval* (common) | The sampling interval in milliseconds |  | Double
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean
@@ -133,7 +136,19 @@ with the following path and query parameters:
 
 
 
+==== Discovery
 
+If the server uses a dedicated discovery endpoint (e.g. `/discovery`), which may support different (less secure) security policies,
+then you can make use of this via the parameter `discoveryEndpointSuffix`, which will be appended to the `endpointUri`. Or by using
+an explicit `discoveryEndpointUri`.
+
+==== Overriding the host name
+
+The client uses the host information from the endpoint information, queried from the server. However in some situations this endpoint URI
+might be different, and wrong from the point of view of the connecting client (e.g. an internal hostname).
+
+In this case it is possible to set the parameter `overrideHost` to `true`, which will take the discovered endpoint information,
+but override the host information with the value of the original URI.
 
 ==== Node ID
 
@@ -166,6 +181,18 @@ However Camel allows to wrap the actual value inside `RAW(…)`, which makes esc
 milo-client://user:password@localhost:12345?node=RAW(nsu=http://foo.bar;s=foo/bar)
 ------------------------
 
+==== Method ID
+
+It is possible to perform methods calls on OPC UA nodes. If the parameter `method` is set to the Node ID of a method call (the node ID must be set to the parent object in this case),
+then a method call will be performed instead of a write operation.
+
+Input parameters are taken from the body:
+
+* If the body is null, then an empty `Variant[]` will be used
+* If the body is a `Variant[]`, then it will be used as is
+* If the body is a `Variant`, then it will be wrapped in a `Variant[]` array
+* Otherwise the body will be converted into a `Variant` and wrapped in an array of `Variant[]`
+
 ==== Security policies
 
 When setting the allowing security policies is it possible to use the well known OPC UA URIs (e.g. `http://opcfoundation.org/UA/SecurityPolicy#Basic128Rsa15`)
diff --git a/components/camel-milo/src/main/docs/milo-server-component.adoc b/components/camel-milo/src/main/docs/milo-server-component.adoc
index 2a809a6..eb76ce5 100644
--- a/components/camel-milo/src/main/docs/milo-server-component.adoc
+++ b/components/camel-milo/src/main/docs/milo-server-component.adoc
@@ -26,7 +26,7 @@ Value write requests from OPC UA Client will trigger messages which are sent int
 
 
 // component options: START
-The OPC UA Server component supports 19 options which are listed below.
+The OPC UA Server component supports 20 options which are listed below.
 
 
 
@@ -45,6 +45,7 @@ The OPC UA Server component supports 19 options which are listed below.
 | *securityPoliciesById* (common) | Security policies by URI or name |  | String>
 | *userAuthentication Credentials* (common) | Set user password combinations in the form of user1:pwd1,user2:pwd2 Usernames and passwords will be URL decoded |  | String
 | *enableAnonymous Authentication* (common) | Enable anonymous authentication, disabled by default | false | boolean
+| *usernameSecurityPolicy Uri* (common) | Set the UserTokenPolicy used when |  | SecurityPolicy
 | *bindAddresses* (common) | Set the addresses of the local addresses the server should bind to |  | String
 | *buildInfo* (common) | Server build info |  | BuildInfo
 | *serverCertificate* (common) | Server certificate |  | Result
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/NamespaceId.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/NamespaceId.java
deleted file mode 100644
index 8e19ec0..0000000
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/NamespaceId.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * 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.camel.component.milo;
-
-import java.io.Serializable;
-
-import static java.util.Objects.requireNonNull;
-
-import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
-import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
-
-public class NamespaceId {
-    private final String uri;
-    private final UShort numeric;
-
-    public NamespaceId(final String uri) {
-        requireNonNull(uri);
-
-        this.uri = uri;
-        this.numeric = null;
-    }
-
-    public NamespaceId(final UShort numeric) {
-        requireNonNull(numeric);
-
-        this.uri = null;
-        this.numeric = numeric;
-    }
-
-    public String getUri() {
-        return this.uri;
-    }
-
-    public UShort getNumeric() {
-        return this.numeric;
-    }
-
-    public boolean isNumeric() {
-        return this.numeric != null;
-    }
-
-    @Override
-    public String toString() {
-        if (isNumeric()) {
-            return String.format("[Namespace - numeric: %s]", this.numeric);
-        } else {
-            return String.format("[Namespace - URI: %s]", this.uri);
-        }
-    }
-
-    public Serializable getValue() {
-        return this.uri != null ? this.uri : this.numeric;
-    }
-
-    public static NamespaceId fromExpandedNodeId(final ExpandedNodeId id) {
-        if (id == null) {
-            return null;
-        }
-
-        if (id.getNamespaceUri() != null) {
-            return new NamespaceId(id.getNamespaceUri());
-        }
-        if (id.getNamespaceIndex() != null) {
-            return new NamespaceId(id.getNamespaceIndex());
-        }
-
-        throw new IllegalStateException(String.format("Unknown namespace type"));
-    }
-}
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/NodeIds.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/NodeIds.java
index 0dad418..db2c619 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/NodeIds.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/NodeIds.java
@@ -20,7 +20,15 @@ import java.util.Base64;
 import java.util.Objects;
 import java.util.UUID;
 
+import static java.util.Objects.requireNonNull;
+
 import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;
 
 /**
  * Helper class to work with node IDs
@@ -29,6 +37,28 @@ public final class NodeIds {
     private NodeIds() {
     }
 
+    public static NodeId toNodeId(final UShort namespaceIndex, final ExpandedNodeId nodeId) {
+        requireNonNull(namespaceIndex);
+
+        final Object id = nodeId.getIdentifier();
+
+        if (id instanceof String) {
+            return new NodeId(namespaceIndex, (String)id);
+        } else if (id instanceof UInteger) {
+            return new NodeId(namespaceIndex, (UInteger)id);
+        } else if (id instanceof ByteString) {
+            return new NodeId(namespaceIndex, (ByteString)id);
+        } else if (id instanceof UUID) {
+            return new NodeId(namespaceIndex, (UUID)id);
+        }
+
+        throw new IllegalStateException("Invalid id type: " + id);
+    }
+
+    public static NodeId toNodeId(final int namespaceIndex, final ExpandedNodeId nodeId) {
+        return toNodeId(ushort(namespaceIndex), nodeId);
+    }
+
     /**
      * Create an attribute value for the "node" attribute
      * 
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/PartialNodeId.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/PartialNodeId.java
deleted file mode 100644
index a9e2006..0000000
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/PartialNodeId.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * 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.camel.component.milo;
-
-import java.io.Serializable;
-import java.util.UUID;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.MoreObjects;
-
-import org.eclipse.milo.opcua.stack.core.types.builtin.ByteString;
-import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
-import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
-import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
-import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
-import org.eclipse.milo.opcua.stack.core.types.enumerated.IdType;
-
-import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
-import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;
-
-public class PartialNodeId {
-
-    private IdType type;
-
-    private final Serializable id;
-
-    public PartialNodeId(final int id) {
-        this(uint(id));
-    }
-
-    public PartialNodeId(final UInteger id) {
-        requireNonNull(id);
-        this.id = id;
-    }
-
-    public PartialNodeId(final String id) {
-        requireNonNull(id);
-        this.id = id;
-    }
-
-    public PartialNodeId(final UUID id) {
-        requireNonNull(id);
-        this.id = id;
-    }
-
-    public PartialNodeId(final ByteString id) {
-        requireNonNull(id);
-        this.id = id;
-    }
-
-    public NodeId toNodeId(final int namespaceIndex) {
-        if (this.id instanceof String) {
-            return new NodeId(namespaceIndex, (String)this.id);
-        } else if (this.id instanceof UInteger) {
-            return new NodeId(ushort(namespaceIndex), (UInteger)this.id);
-        } else if (this.id instanceof ByteString) {
-            return new NodeId(namespaceIndex, (ByteString)this.id);
-        } else if (this.id instanceof UUID) {
-            return new NodeId(namespaceIndex, (UUID)this.id);
-        }
-        throw new IllegalStateException("Invalid id type: " + this.id);
-    }
-
-    public NodeId toNodeId(final UShort namespaceIndex) {
-        if (this.id instanceof String) {
-            return new NodeId(namespaceIndex, (String)this.id);
-        } else if (this.id instanceof UInteger) {
-            return new NodeId(namespaceIndex, (UInteger)this.id);
-        } else if (this.id instanceof ByteString) {
-            return new NodeId(namespaceIndex, (ByteString)this.id);
-        } else if (this.id instanceof UUID) {
-            return new NodeId(namespaceIndex, (UUID)this.id);
-        }
-        throw new IllegalStateException("Invalid id type: " + this.id);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(this).add("type", this.type).add("id", this.id).toString();
-    }
-
-    public Serializable getValue() {
-        return this.id;
-    }
-
-    public static PartialNodeId fromExpandedNodeId(final ExpandedNodeId node) {
-        if (node == null) {
-            return null;
-        }
-
-        final Object value = node.getIdentifier();
-
-        if (value instanceof String) {
-            return new PartialNodeId((String)value);
-        } else if (value instanceof UInteger) {
-            return new PartialNodeId((UInteger)value);
-        } else if (value instanceof UUID) {
-            return new PartialNodeId((UUID)value);
-        } else if (value instanceof ByteString) {
-            return new PartialNodeId((ByteString)value);
-        }
-
-        throw new IllegalStateException(String.format("Unknown node id type: " + value));
-    }
-}
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientComponent.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientComponent.java
index 9eb38c2..2a858ff 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientComponent.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientComponent.java
@@ -16,24 +16,13 @@
  */
 package org.apache.camel.component.milo.client;
 
-import java.io.IOException;
-import java.security.GeneralSecurityException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.function.Consumer;
 
-import com.google.common.base.Supplier;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
-
 import org.apache.camel.Endpoint;
-import org.apache.camel.component.milo.KeyStoreLoader;
-import org.apache.camel.component.milo.KeyStoreLoader.Result;
 import org.apache.camel.impl.DefaultComponent;
-import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
-import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
-import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
-import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,13 +47,15 @@ public class MiloClientComponent extends DefaultComponent {
 
     private synchronized MiloClientEndpoint createEndpoint(final String uri, final MiloClientConfiguration configuration, final Map<String, Object> parameters) throws Exception {
 
-        MiloClientConnection connection = this.cache.get(configuration.toCacheId());
+        final String cacheId = configuration.toCacheId();
+
+        MiloClientConnection connection = this.cache.get(cacheId);
 
         if (connection == null) {
-            LOG.info("Cache miss - creating new connection instance: {}", configuration.toCacheId());
+            LOG.info("Cache miss - creating new connection instance: {}", cacheId);
 
-            connection = new MiloClientConnection(configuration, mapToClientConfiguration(configuration));
-            this.cache.put(configuration.toCacheId(), connection);
+            connection = new MiloClientConnection(configuration);
+            this.cache.put(cacheId, connection);
         }
 
         final MiloClientEndpoint endpoint = new MiloClientEndpoint(uri, this, connection, configuration.getEndpointUri());
@@ -73,79 +64,11 @@ public class MiloClientComponent extends DefaultComponent {
 
         // register connection with endpoint
 
-        this.connectionMap.put(configuration.toCacheId(), endpoint);
+        this.connectionMap.put(cacheId, endpoint);
 
         return endpoint;
     }
 
-    private OpcUaClientConfigBuilder mapToClientConfiguration(final MiloClientConfiguration configuration) {
-        final OpcUaClientConfigBuilder builder = new OpcUaClientConfigBuilder();
-
-        whenHasText(configuration::getApplicationName, value -> builder.setApplicationName(LocalizedText.english(value)));
-        whenHasText(configuration::getApplicationUri, builder::setApplicationUri);
-        whenHasText(configuration::getProductUri, builder::setProductUri);
-
-        if (configuration.getRequestTimeout() != null) {
-            builder.setRequestTimeout(Unsigned.uint(configuration.getRequestTimeout()));
-        }
-        if (configuration.getChannelLifetime() != null) {
-            builder.setChannelLifetime(Unsigned.uint(configuration.getChannelLifetime()));
-        }
-
-        whenHasText(configuration::getSessionName, value -> builder.setSessionName(() -> value));
-        if (configuration.getSessionTimeout() != null) {
-            builder.setSessionTimeout(UInteger.valueOf(configuration.getSessionTimeout()));
-        }
-
-        if (configuration.getMaxPendingPublishRequests() != null) {
-            builder.setMaxPendingPublishRequests(UInteger.valueOf(configuration.getMaxPendingPublishRequests()));
-        }
-
-        if (configuration.getMaxResponseMessageSize() != null) {
-            builder.setMaxResponseMessageSize(UInteger.valueOf(configuration.getMaxPendingPublishRequests()));
-        }
-
-        if (configuration.getKeyStoreUrl() != null) {
-            setKey(configuration, builder);
-        }
-
-        return builder;
-    }
-
-    private void setKey(final MiloClientConfiguration configuration, final OpcUaClientConfigBuilder builder) {
-        final KeyStoreLoader loader = new KeyStoreLoader();
-
-        final Result result;
-        try {
-            // key store properties
-            loader.setType(configuration.getKeyStoreType());
-            loader.setUrl(configuration.getKeyStoreUrl());
-            loader.setKeyStorePassword(configuration.getKeyStorePassword());
-
-            // key properties
-            loader.setKeyAlias(configuration.getKeyAlias());
-            loader.setKeyPassword(configuration.getKeyPassword());
-
-            result = loader.load();
-        } catch (GeneralSecurityException | IOException e) {
-            throw new IllegalStateException("Failed to load key", e);
-        }
-
-        if (result == null) {
-            throw new IllegalStateException("Key not found in keystore");
-        }
-
-        builder.setCertificate(result.getCertificate());
-        builder.setKeyPair(result.getKeyPair());
-    }
-
-    private void whenHasText(final Supplier<String> valueSupplier, final Consumer<String> valueConsumer) {
-        final String value = valueSupplier.get();
-        if (value != null && !value.isEmpty()) {
-            valueConsumer.accept(value);
-        }
-    }
-
     /**
      * All default options for client
      */
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
index 75743ea..14184da 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConfiguration.java
@@ -16,15 +16,24 @@
  */
 package org.apache.camel.component.milo.client;
 
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.security.GeneralSecurityException;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Consumer;
 
+import com.google.common.base.Supplier;
 import org.apache.camel.component.milo.KeyStoreLoader;
+import org.apache.camel.component.milo.KeyStoreLoader.Result;
 import org.apache.camel.spi.UriParam;
 import org.apache.camel.spi.UriParams;
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
 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.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
 
 @UriParams
 public class MiloClientConfiguration implements Cloneable {
@@ -38,6 +47,12 @@ public class MiloClientConfiguration implements Cloneable {
     private String endpointUri;
 
     @UriParam
+    private String discoveryEndpointUri;
+
+    @UriParam
+    private String discoveryEndpointSuffix;
+
+    @UriParam
     private String clientId;
 
     @UriParam(label = "client", defaultValue = DEFAULT_APPLICATION_NAME)
@@ -92,11 +107,23 @@ public class MiloClientConfiguration implements Cloneable {
     }
 
     public MiloClientConfiguration(final MiloClientConfiguration other) {
-        this.clientId = other.clientId;
         this.endpointUri = other.endpointUri;
+        this.discoveryEndpointUri = other.discoveryEndpointUri;
+        this.discoveryEndpointSuffix = other.discoveryEndpointSuffix;
+        this.clientId = other.clientId;
         this.applicationName = other.applicationName;
+        this.applicationUri = other.applicationUri;
         this.productUri = other.productUri;
         this.requestTimeout = other.requestTimeout;
+        this.channelLifetime = other.channelLifetime;
+        this.sessionName = other.sessionName;
+        this.maxPendingPublishRequests = other.maxPendingPublishRequests;
+        this.maxResponseMessageSize = other.maxResponseMessageSize;
+        this.keyStoreUrl = other.keyStoreUrl;
+        this.keyStoreType = other.keyStoreType;
+        this.keyAlias = other.keyAlias;
+        this.keyStorePassword = other.keyStorePassword;
+        this.keyPassword = other.keyPassword;
         this.allowedSecurityPolicies = allowedSecurityPolicies != null ? new HashSet<>(other.allowedSecurityPolicies) : null;
         this.overrideHost = other.overrideHost;
     }
@@ -110,6 +137,28 @@ public class MiloClientConfiguration implements Cloneable {
     }
 
     /**
+     * An alternative discovery URI
+     */
+    public void setDiscoveryEndpointUri(final String endpointDiscoveryUri) {
+        this.discoveryEndpointUri = endpointDiscoveryUri;
+    }
+
+    public String getDiscoveryEndpointUri() {
+        return this.discoveryEndpointUri;
+    }
+
+    /**
+     * A suffix for endpoint URI when discovering
+     */
+    public void setDiscoveryEndpointSuffix(final String endpointDiscoverySuffix) {
+        this.discoveryEndpointSuffix = endpointDiscoverySuffix;
+    }
+
+    public String getDiscoveryEndpointSuffix() {
+        return this.discoveryEndpointSuffix;
+    }
+
+    /**
      * A virtual client id to force the creation of a new connection instance
      */
     public void setClientId(final String clientId) {
@@ -347,4 +396,77 @@ public class MiloClientConfiguration implements Cloneable {
             return this.endpointUri;
         }
     }
+
+    public OpcUaClientConfigBuilder newBuilder() {
+        return mapToClientConfiguration(this);
+    }
+
+    private static OpcUaClientConfigBuilder mapToClientConfiguration(final MiloClientConfiguration configuration) {
+        final OpcUaClientConfigBuilder builder = new OpcUaClientConfigBuilder();
+
+        whenHasText(configuration::getApplicationName, value -> builder.setApplicationName(LocalizedText.english(value)));
+        whenHasText(configuration::getApplicationUri, builder::setApplicationUri);
+        whenHasText(configuration::getProductUri, builder::setProductUri);
+
+        if (configuration.getRequestTimeout() != null) {
+            builder.setRequestTimeout(Unsigned.uint(configuration.getRequestTimeout()));
+        }
+        if (configuration.getChannelLifetime() != null) {
+            builder.setChannelLifetime(Unsigned.uint(configuration.getChannelLifetime()));
+        }
+
+        whenHasText(configuration::getSessionName, value -> builder.setSessionName(() -> value));
+        if (configuration.getSessionTimeout() != null) {
+            builder.setSessionTimeout(UInteger.valueOf(configuration.getSessionTimeout()));
+        }
+
+        if (configuration.getMaxPendingPublishRequests() != null) {
+            builder.setMaxPendingPublishRequests(UInteger.valueOf(configuration.getMaxPendingPublishRequests()));
+        }
+
+        if (configuration.getMaxResponseMessageSize() != null) {
+            builder.setMaxResponseMessageSize(UInteger.valueOf(configuration.getMaxPendingPublishRequests()));
+        }
+
+        if (configuration.getKeyStoreUrl() != null) {
+            setKey(configuration, builder);
+        }
+
+        return builder;
+    }
+
+    private static void setKey(final MiloClientConfiguration configuration, final OpcUaClientConfigBuilder builder) {
+        final KeyStoreLoader loader = new KeyStoreLoader();
+
+        final Result result;
+        try {
+            // key store properties
+            loader.setType(configuration.getKeyStoreType());
+            loader.setUrl(configuration.getKeyStoreUrl());
+            loader.setKeyStorePassword(configuration.getKeyStorePassword());
+
+            // key properties
+            loader.setKeyAlias(configuration.getKeyAlias());
+            loader.setKeyPassword(configuration.getKeyPassword());
+
+            result = loader.load();
+        } catch (GeneralSecurityException | IOException e) {
+            throw new IllegalStateException("Failed to load key", e);
+        }
+
+        if (result == null) {
+            throw new IllegalStateException("Key not found in keystore");
+        }
+
+        builder.setCertificate(result.getCertificate());
+        builder.setKeyPair(result.getKeyPair());
+    }
+
+    private static void whenHasText(final Supplier<String> valueSupplier, final Consumer<String> valueConsumer) {
+        final String value = valueSupplier.get();
+        if (value != null && !value.isEmpty()) {
+            valueConsumer.accept(value);
+        }
+    }
+
 }
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConnection.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConnection.java
index a19e963..d7af594 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConnection.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConnection.java
@@ -16,19 +16,19 @@
  */
 package org.apache.camel.component.milo.client;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.function.Consumer;
 
 import static java.util.Objects.requireNonNull;
 
-import org.apache.camel.component.milo.NamespaceId;
-import org.apache.camel.component.milo.PartialNodeId;
 import org.apache.camel.component.milo.client.internal.SubscriptionManager;
-import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
 import org.eclipse.milo.opcua.stack.core.Stack;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
 import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
 import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.structured.CallMethodResult;
 
 public class MiloClientConnection implements AutoCloseable {
 
@@ -38,18 +38,15 @@ public class MiloClientConnection implements AutoCloseable {
 
     private boolean initialized;
 
-    private final OpcUaClientConfigBuilder clientConfiguration;
-
-    public MiloClientConnection(final MiloClientConfiguration configuration, final OpcUaClientConfigBuilder clientConfiguration) {
+    public MiloClientConnection(final MiloClientConfiguration configuration) {
         requireNonNull(configuration);
 
         // make a copy since the configuration is mutable
         this.configuration = configuration.clone();
-        this.clientConfiguration = clientConfiguration;
     }
 
     protected void init() throws Exception {
-        this.manager = new SubscriptionManager(this.configuration, this.clientConfiguration, Stack.sharedScheduledExecutor(), 10_000);
+        this.manager = new SubscriptionManager(this.configuration, Stack.sharedScheduledExecutor(), 10_000);
     }
 
     @Override
@@ -78,17 +75,14 @@ public class MiloClientConnection implements AutoCloseable {
         void unregister();
     }
 
-    public MonitorHandle monitorValue(final MiloClientItemConfiguration configuration, final Consumer<DataValue> valueConsumer) {
+    public MonitorHandle monitorValue(final ExpandedNodeId nodeId, Double samplingInterval, final Consumer<DataValue> valueConsumer) {
 
         requireNonNull(configuration);
         requireNonNull(valueConsumer);
 
         checkInit();
 
-        final NamespaceId namespaceId = configuration.makeNamespaceId();
-        final PartialNodeId partialNodeId = configuration.makePartialNodeId();
-
-        final UInteger handle = this.manager.registerItem(namespaceId, partialNodeId, configuration.getSamplingInterval(), valueConsumer);
+        final UInteger handle = this.manager.registerItem(nodeId, samplingInterval, valueConsumer);
 
         return () -> MiloClientConnection.this.manager.unregisterItem(handle);
     }
@@ -97,10 +91,38 @@ public class MiloClientConnection implements AutoCloseable {
         return this.configuration.toCacheId();
     }
 
-    public void writeValue(final NamespaceId namespaceId, final PartialNodeId partialNodeId, final Object value, final boolean await) {
+    public CompletableFuture<?> writeValue(final ExpandedNodeId nodeId, final Object value) {
+        checkInit();
+
+        return this.manager.write(nodeId, mapWriteValue(value));
+    }
+
+    public CompletableFuture<CallMethodResult> call(final ExpandedNodeId nodeId, final ExpandedNodeId methodId, final Object value) {
         checkInit();
 
-        this.manager.write(namespaceId, partialNodeId, mapValue(value), await);
+        return this.manager.call(nodeId, methodId, mapCallValue(value));
+    }
+
+    /**
+     * Map the incoming value to some value callable to the milo client
+     *
+     * @param value the incoming value
+     * @return the outgoing call request
+     */
+    private Variant[] mapCallValue(final Object value) {
+
+        if (value == null) {
+            return new Variant[0];
+        }
+
+        if (value instanceof Variant[]) {
+            return (Variant[])value;
+        }
+        if (value instanceof Variant) {
+            return new Variant[] {(Variant)value};
+        }
+
+        return new Variant[] {new Variant(value)};
     }
 
     /**
@@ -109,7 +131,7 @@ public class MiloClientConnection implements AutoCloseable {
      * @param value the incoming value
      * @return the outgoing value
      */
-    private DataValue mapValue(final Object value) {
+    private DataValue mapWriteValue(final Object value) {
         if (value instanceof DataValue) {
             return (DataValue)value;
         }
@@ -119,4 +141,4 @@ public class MiloClientConnection implements AutoCloseable {
         return new DataValue(new Variant(value), StatusCode.GOOD, null, null);
     }
 
-}
\ No newline at end of file
+}
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java
index 57f08ed..7982698 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientConsumer.java
@@ -16,7 +16,7 @@
  */
 package org.apache.camel.component.milo.client;
 
-import java.util.Objects;
+import static java.util.Objects.requireNonNull;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
@@ -26,6 +26,7 @@ import org.apache.camel.component.milo.client.MiloClientConnection.MonitorHandle
 import org.apache.camel.impl.DefaultConsumer;
 import org.apache.camel.impl.DefaultMessage;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,26 +36,27 @@ public class MiloClientConsumer extends DefaultConsumer {
 
     private final MiloClientConnection connection;
 
-    private final MiloClientItemConfiguration configuration;
-
     private MonitorHandle handle;
 
-    public MiloClientConsumer(final MiloClientEndpoint endpoint, final Processor processor, final MiloClientConnection connection,
-                              final MiloClientItemConfiguration configuration) {
+    private ExpandedNodeId node;
+
+    private Double samplingInterval;
+
+    public MiloClientConsumer(final MiloClientEndpoint endpoint, final Processor processor, final MiloClientConnection connection) {
         super(endpoint, processor);
 
-        Objects.requireNonNull(connection);
-        Objects.requireNonNull(configuration);
+        requireNonNull(connection);
 
         this.connection = connection;
-        this.configuration = configuration;
+        this.node = endpoint.getNodeId();
+        this.samplingInterval = endpoint.getSamplingInterval();
     }
 
     @Override
     protected void doStart() throws Exception {
         super.doStart();
 
-        this.handle = this.connection.monitorValue(this.configuration, this::handleValueUpdate);
+        this.handle = this.connection.monitorValue(this.node, this.samplingInterval, this::handleValueUpdate);
     }
 
     @Override
@@ -68,6 +70,8 @@ public class MiloClientConsumer extends DefaultConsumer {
     }
 
     private void handleValueUpdate(final DataValue value) {
+        LOG.debug("Handle item update - {} = {}", node, value);
+
         final Exchange exchange = getEndpoint().createExchange();
         exchange.setIn(mapMessage(value));
         try {
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientEndpoint.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientEndpoint.java
index 56041ff..9b4f54f 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientEndpoint.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientEndpoint.java
@@ -21,8 +21,6 @@ import java.util.Objects;
 import org.apache.camel.Consumer;
 import org.apache.camel.Processor;
 import org.apache.camel.Producer;
-import org.apache.camel.component.milo.NamespaceId;
-import org.apache.camel.component.milo.PartialNodeId;
 import org.apache.camel.impl.DefaultEndpoint;
 import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.UriEndpoint;
@@ -35,7 +33,7 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
  * data
  */
 @UriEndpoint(firstVersion = "2.19.0", scheme = "milo-client", syntax = "milo-client:endpointUri", title = "OPC UA Client", consumerClass = MiloClientConsumer.class, label = "iot")
-public class MiloClientEndpoint extends DefaultEndpoint implements MiloClientItemConfiguration {
+public class MiloClientEndpoint extends DefaultEndpoint {
 
     /**
      * The OPC UA server endpoint
@@ -51,6 +49,12 @@ public class MiloClientEndpoint extends DefaultEndpoint implements MiloClientIte
     private ExpandedNodeId node;
 
     /**
+     * The method definition (see Method ID)
+     */
+    @UriParam
+    private ExpandedNodeId method;
+
+    /**
      * The sampling interval in milliseconds
      */
     @UriParam
@@ -97,17 +101,17 @@ public class MiloClientEndpoint extends DefaultEndpoint implements MiloClientIte
 
     @Override
     public Producer createProducer() throws Exception {
-        return new MiloClientProducer(this, this.connection, this, this.defaultAwaitWrites);
+        return new MiloClientProducer(this, this.connection, this.defaultAwaitWrites);
     }
 
     @Override
     public Consumer createConsumer(final Processor processor) throws Exception {
-        return new MiloClientConsumer(this, processor, this.connection, this);
+        return new MiloClientConsumer(this, processor, this.connection);
     }
 
     @Override
     public boolean isSingleton() {
-        return true;
+        return false;
     }
 
     public MiloClientConnection getConnection() {
@@ -116,33 +120,19 @@ public class MiloClientEndpoint extends DefaultEndpoint implements MiloClientIte
 
     // item configuration
 
-    @Override
-    public PartialNodeId makePartialNodeId() {
-        PartialNodeId result = null;
-
-        if (this.node != null) {
-            result = PartialNodeId.fromExpandedNodeId(this.node);
-        }
-
-        if (result == null) {
-            throw new IllegalStateException("Missing or invalid node id configuration");
+    public void setMethod(final String method) {
+        if (method == null) {
+            this.method = null;
         } else {
-            return result;
+            this.method = ExpandedNodeId.parse(method);
         }
     }
 
-    @Override
-    public NamespaceId makeNamespaceId() {
-        NamespaceId result = null;
-
-        if (this.node != null) {
-            result = NamespaceId.fromExpandedNodeId(this.node);
-        }
-
-        if (result == null) {
-            throw new IllegalStateException("Missing or invalid node id configuration");
+    public String getMethod() {
+        if (this.method != null) {
+            return this.method.toParseableString();
         } else {
-            return result;
+            return null;
         }
     }
 
@@ -162,7 +152,14 @@ public class MiloClientEndpoint extends DefaultEndpoint implements MiloClientIte
         }
     }
 
-    @Override
+    ExpandedNodeId getNodeId() {
+        return this.node;
+    }
+
+    ExpandedNodeId getMethodId() {
+        return this.method;
+    }
+
     public Double getSamplingInterval() {
         return this.samplingInterval;
     }
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientItemConfiguration.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientItemConfiguration.java
deleted file mode 100644
index 5e7954d..0000000
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientItemConfiguration.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * 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.camel.component.milo.client;
-
-import org.apache.camel.component.milo.NamespaceId;
-import org.apache.camel.component.milo.PartialNodeId;
-
-public interface MiloClientItemConfiguration {
-    NamespaceId makeNamespaceId();
-
-    PartialNodeId makePartialNodeId();
-
-    Double getSamplingInterval();
-}
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java
index bc3d6c2..5e3c7e8 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/MiloClientProducer.java
@@ -16,47 +16,62 @@
  */
 package org.apache.camel.component.milo.client;
 
-import org.apache.camel.Endpoint;
+import java.util.concurrent.CompletableFuture;
+
+import static java.lang.Boolean.TRUE;
+
+import org.apache.camel.AsyncCallback;
 import org.apache.camel.Exchange;
 import org.apache.camel.Message;
-import org.apache.camel.component.milo.NamespaceId;
-import org.apache.camel.component.milo.PartialNodeId;
-import org.apache.camel.impl.DefaultProducer;
+import org.apache.camel.impl.DefaultAsyncProducer;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class MiloClientProducer extends DefaultProducer {
+public class MiloClientProducer extends DefaultAsyncProducer {
 
     private static final Logger LOG = LoggerFactory.getLogger(MiloClientProducer.class);
 
     private final MiloClientConnection connection;
 
-    private final NamespaceId namespaceId;
-
-    private final PartialNodeId partialNodeId;
+    private final ExpandedNodeId nodeId;
+    private final ExpandedNodeId methodId;
 
     private final boolean defaultAwaitWrites;
 
-    public MiloClientProducer(final Endpoint endpoint, final MiloClientConnection connection, final MiloClientItemConfiguration configuration, final boolean defaultAwaitWrites) {
+    public MiloClientProducer(final MiloClientEndpoint endpoint, final MiloClientConnection connection, final boolean defaultAwaitWrites) {
         super(endpoint);
 
         this.connection = connection;
         this.defaultAwaitWrites = defaultAwaitWrites;
 
-        this.namespaceId = configuration.makeNamespaceId();
-        this.partialNodeId = configuration.makePartialNodeId();
+        this.nodeId = endpoint.getNodeId();
+        this.methodId = endpoint.getMethodId();
     }
 
     @Override
-    public void process(final Exchange exchange) throws Exception {
+    public boolean process(Exchange exchange, AsyncCallback async) {
         final Message msg = exchange.getIn();
         final Object value = msg.getBody();
 
         LOG.debug("Processing message: {}", value);
 
+        final CompletableFuture<?> future;
+
+        if (this.methodId == null) {
+            future = this.connection.writeValue(this.nodeId, value);
+        } else {
+            future = this.connection.call(this.nodeId, this.methodId, value);
+        }
+
         final Boolean await = msg.getHeader("await", this.defaultAwaitWrites, Boolean.class);
 
-        this.connection.writeValue(this.namespaceId, this.partialNodeId, value, await != null ? await : false);
+        if (TRUE.equals(await)) {
+            future.whenComplete((v, ex) -> async.done(false));
+            return false;
+        } else {
+            return true;
+        }
     }
 
 }
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java
index 904217d..4b0c655 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java
@@ -27,7 +27,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -35,8 +34,9 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
-import org.apache.camel.component.milo.NamespaceId;
-import org.apache.camel.component.milo.PartialNodeId;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+
+import com.google.common.base.Strings;
 import org.apache.camel.component.milo.client.MiloClientConfiguration;
 import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
 import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
@@ -54,14 +54,18 @@ import org.eclipse.milo.opcua.stack.core.StatusCodes;
 import org.eclipse.milo.opcua.stack.core.UaException;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
 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.Variant;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
 import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
 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.CallMethodRequest;
+import org.eclipse.milo.opcua.stack.core.types.structured.CallMethodResult;
 import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
 import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
 import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
@@ -69,6 +73,8 @@ import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.component.milo.NodeIds.toNodeId;
+
 public class SubscriptionManager {
 
     private static final Logger LOG = LoggerFactory.getLogger(SubscriptionManager.class);
@@ -107,25 +113,19 @@ public class SubscriptionManager {
     }
 
     private static class Subscription {
-        private final NamespaceId namespaceId;
-        private final PartialNodeId partialNodeId;
+        private final ExpandedNodeId nodeId;
         private final Double samplingInterval;
 
         private final Consumer<DataValue> valueConsumer;
 
-        Subscription(final NamespaceId namespaceId, final PartialNodeId partialNodeId, final Double samplingInterval, final Consumer<DataValue> valueConsumer) {
-            this.namespaceId = namespaceId;
-            this.partialNodeId = partialNodeId;
+        Subscription(ExpandedNodeId nodeId, final Double samplingInterval, final Consumer<DataValue> valueConsumer) {
+            this.nodeId = nodeId;
             this.samplingInterval = samplingInterval;
             this.valueConsumer = valueConsumer;
         }
 
-        public NamespaceId getNamespaceId() {
-            return this.namespaceId;
-        }
-
-        public PartialNodeId getPartialNodeId() {
-            return this.partialNodeId;
+        public ExpandedNodeId getNodeId() {
+            return nodeId;
         }
 
         public Double getSamplingInterval() {
@@ -165,18 +165,12 @@ public class SubscriptionManager {
             for (final Map.Entry<UInteger, Subscription> entry : subscriptions.entrySet()) {
                 final Subscription s = entry.getValue();
 
-                UShort namespaceIndex;
-                if (s.getNamespaceId().isNumeric()) {
-                    namespaceIndex = s.getNamespaceId().getNumeric();
-                } else {
-                    namespaceIndex = lookupNamespace(s.getNamespaceId().getUri());
-                }
+                final NodeId node = lookupNamespace(s.getNodeId()).get();
 
-                if (namespaceIndex == null) {
+                if (node == null) {
                     handleSubscriptionError(new StatusCode(StatusCodes.Bad_InvalidArgument), entry.getKey(), s);
                 } else {
-                    final NodeId nodeId = s.getPartialNodeId().toNodeId(namespaceIndex);
-                    final ReadValueId itemId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);
+                    final ReadValueId itemId = new ReadValueId(node, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);
                     Double samplingInterval = s.getSamplingInterval();
                     if (samplingInterval == null) {
                         // work around a bug (NPE) in Eclipse Milo 0.1.3
@@ -240,10 +234,6 @@ public class SubscriptionManager {
             }
         }
 
-        private UShort lookupNamespace(final String namespaceUri) throws Exception {
-            return lookupNamespaceIndex(namespaceUri).get();
-        }
-
         private CompletableFuture<UShort> lookupNamespaceIndex(final String namespaceUri) {
 
             LOG.trace("Looking up namespace: {}", namespaceUri);
@@ -291,27 +281,31 @@ public class SubscriptionManager {
             }
         }
 
-        public CompletableFuture<StatusCode> write(final NamespaceId namespaceId, final PartialNodeId partialNodeId, final DataValue value) {
+        private CompletableFuture<NodeId> lookupNamespace(final ExpandedNodeId nodeId) {
+            LOG.trace("Expanded Node Id: {}", nodeId);
 
-            final CompletableFuture<UShort> future;
+            final String uri = nodeId.getNamespaceUri();
 
-            LOG.trace("Namespace: {}", namespaceId);
-            if (namespaceId.isNumeric()) {
-                LOG.trace("Using provided index: {}", namespaceId.getNumeric());
-                future = CompletableFuture.completedFuture(namespaceId.getNumeric());
+            if (uri != null) {
+                LOG.trace("Looking up namespace: {}", uri);
+                return lookupNamespaceIndex(uri).thenApply(index -> toNodeId(index, nodeId));
             } else {
-                LOG.trace("Looking up namespace: {}", namespaceId.getUri());
-                future = lookupNamespaceIndex(namespaceId.getUri());
+                final UShort index = nodeId.getNamespaceIndex();
+                LOG.trace("Using provided index: {}", index);
+                return completedFuture(toNodeId(index, nodeId));
             }
 
-            return future.thenCompose(index -> {
+        }
+
+        public CompletableFuture<StatusCode> write(final ExpandedNodeId nodeId, final DataValue value) {
 
-                final NodeId nodeId = partialNodeId.toNodeId(index);
-                LOG.debug("Node - partial: {}, full: {}", partialNodeId, nodeId);
+            return lookupNamespace(nodeId).thenCompose(node -> {
 
-                return this.client.writeValue(nodeId, value).whenComplete((status, error) -> {
+                LOG.debug("Node - expanded: {}, full: {}", nodeId, node);
+
+                return this.client.writeValue(node, value).whenComplete((status, error) -> {
                     if (status != null) {
-                        LOG.debug("Write to ns={}/{}, id={} = {} -> {}", namespaceId, index, nodeId, value, status);
+                        LOG.debug("Write to node={} = {} -> {}", node, value, status);
                     } else {
                         LOG.debug("Failed to write", error);
                     }
@@ -320,10 +314,34 @@ public class SubscriptionManager {
             });
         }
 
+        public CompletableFuture<CallMethodResult> call(final ExpandedNodeId nodeId, final ExpandedNodeId methodId, final Variant[] inputArguments) {
+
+            return lookupNamespace(nodeId).thenCompose(node -> {
+
+                LOG.debug("Node   - expanded: {}, full: {}", nodeId, node);
+
+                return lookupNamespace(methodId).thenCompose(method -> {
+
+                    LOG.debug("Method - expanded: {}, full: {}", methodId, method);
+
+                    final CallMethodRequest cmr = new CallMethodRequest(node, method, inputArguments);
+
+                    return this.client.call(cmr).whenComplete((status, error) -> {
+                        if (status != null) {
+                            LOG.debug("Call to node={}, method={} = {}-> {}", nodeId, methodId, inputArguments, status);
+                        } else {
+                            LOG.debug("Failed to call", error);
+                        }
+                    });
+
+                });
+
+            });
+        }
+
     }
 
     private final MiloClientConfiguration configuration;
-    private final OpcUaClientConfigBuilder clientBuilder;
     private final ScheduledExecutorService executor;
     private final long reconnectTimeout;
 
@@ -332,11 +350,9 @@ public class SubscriptionManager {
     private Future<?> reconnectJob;
     private final Map<UInteger, Subscription> subscriptions = new HashMap<>();
 
-    public SubscriptionManager(final MiloClientConfiguration configuration, final OpcUaClientConfigBuilder clientBuilder, final ScheduledExecutorService executor,
-                               final long reconnectTimeout) {
+    public SubscriptionManager(final MiloClientConfiguration configuration, final ScheduledExecutorService executor, final long reconnectTimeout) {
 
         this.configuration = configuration;
-        this.clientBuilder = clientBuilder;
         this.executor = executor;
         this.reconnectTimeout = reconnectTimeout;
 
@@ -409,7 +425,13 @@ public class SubscriptionManager {
     }
 
     private Connected performConnect() throws Exception {
-        final EndpointDescription endpoint = UaTcpStackClient.getEndpoints(this.configuration.getEndpointUri()).thenApply(endpoints -> {
+
+        // eval enpoint
+
+        final String discoveryUri = getEndpointDiscoveryUri();
+        LOG.debug("Discovering endpoints from: {}", discoveryUri);
+
+        final EndpointDescription endpoint = UaTcpStackClient.getEndpoints(discoveryUri).thenApply(endpoints -> {
             if (LOG.isDebugEnabled()) {
                 LOG.debug("Found enpoints:");
                 for (final EndpointDescription ep : endpoints) {
@@ -426,7 +448,7 @@ public class SubscriptionManager {
 
         LOG.debug("Selected endpoint: {}", endpoint);
 
-        final URI uri = URI.create(this.configuration.getEndpointUri());
+        final URI uri = URI.create(getEndpointDiscoveryUri());
 
         // set identity providers
 
@@ -441,16 +463,14 @@ public class SubscriptionManager {
             providers.add(new UsernameProvider(creds[0], creds[1]));
         }
 
-        // FIXME: need a way to clone
-        final OpcUaClientConfigBuilder cfg = this.clientBuilder;
+        providers.add(AnonymousProvider.INSTANCE);
 
-        providers.add(new AnonymousProvider());
+        final OpcUaClientConfigBuilder cfg = this.configuration.newBuilder();
         cfg.setIdentityProvider(new CompositeProvider(providers));
-
-        // set endpoint
-
         cfg.setEndpoint(endpoint);
 
+        // create client
+
         final OpcUaClient client = new OpcUaClient(cfg.build());
         client.connect().get();
 
@@ -468,6 +488,19 @@ public class SubscriptionManager {
         }
     }
 
+    private String getEndpointDiscoveryUri() {
+
+        if (!Strings.isNullOrEmpty(this.configuration.getDiscoveryEndpointUri())) {
+            return this.configuration.getDiscoveryEndpointUri();
+        }
+
+        if (!Strings.isNullOrEmpty(this.configuration.getDiscoveryEndpointSuffix())) {
+            return this.configuration.getEndpointUri() + this.configuration.getDiscoveryEndpointSuffix();
+        }
+
+        return this.configuration.getEndpointUri();
+    }
+
     public void dispose() {
         Connected connected;
 
@@ -583,10 +616,16 @@ public class SubscriptionManager {
         }
     }
 
-    public UInteger registerItem(final NamespaceId namespaceId, final PartialNodeId partialNodeId, final Double samplingInterval, final Consumer<DataValue> valueConsumer) {
+    private static <T> CompletableFuture<T> newNotConnectedResult() {
+        final CompletableFuture<T> result = new CompletableFuture<>();
+        result.completeExceptionally(new IllegalStateException("No connected"));
+        return result;
+    }
+
+    public UInteger registerItem(final ExpandedNodeId nodeId, final Double samplingInterval, final Consumer<DataValue> valueConsumer) {
 
         final UInteger clientHandle = Unsigned.uint(this.clientHandleCounter.incrementAndGet());
-        final Subscription subscription = new Subscription(namespaceId, partialNodeId, samplingInterval, valueConsumer);
+        final Subscription subscription = new Subscription(nodeId, samplingInterval, valueConsumer);
 
         synchronized (this) {
             this.subscriptions.put(clientHandle, subscription);
@@ -607,30 +646,37 @@ public class SubscriptionManager {
         }
     }
 
-    public void write(final NamespaceId namespaceId, final PartialNodeId partialNodeId, final DataValue value, final boolean await) {
-        CompletableFuture<Object> future = null;
-
+    public CompletableFuture<CallMethodResult> call(final ExpandedNodeId nodeId, final ExpandedNodeId methodId, final Variant[] inputArguments) {
         synchronized (this) {
-            if (this.connected != null) {
-                future = this.connected.write(namespaceId, partialNodeId, value).handleAsync((status, e) -> {
-                    // handle outside the lock, running using
-                    // handleAsync
-                    if (e != null) {
-                        handleConnectionFailue(e);
-                    }
-                    return null;
-                }, this.executor);
+            if (this.connected == null) {
+                return newNotConnectedResult();
             }
+
+            return this.connected.call(nodeId, methodId, inputArguments).handleAsync((status, e) -> {
+                // handle outside the lock, running using
+                // handleAsync
+                if (e != null) {
+                    handleConnectionFailue(e);
+                }
+                return null;
+            }, this.executor);
         }
+    }
 
-        if (await && future != null) {
-            try {
-                future.get();
-            } catch (InterruptedException | ExecutionException e) {
-                // should never happen since our previous handler should not
-                // fail
-                LOG.warn("Failed to wait for completion", e);
+    public CompletableFuture<?> write(final ExpandedNodeId nodeId, final DataValue value) {
+        synchronized (this) {
+            if (this.connected == null) {
+                return newNotConnectedResult();
             }
+
+            return this.connected.write(nodeId, value).handleAsync((status, e) -> {
+                // handle outside the lock, running using
+                // handleAsync
+                if (e != null) {
+                    handleConnectionFailue(e);
+                }
+                return null;
+            }, this.executor);
         }
     }
 
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
index 62665a1..58c70be 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerComponent.java
@@ -57,6 +57,7 @@ import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateManager;
 import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateValidator;
 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.enumerated.UserTokenType;
 import org.eclipse.milo.opcua.stack.core.types.structured.BuildInfo;
 import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
 import org.slf4j.Logger;
@@ -122,6 +123,8 @@ public class MiloServerComponent extends DefaultComponent {
 
     private Map<String, String> userMap;
 
+    private String usernameSecurityPolicyUri = OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME.getSecurityPolicyUri();
+
     private List<String> bindAddresses;
 
     private Supplier<CertificateValidator> certificateValidator;
@@ -157,7 +160,7 @@ public class MiloServerComponent extends DefaultComponent {
             // set identity validator
 
             final Map<String, String> userMap = this.userMap != null ? new HashMap<>(this.userMap) : Collections.emptyMap();
-            final boolean allowAnonymous = this.enableAnonymousAuthentication != null ? this.enableAnonymousAuthentication : false;
+            final boolean allowAnonymous = Boolean.TRUE.equals(this.enableAnonymousAuthentication);
             final IdentityValidator identityValidator = new UsernameIdentityValidator(allowAnonymous, challenge -> {
                 final String pwd = userMap.get(challenge.getUsername());
                 if (pwd == null) {
@@ -170,11 +173,11 @@ public class MiloServerComponent extends DefaultComponent {
             // add token policies
 
             final List<UserTokenPolicy> tokenPolicies = new LinkedList<>();
-            if (Boolean.TRUE.equals(this.enableAnonymousAuthentication)) {
+            if (allowAnonymous) {
                 tokenPolicies.add(OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS);
             }
             if (userMap != null) {
-                tokenPolicies.add(OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME);
+                tokenPolicies.add(getUsernamePolicy());
             }
             this.serverConfig.setUserTokenPolicies(tokenPolicies);
         }
@@ -204,6 +207,18 @@ public class MiloServerComponent extends DefaultComponent {
         return this.serverConfig.build();
     }
 
+    /**
+     * Get the user token policy for using with username authentication
+     * 
+     * @return the user token policy to use for username authentication
+     */
+    private UserTokenPolicy getUsernamePolicy() {
+        if (this.usernameSecurityPolicyUri == null || this.usernameSecurityPolicyUri.isEmpty()) {
+            return OpcUaServerConfig.USER_TOKEN_POLICY_USERNAME;
+        }
+        return new UserTokenPolicy("username", UserTokenType.UserName, null, null, this.usernameSecurityPolicyUri);
+    }
+
     private void runOnStop(final Runnable runnable) {
         this.runOnStop.add(runnable);
     }
@@ -355,7 +370,7 @@ public class MiloServerComponent extends DefaultComponent {
                     try {
                         this.userMap.put(URLDecoder.decode(toks[0], URL_CHARSET), URLDecoder.decode(toks[1], URL_CHARSET));
                     } catch (final UnsupportedEncodingException e) {
-                        // FIXME: do log
+                        LOG.warn("Failed to decode user map entry", e);
                     }
                 }
             }
@@ -372,6 +387,20 @@ public class MiloServerComponent extends DefaultComponent {
     }
 
     /**
+     * Set the {@link UserTokenPolicy} used when
+     */
+    public void setUsernameSecurityPolicyUri(final SecurityPolicy usernameSecurityPolicy) {
+        this.usernameSecurityPolicyUri = usernameSecurityPolicy.getSecurityPolicyUri();
+    }
+
+    /**
+     * Set the {@link UserTokenPolicy} used when
+     */
+    public void setUsernameSecurityPolicyUri(String usernameSecurityPolicyUri) {
+        this.usernameSecurityPolicyUri = usernameSecurityPolicyUri;
+    }
+
+    /**
      * Set the addresses of the local addresses the server should bind to
      */
     public void setBindAddresses(final String bindAddresses) {
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerProducer.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerProducer.java
index c42da2d..830ad78 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerProducer.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/MiloServerProducer.java
@@ -20,9 +20,13 @@ import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.component.milo.server.internal.CamelServerItem;
 import org.apache.camel.impl.DefaultProducer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class MiloServerProducer extends DefaultProducer {
 
+    private static final Logger LOG = LoggerFactory.getLogger(MiloServerProducer.class);
+
     private final CamelServerItem item;
 
     public MiloServerProducer(final Endpoint endpoint, final CamelServerItem item) {
@@ -33,6 +37,9 @@ public class MiloServerProducer extends DefaultProducer {
     @Override
     public void process(final Exchange exchange) throws Exception {
         final Object value = exchange.getIn().getBody();
+
+        LOG.trace("Update item value - {} = {}", this.item, value);
+
         this.item.update(value);
     }
 }
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java
index c1c762b..6fb6cde 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelNamespace.java
@@ -22,7 +22,6 @@ import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 
 import com.google.common.collect.Lists;
-
 import org.apache.camel.component.milo.client.MiloClientConsumer;
 import org.eclipse.milo.opcua.sdk.core.Reference;
 import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
diff --git a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java
index f57a96e..e5a6c4e5 100644
--- a/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java
+++ b/components/camel-milo/src/main/java/org/apache/camel/component/milo/server/internal/CamelServerItem.java
@@ -42,14 +42,16 @@ import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.
 public class CamelServerItem {
     private static final Logger LOG = LoggerFactory.getLogger(CamelServerItem.class);
 
-    private UaObjectNode baseNode;
-    private UaVariableNode item;
+    private final String itemId;
+    private final UaObjectNode baseNode;
+    private final UaVariableNode item;
 
-    private DataValue value = new DataValue(StatusCode.BAD);
     private final Set<Consumer<DataValue>> listeners = new CopyOnWriteArraySet<>();
+    private DataValue value = new DataValue(StatusCode.BAD);
 
     public CamelServerItem(final String itemId, final ServerNodeMap nodeManager, final UShort namespaceIndex, final UaObjectNode baseNode) {
 
+        this.itemId = itemId;
         this.baseNode = baseNode;
 
         final NodeId nodeId = new NodeId(namespaceIndex, "items-" + itemId);
@@ -139,4 +141,9 @@ public class CamelServerItem {
     public void update(final Object value) {
         this.value = new DataValue(new Variant(value), StatusCode.GOOD, DateTime.now());
     }
+
+    @Override
+    public String toString() {
+        return "[CamelServerItem - '" + this.itemId + "']";
+    }
 }
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java
index 6d41ce2..097f0c2 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/AbstractMiloServerTest.java
@@ -24,6 +24,7 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.component.milo.server.MiloServerComponent;
 import org.apache.camel.component.mock.AssertionClause;
 import org.apache.camel.test.junit4.CamelTestSupport;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
 import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
 
 public abstract class AbstractMiloServerTest extends CamelTestSupport {
@@ -40,6 +41,10 @@ public abstract class AbstractMiloServerTest extends CamelTestSupport {
         return this.serverPort;
     }
 
+    protected boolean isAddServer() {
+        return true;
+    }
+
     /**
      * Replace the port placeholder with the dynamic server port
      * 
@@ -83,14 +88,18 @@ public abstract class AbstractMiloServerTest extends CamelTestSupport {
     }
 
     protected void configureContext(final CamelContext context) throws Exception {
-        final MiloServerComponent server = context.getComponent("milo-server", MiloServerComponent.class);
-        configureMiloServer(server);
+        if (isAddServer()) {
+            final MiloServerComponent server = context.getComponent("milo-server", MiloServerComponent.class);
+            configureMiloServer(server);
+        }
     }
 
     protected void configureMiloServer(final MiloServerComponent server) throws Exception {
         server.setBindAddresses("localhost");
         server.setBindPort(this.serverPort);
         server.setUserAuthenticationCredentials("foo:bar,foo2:bar2");
+        server.setUsernameSecurityPolicyUri(SecurityPolicy.None);
+        server.setSecurityPoliciesById("None");
     }
 
     /**
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
index aacc9b6..93c0793 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.milo;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.EnumSet;
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
@@ -29,6 +30,7 @@ import org.apache.camel.RoutesBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.milo.server.MiloServerComponent;
 import org.apache.camel.component.mock.MockEndpoint;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
 import org.junit.Test;
 
 /**
@@ -43,15 +45,15 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest
     // with key
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
                                                         + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
-                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1";
+                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1&discoveryEndpointSuffix=/discovery";
 
     // with wrong password
     private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery";
 
     // without key, clientId=1
     private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?clientId=1&node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery";
 
     private static final String MOCK_TEST_1 = "mock:test1";
     private static final String MOCK_TEST_2 = "mock:test2";
@@ -81,6 +83,9 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest
 
         server.setServerCertificate(loadDefaultTestKey());
         server.setDefaultCertificateValidator(baseDir.toFile());
+
+        server.setSecurityPolicies(EnumSet.of(SecurityPolicy.Basic256Sha256));
+        server.setUsernameSecurityPolicyUri(SecurityPolicy.Basic256Sha256);
     }
 
     @Override
@@ -119,6 +124,6 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest
         this.producer1.sendBody("Foo");
 
         // assert
-        this.assertMockEndpointsSatisfied();
+        assertMockEndpointsSatisfied();
     }
 }
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
index 3162db5..7367cdd 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
@@ -35,7 +35,7 @@ public class MonitorItemTest extends AbstractMiloServerTest {
     private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1";
 
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&allowedSecurityPolicies=None";
 
     private static final String MOCK_TEST_1 = "mock:test1";
 
@@ -84,6 +84,6 @@ public class MonitorItemTest extends AbstractMiloServerTest {
         testBody(this.test1Endpoint.message(2), assertGoodValue("Baz"));
 
         // assert
-        this.assertMockEndpointsSatisfied();
+        assertMockEndpointsSatisfied();
     }
 }
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
index 6bce7d1..4e30664 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
@@ -104,7 +104,7 @@ public class WriteClientTest extends AbstractMiloServerTest {
         sendValue(this.producer1, new Variant("Foo2"));
 
         // assert
-        this.assertMockEndpointsSatisfied();
+        assertMockEndpointsSatisfied();
     }
 
     @Test
@@ -122,7 +122,7 @@ public class WriteClientTest extends AbstractMiloServerTest {
         sendValue(this.producer2, new Variant("Foo2"));
 
         // assert
-        this.assertMockEndpointsSatisfied();
+        assertMockEndpointsSatisfied();
     }
 
     @Test
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
new file mode 100644
index 0000000..51574ad
--- /dev/null
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
@@ -0,0 +1,126 @@
+/**
+ * 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.camel.component.milo.call;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.camel.Produce;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.milo.AbstractMiloServerTest;
+import org.apache.camel.component.milo.call.MockCall.Call1;
+import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
+import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;
+import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfigBuilder;
+import org.eclipse.milo.opcua.sdk.server.identity.AnonymousIdentityValidator;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.stack.core.application.DefaultCertificateManager;
+import org.eclipse.milo.opcua.stack.core.application.InsecureCertificateValidator;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.camel.component.milo.NodeIds.nodeValue;
+
+/**
+ * Unit tests for calling from the client side
+ */
+public class CallClientTest extends AbstractMiloServerTest {
+
+    private static final String DIRECT_START_1 = "direct:start1";
+
+    private static final String MILO_CLIENT_BASE_C1 = "milo-client:tcp://localhost:@@port@@";
+
+    private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MockNamespace.URI, MockNamespace.FOLDER_ID) + "&method="
+                                                        + nodeValue(MockNamespace.URI, "id1");
+
+    @Produce(uri = DIRECT_START_1)
+    protected ProducerTemplate producer1;
+
+    private OpcUaServer server;
+
+    private Call1 call1;
+
+    @Override
+    protected boolean isAddServer() {
+        return false;
+    }
+
+    @Before
+    public void start() throws Exception {
+        final OpcUaServerConfigBuilder config = new OpcUaServerConfigBuilder();
+        config.setBindAddresses(Arrays.asList("localhost"));
+        config.setBindPort(getServerPort());
+        config.setIdentityValidator(AnonymousIdentityValidator.INSTANCE);
+        config.setUserTokenPolicies(Arrays.asList(OpcUaServerConfig.USER_TOKEN_POLICY_ANONYMOUS));
+        config.setSecurityPolicies(EnumSet.of(SecurityPolicy.None));
+        config.setCertificateManager(new DefaultCertificateManager());
+        config.setCertificateValidator(new InsecureCertificateValidator());
+
+        this.server = new OpcUaServer(config.build());
+
+        this.call1 = new MockCall.Call1();
+
+        this.server.getNamespaceManager().registerAndAdd(MockNamespace.URI, index -> {
+
+            final List<UaMethodNode> methods = new LinkedList<>();
+            methods.add(MockCall.fromNode(index, this.server.getNodeMap(), "id1", "name1", this.call1));
+
+            return new MockNamespace(index, this.server, methods);
+        });
+
+        this.server.startup().get();
+    }
+
+    @After
+    public void stop() {
+        this.server.shutdown();
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from(DIRECT_START_1).to(resolve(MILO_CLIENT_ITEM_C1_1));
+            }
+        };
+    }
+
+    @Test
+    public void testCall1() throws Exception {
+        // call
+
+        doCall(this.producer1, "foo");
+        doCall(this.producer1, "bar");
+
+        // assert
+
+        Assert.assertArrayEquals(new Object[] {"foo", "bar"}, this.call1.calls.toArray());
+    }
+
+    private static void doCall(final ProducerTemplate producerTemplate, final Object input) {
+        // we always write synchronously since we do need the message order
+        producerTemplate.sendBodyAndHeader(input, "await", true);
+    }
+}
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java
new file mode 100644
index 0000000..e6c8f1d
--- /dev/null
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockCall.java
@@ -0,0 +1,72 @@
+/**
+ * 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.camel.component.milo.call;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.milo.opcua.sdk.server.annotations.UaInputArgument;
+import org.eclipse.milo.opcua.sdk.server.annotations.UaMethod;
+import org.eclipse.milo.opcua.sdk.server.annotations.UaOutputArgument;
+import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.InvocationContext;
+import org.eclipse.milo.opcua.sdk.server.util.AnnotationBasedInvocationHandler.Out;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText.english;
+
+public final class MockCall {
+
+    private MockCall() {
+    }
+
+    // in: str1[String], out: out1[String]
+    public static class Call1 {
+
+        public List<String> calls = new LinkedList<>();
+
+        @UaMethod
+        public void call(final InvocationContext context, @UaInputArgument(name = "in1")
+        final String in1, @UaOutputArgument(name = "out1")
+        final Out<String> out1) {
+            this.calls.add(in1);
+            out1.set("out-" + in1);
+        }
+    }
+
+    public static UaMethodNode fromNode(final UShort index, final ServerNodeMap nodeMap, final String nodeId, final String name, final Object methodObject) {
+
+        try {
+            final UaMethodNode method = new UaMethodNode(nodeMap, new NodeId(index, nodeId), new QualifiedName(index, name), english(name), english(nodeId), UInteger.MIN,
+                                                         UInteger.MIN, true, true);
+
+            final AnnotationBasedInvocationHandler handler = AnnotationBasedInvocationHandler.fromAnnotatedObject(nodeMap, methodObject);
+            method.setInputArguments(handler.getInputArguments());
+            method.setOutputArguments(handler.getOutputArguments());
+            method.setInvocationHandler(handler);
+            return method;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java
new file mode 100644
index 0000000..1d0b137
--- /dev/null
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/MockNamespace.java
@@ -0,0 +1,177 @@
+/**
+ * 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.camel.component.milo.call;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+
+import static java.util.stream.Collectors.toList;
+
+import org.eclipse.milo.opcua.sdk.core.Reference;
+import org.eclipse.milo.opcua.sdk.server.OpcUaServer;
+import org.eclipse.milo.opcua.sdk.server.api.AccessContext;
+import org.eclipse.milo.opcua.sdk.server.api.DataItem;
+import org.eclipse.milo.opcua.sdk.server.api.MethodInvocationHandler;
+import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;
+import org.eclipse.milo.opcua.sdk.server.api.Namespace;
+import org.eclipse.milo.opcua.sdk.server.api.ServerNodeMap;
+import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.FolderNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.AttributeContext;
+import org.eclipse.milo.opcua.sdk.server.nodes.ServerNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;
+import org.eclipse.milo.opcua.sdk.server.nodes.UaMethodNode;
+import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel;
+import org.eclipse.milo.opcua.stack.core.Identifiers;
+import org.eclipse.milo.opcua.stack.core.StatusCodes;
+import org.eclipse.milo.opcua.stack.core.UaException;
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
+import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
+import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
+import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
+import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;
+
+public class MockNamespace implements Namespace {
+
+    public static final int FOLDER_ID = 1;
+
+    public static final String URI = "urn:mock:namespace";
+
+    private final UShort index;
+
+    private final ServerNodeMap nodeMap;
+    private final SubscriptionModel subscriptionModel;
+
+    public MockNamespace(final UShort index, final OpcUaServer server, List<UaMethodNode> methods) {
+        this.index = index;
+        this.nodeMap = server.getNodeMap();
+        this.subscriptionModel = new SubscriptionModel(server, this);
+
+        registerItems(methods);
+    }
+
+    private void registerItems(List<UaMethodNode> methods) {
+
+        // create a folder
+
+        final UaFolderNode folder = new UaFolderNode(this.nodeMap, new NodeId(this.index, FOLDER_ID), new QualifiedName(this.index, "FooBarFolder"),
+                                                     LocalizedText.english("Foo Bar Folder"));
+
+        // add our folder to the objects folder
+
+        this.nodeMap.getNode(Identifiers.ObjectsFolder).ifPresent(node -> {
+            ((FolderNode)node).addComponent(folder);
+        });
+
+        // add method calls
+
+        methods.forEach(folder::addComponent);
+    }
+
+    // default method implementations follow
+
+    @Override
+    public void read(final ReadContext context, final Double maxAge, final TimestampsToReturn timestamps, final List<ReadValueId> readValueIds) {
+
+        final List<DataValue> results = new ArrayList<>(readValueIds.size());
+
+        for (final ReadValueId id : readValueIds) {
+            final ServerNode node = this.nodeMap.get(id.getNodeId());
+
+            final DataValue value = node != null ? node.readAttribute(new AttributeContext(context), id.getAttributeId()) : new DataValue(StatusCodes.Bad_NodeIdUnknown);
+
+            results.add(value);
+        }
+
+        // report back with result
+
+        context.complete(results);
+    }
+
+    @Override
+    public void write(final WriteContext context, final List<WriteValue> writeValues) {
+
+        final List<StatusCode> results = writeValues.stream().map(value -> {
+            if (this.nodeMap.containsKey(value.getNodeId())) {
+                return new StatusCode(StatusCodes.Bad_NotWritable);
+            } else {
+                return new StatusCode(StatusCodes.Bad_NodeIdUnknown);
+            }
+        }).collect(toList());
+
+        // report back with result
+
+        context.complete(results);
+    }
+
+    @Override
+    public CompletableFuture<List<Reference>> browse(final AccessContext context, final NodeId nodeId) {
+        final ServerNode node = this.nodeMap.get(nodeId);
+
+        if (node != null) {
+            return CompletableFuture.completedFuture(node.getReferences());
+        } else {
+            final CompletableFuture<List<Reference>> f = new CompletableFuture<>();
+            f.completeExceptionally(new UaException(StatusCodes.Bad_NodeIdUnknown));
+            return f;
+        }
+    }
+
+    @Override
+    public Optional<MethodInvocationHandler> getInvocationHandler(final NodeId methodId) {
+        return Optional.ofNullable(this.nodeMap.get(methodId)).filter(n -> n instanceof UaMethodNode).flatMap(n -> {
+            final UaMethodNode m = (UaMethodNode)n;
+            return m.getInvocationHandler();
+        });
+    }
+
+    @Override
+    public void onDataItemsCreated(final List<DataItem> dataItems) {
+        this.subscriptionModel.onDataItemsCreated(dataItems);
+    }
+
+    @Override
+    public void onDataItemsModified(final List<DataItem> dataItems) {
+        this.subscriptionModel.onDataItemsModified(dataItems);
+    }
+
+    @Override
+    public void onDataItemsDeleted(final List<DataItem> dataItems) {
+        this.subscriptionModel.onDataItemsDeleted(dataItems);
+    }
+
+    @Override
+    public void onMonitoringModeChanged(final List<MonitoredItem> monitoredItems) {
+        this.subscriptionModel.onMonitoringModeChanged(monitoredItems);
+    }
+
+    @Override
+    public UShort getNamespaceIndex() {
+        return this.index;
+    }
+
+    @Override
+    public String getNamespaceUri() {
+        return URI;
+    }
+}
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/NodeIdTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/NodeIdTest.java
similarity index 88%
rename from components/camel-milo/src/test/java/org/apache/camel/component/milo/NodeIdTest.java
rename to components/camel-milo/src/test/java/org/apache/camel/component/milo/client/NodeIdTest.java
index f1f83b5..0995989 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/NodeIdTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/client/NodeIdTest.java
@@ -14,12 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.component.milo;
+package org.apache.camel.component.milo.client;
 
 import java.io.Serializable;
 
 import org.apache.camel.ResolveEndpointFailedException;
-import org.apache.camel.component.milo.client.MiloClientEndpoint;
+import org.apache.camel.component.milo.AbstractMiloServerTest;
+import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId;
 import org.junit.Test;
 
 import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
@@ -73,14 +74,12 @@ public class NodeIdTest extends AbstractMiloServerTest {
 
     private void assertNodeId(final MiloClientEndpoint endpoint, final Serializable namespace, final Serializable partialNodeId) {
 
-        final NamespaceId ns = endpoint.makeNamespaceId();
-        final PartialNodeId pn = endpoint.makePartialNodeId();
+        final ExpandedNodeId en = endpoint.getNodeId();
 
-        assertNotNull(ns);
-        assertNotNull(pn);
+        assertNotNull(en);
 
-        assertEquals(namespace, ns.getValue());
-        assertEquals(partialNodeId, pn.getValue());
+        assertEquals(namespace, en.getNamespaceUri() == null ? en.getNamespaceIndex() : en.getNamespaceUri());
+        assertEquals(partialNodeId, en.getIdentifier());
     }
 
 }
diff --git a/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/client/springboot/MiloClientComponentConfiguration.java b/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/client/springboot/MiloClientComponentConfiguration.java
index 2abe210..3652c40 100644
--- a/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/client/springboot/MiloClientComponentConfiguration.java
+++ b/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/client/springboot/MiloClientComponentConfiguration.java
@@ -114,6 +114,14 @@ public class MiloClientComponentConfiguration
         public static final Class CAMEL_NESTED_CLASS = org.apache.camel.component.milo.client.MiloClientConfiguration.class;
         private String endpointUri;
         /**
+         * An alternative discovery URI
+         */
+        private String discoveryEndpointUri;
+        /**
+         * A suffix for endpoint URI when discovering
+         */
+        private String discoveryEndpointSuffix;
+        /**
          * A virtual client id to force the creation of a new connection
          * instance
          */
@@ -189,6 +197,22 @@ public class MiloClientComponentConfiguration
             this.endpointUri = endpointUri;
         }
 
+        public String getDiscoveryEndpointUri() {
+            return discoveryEndpointUri;
+        }
+
+        public void setDiscoveryEndpointUri(String discoveryEndpointUri) {
+            this.discoveryEndpointUri = discoveryEndpointUri;
+        }
+
+        public String getDiscoveryEndpointSuffix() {
+            return discoveryEndpointSuffix;
+        }
+
+        public void setDiscoveryEndpointSuffix(String discoveryEndpointSuffix) {
+            this.discoveryEndpointSuffix = discoveryEndpointSuffix;
+        }
+
         public String getClientId() {
             return clientId;
         }
diff --git a/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/server/springboot/MiloServerComponentConfiguration.java b/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/server/springboot/MiloServerComponentConfiguration.java
index 8dc1278..1e57875 100644
--- a/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/server/springboot/MiloServerComponentConfiguration.java
+++ b/platforms/spring-boot/components-starter/camel-milo-starter/src/main/java/org/apache/camel/component/milo/server/springboot/MiloServerComponentConfiguration.java
@@ -91,6 +91,10 @@ public class MiloServerComponentConfiguration
      */
     private Boolean enableAnonymousAuthentication = false;
     /**
+     * Set the UserTokenPolicy used when
+     */
+    private SecurityPolicy usernameSecurityPolicyUri;
+    /**
      * Set the addresses of the local addresses the server should bind to
      */
     private String bindAddresses;
@@ -222,6 +226,15 @@ public class MiloServerComponentConfiguration
         this.enableAnonymousAuthentication = enableAnonymousAuthentication;
     }
 
+    public SecurityPolicy getUsernameSecurityPolicyUri() {
+        return usernameSecurityPolicyUri;
+    }
+
+    public void setUsernameSecurityPolicyUri(
+            SecurityPolicy usernameSecurityPolicyUri) {
+        this.usernameSecurityPolicyUri = usernameSecurityPolicyUri;
+    }
+
     public String getBindAddresses() {
         return bindAddresses;
     }

-- 
To stop receiving notification emails like this one, please contact
acosentino@apache.org.

[camel] 02/03: Use "overrideHost" to lock the host to "localhost" for unit tests

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

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 73ca0681e2e4585406911b6df2fec33204944a94
Author: Jens Reimann <jr...@redhat.com>
AuthorDate: Fri Mar 23 11:04:03 2018 +0100

    Use "overrideHost" to lock the host to "localhost" for unit tests
---
 .../milo/MonitorItemMultiConnectionsCertTest.java          |  8 +++++---
 .../component/milo/MonitorItemMultiConnectionsTest.java    |  6 +++---
 .../org/apache/camel/component/milo/MonitorItemTest.java   |  3 ++-
 .../org/apache/camel/component/milo/WriteClientTest.java   | 14 +++++++++-----
 .../apache/camel/component/milo/call/CallClientTest.java   |  2 +-
 5 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
index 93c0793..8145f0d 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsCertTest.java
@@ -45,15 +45,17 @@ public class MonitorItemMultiConnectionsCertTest extends AbstractMiloServerTest
     // with key
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
                                                         + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
-                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1&discoveryEndpointSuffix=/discovery";
+                                                        + "&keyStoreUrl=file:src/test/resources/cert/cert.p12&keyStorePassword=pwd1&keyPassword=pwd1&discoveryEndpointSuffix=/discovery&overrideHost=true";
 
     // with wrong password
     private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery";
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
+                                                        + "&discoveryEndpointSuffix=/discovery&overrideHost=true";
 
     // without key, clientId=1
     private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?clientId=1&node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&discoveryEndpointSuffix=/discovery";
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
+                                                        + "&discoveryEndpointSuffix=/discovery&overrideHost=true";
 
     private static final String MOCK_TEST_1 = "mock:test1";
     private static final String MOCK_TEST_2 = "mock:test2";
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java
index 42bcedd..98c932c 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemMultiConnectionsTest.java
@@ -35,13 +35,13 @@ public class MonitorItemMultiConnectionsTest extends AbstractMiloServerTest {
     private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1";
 
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true";
 
     private static final String MILO_CLIENT_ITEM_C2_1 = "milo-client:tcp://foo:bar2@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true";
 
     private static final String MILO_CLIENT_ITEM_C3_1 = "milo-client:tcp://foo2:bar@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&overrideHost=true";
 
     private static final String MOCK_TEST_1 = "mock:test1";
     private static final String MOCK_TEST_2 = "mock:test2";
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
index 7367cdd..43c222e 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/MonitorItemTest.java
@@ -35,7 +35,8 @@ public class MonitorItemTest extends AbstractMiloServerTest {
     private static final String MILO_SERVER_ITEM_1 = "milo-server:myitem1";
 
     private static final String MILO_CLIENT_ITEM_C1_1 = "milo-client:tcp://foo:bar@localhost:@@port@@?node="
-                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1") + "&allowedSecurityPolicies=None";
+                                                        + NodeIds.nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
+                                                        + "&allowedSecurityPolicies=None&overrideHost=true";
 
     private static final String MOCK_TEST_1 = "mock:test1";
 
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
index 4e30664..bc34d00 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/WriteClientTest.java
@@ -44,11 +44,15 @@ public class WriteClientTest extends AbstractMiloServerTest {
     private static final String MILO_CLIENT_BASE_C1 = "milo-client:tcp://foo:bar@localhost:@@port@@";
     private static final String MILO_CLIENT_BASE_C2 = "milo-client:tcp://foo2:bar2@localhost:@@port@@";
 
-    private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
-    private static final String MILO_CLIENT_ITEM_C1_2 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem2");
-
-    private static final String MILO_CLIENT_ITEM_C2_1 = MILO_CLIENT_BASE_C2 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1");
-    private static final String MILO_CLIENT_ITEM_C2_2 = MILO_CLIENT_BASE_C2 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem2");
+    private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
+                                                        + "&overrideHost=true";
+    private static final String MILO_CLIENT_ITEM_C1_2 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem2")
+                                                        + "&overrideHost=true";
+
+    private static final String MILO_CLIENT_ITEM_C2_1 = MILO_CLIENT_BASE_C2 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem1")
+                                                        + "&overrideHost=true";
+    private static final String MILO_CLIENT_ITEM_C2_2 = MILO_CLIENT_BASE_C2 + "?node=" + nodeValue(MiloServerComponent.DEFAULT_NAMESPACE_URI, "items-myitem2")
+                                                        + "&overrideHost=true";
 
     private static final String MOCK_TEST_1 = "mock:test1";
     private static final String MOCK_TEST_2 = "mock:test2";
diff --git a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
index 51574ad..7bba3a7 100644
--- a/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
+++ b/components/camel-milo/src/test/java/org/apache/camel/component/milo/call/CallClientTest.java
@@ -52,7 +52,7 @@ public class CallClientTest extends AbstractMiloServerTest {
     private static final String MILO_CLIENT_BASE_C1 = "milo-client:tcp://localhost:@@port@@";
 
     private static final String MILO_CLIENT_ITEM_C1_1 = MILO_CLIENT_BASE_C1 + "?node=" + nodeValue(MockNamespace.URI, MockNamespace.FOLDER_ID) + "&method="
-                                                        + nodeValue(MockNamespace.URI, "id1");
+                                                        + nodeValue(MockNamespace.URI, "id1") + "&overrideHost=true";
 
     @Produce(uri = DIRECT_START_1)
     protected ProducerTemplate producer1;

-- 
To stop receiving notification emails like this one, please contact
acosentino@apache.org.