/**
* 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.server.internal;
import java.util.HashMap;
import java.util.List;
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;
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.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.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.UaObjectNode;
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.Variant;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.eclipse.milo.opcua.stack.core.types.structured.WriteValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CamelNamespace implements Namespace {
private static final Logger LOG = LoggerFactory.getLogger(MiloClientConsumer.class);
private final UShort namespaceIndex;
private final String namespaceUri;
private final ServerNodeMap nodeManager;
private final SubscriptionModel subscriptionModel;
private final UaFolderNode folder;
private final UaObjectNode itemsObject;
private final Map<String, CamelServerItem> itemMap = new HashMap<>();
public CamelNamespace(final UShort namespaceIndex, final String namespaceUri, final OpcUaServer server) {
this.namespaceIndex = namespaceIndex;
this.namespaceUri = namespaceUri;
this.nodeManager = server.getNodeMap();
this.subscriptionModel = new SubscriptionModel(server, this);
// create structure
{
final NodeId nodeId = new NodeId(namespaceIndex, "camel");
final QualifiedName name = new QualifiedName(namespaceIndex, "camel");
final LocalizedText displayName = LocalizedText.english("Camel");
this.folder = new UaFolderNode(this.nodeManager, nodeId, name, displayName);
this.nodeManager.addNode(this.folder);
}
{
final NodeId nodeId = new NodeId(namespaceIndex, "items");
final QualifiedName name = new QualifiedName(namespaceIndex, "items");
final LocalizedText displayName = LocalizedText.english("Items");
this.itemsObject = new UaObjectNode(this.nodeManager, nodeId, name, displayName);
this.folder.addComponent(this.itemsObject);
}
// register reference to structure
try {
server.getUaNamespace().addReference(Identifiers.ObjectsFolder, Identifiers.Organizes, true, this.folder.getNodeId().expanded(), NodeClass.Object);
} catch (final UaException e) {
throw new RuntimeException("Failed to register folder", e);
}
}
@Override
public UShort getNamespaceIndex() {
return this.namespaceIndex;
}
@Override
public String getNamespaceUri() {
return this.namespaceUri;
}
@Override
public CompletableFuture<List<Reference>> browse(final AccessContext context, final NodeId nodeId) {
final ServerNode node = this.nodeManager.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 void read(final ReadContext context, final Double maxAge, final TimestampsToReturn timestamps, final List<ReadValueId> readValueIds) {
final List<DataValue> results = Lists.newArrayListWithCapacity(readValueIds.size());
for (final ReadValueId id : readValueIds) {
final ServerNode node = this.nodeManager.get(id.getNodeId());
final DataValue value;
if (node != null) {
value = node.readAttribute(new AttributeContext(context), id.getAttributeId(), timestamps, id.getIndexRange());
} else {
value = new DataValue(StatusCodes.Bad_NodeIdUnknown);
}
results.add(value);
}
context.complete(results);
}
@Override
public void write(final WriteContext context, final List<WriteValue> writeValues) {
final List<StatusCode> results = Lists.newArrayListWithCapacity(writeValues.size());
for (final WriteValue writeValue : writeValues) {
try {
final ServerNode node = this.nodeManager.getNode(writeValue.getNodeId()).orElseThrow(() -> new UaException(StatusCodes.Bad_NodeIdUnknown));
node.writeAttribute(new AttributeContext(context), writeValue.getAttributeId(), writeValue.getValue(), writeValue.getIndexRange());
if (LOG.isTraceEnabled()) {
final Variant variant = writeValue.getValue().getValue();
final Object o = variant != null ? variant.getValue() : null;
LOG.trace("Wrote value={} to attributeId={} of {}", o, writeValue.getAttributeId(), writeValue.getNodeId());
}
results.add(StatusCode.GOOD);
} catch (final UaException e) {
results.add(e.getStatusCode());
}
}
context.complete(results);
}
@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);
}
public CamelServerItem getOrAddItem(final String itemId) {
synchronized (this) {
CamelServerItem item = this.itemMap.get(itemId);
if (item == null) {
item = new CamelServerItem(itemId, this.nodeManager, this.namespaceIndex, this.itemsObject);
this.itemMap.put(itemId, item);
}
return item;
}
}
}