/**
* 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 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;
public class MiloClientComponent extends DefaultComponent {
private static final Logger LOG = LoggerFactory.getLogger(MiloClientComponent.class);
private final Map<String, MiloClientConnection> cache = new HashMap<>();
private final Multimap<String, MiloClientEndpoint> connectionMap = HashMultimap.create();
private MiloClientConfiguration defaultConfiguration = new MiloClientConfiguration();
@Override
protected Endpoint createEndpoint(final String uri, final String remaining, final Map<String, Object> parameters) throws Exception {
final MiloClientConfiguration configuration = new MiloClientConfiguration(this.defaultConfiguration);
configuration.setEndpointUri(remaining);
setProperties(configuration, parameters);
return createEndpoint(uri, configuration, parameters);
}
private synchronized MiloClientEndpoint createEndpoint(final String uri, final MiloClientConfiguration configuration, final Map<String, Object> parameters) throws Exception {
MiloClientConnection connection = this.cache.get(configuration.toCacheId());
if (connection == null) {
LOG.info("Cache miss - creating new connection instance: {}", configuration.toCacheId());
connection = new MiloClientConnection(configuration, mapToClientConfiguration(configuration));
this.cache.put(configuration.toCacheId(), connection);
}
final MiloClientEndpoint endpoint = new MiloClientEndpoint(uri, this, connection, configuration.getEndpointUri());
setProperties(endpoint, parameters);
// register connection with endpoint
this.connectionMap.put(configuration.toCacheId(), 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.getSecureChannelReauthenticationEnabled() != null) {
builder.setSecureChannelReauthenticationEnabled(configuration.getSecureChannelReauthenticationEnabled());
}
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
*/
public void setDefaultConfiguration(final MiloClientConfiguration defaultConfiguration) {
this.defaultConfiguration = defaultConfiguration;
}
/**
* Default application name
*/
public void setApplicationName(final String applicationName) {
this.defaultConfiguration.setApplicationName(applicationName);
}
/**
* Default application URI
*/
public void setApplicationUri(final String applicationUri) {
this.defaultConfiguration.setApplicationUri(applicationUri);
}
/**
* Default product URI
*/
public void setProductUri(final String productUri) {
this.defaultConfiguration.setProductUri(productUri);
}
/**
* Default reconnect timeout
*/
public void setReconnectTimeout(final Long reconnectTimeout) {
this.defaultConfiguration.setRequestTimeout(reconnectTimeout);
}
public synchronized void disposed(final MiloClientEndpoint endpoint) {
final MiloClientConnection connection = endpoint.getConnection();
// unregister usage of connection
this.connectionMap.remove(connection.getConnectionId(), endpoint);
// test if this was the last endpoint using this connection
if (!this.connectionMap.containsKey(connection.getConnectionId())) {
// this was the last endpoint using the connection ...
// ... remove from the cache
this.cache.remove(connection.getConnectionId());
// ... and close
try {
connection.close();
} catch (final Exception e) {
LOG.warn("Failed to close connection", e);
}
}
}
}