All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 35.0.0.Beta1
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.ejb.client;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.RealmCallback;
import javax.xml.bind.DatatypeConverter;

import org.jboss.ejb.client.remoting.RemotingConnectionUtil;
import org.jboss.logging.Logger;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Options;

/**
 * A {@link EJBClientConfiguration} which is configured through {@link Properties}. Some well known
 * properties will be looked for in the {@link Properties} that is passed to the {@link #PropertiesBasedEJBClientConfiguration(java.util.Properties) constructor},
 * for setting up the configurations
 *
 * @author Jaikiran Pai
 */
public class PropertiesBasedEJBClientConfiguration implements EJBClientConfiguration {

    private static final Logger logger = Logger.getLogger(PropertiesBasedEJBClientConfiguration.class);

    private static final String PROPERTY_KEY_ENDPOINT_NAME = "endpoint.name";
    private static final String DEFAULT_ENDPOINT_NAME = "config-based-ejb-client-endpoint";

    private static final String PROPERTY_KEY_INVOCATION_TIMEOUT = "invocation.timeout";
    private static final String PROPERTY_KEY_RECONNECT_TASKS_TIMEOUT = "reconnect.tasks.timeout";
    private static final String PROPERTY_KEY_DEPLOYMENT_NODE_SELECTOR = "deployment.node.selector";

    private static final String ENDPOINT_CREATION_OPTIONS_PREFIX = "endpoint.create.options.";
    // The default options that will be used (unless overridden by the config file) for endpoint creation
    private static final OptionMap DEFAULT_ENDPOINT_CREATION_OPTIONS = OptionMap.create(Options.THREAD_DAEMON, true);

    // The default options that will be used (unless overridden by the config file) while adding a remote connection
    // provider to the endpoint
    private static final OptionMap DEFAULT_CONNECTION_PROVIDER_CREATION_OPTIONS = OptionMap.EMPTY;
    private static final String REMOTE_CONNECTION_PROVIDER_CREATE_OPTIONS_PREFIX = "remote.connectionprovider.create.options.";

    private static final String PROPERTY_KEY_REMOTE_CONNECTIONS = "remote.connections";
    private static final String PROPERTY_KEY_REMOTE_CONNECTIONS_CONNECT_EAGER = "remote.connections.connect.eager";

    // The default options that will be used (unless overridden by the config file) while creating a connection
    private static final OptionMap DEFAULT_CONNECTION_CREATION_OPTIONS = OptionMap.EMPTY;
    private static final long DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS = 5000;

    private static final String PROPERTY_KEY_USERNAME = "username";
    private static final String PROPERTY_KEY_PASSWORD = "password";
    private static final String PROPERTY_KEY_PASSWORD_BASE64 = "password.base64";
    private static final String PROPERTY_KEY_REALM = "realm";
    private static final String PROPERTY_KEY_CALLBACK_HANDLER_CLASS = "callback.handler.class";

    private static final String PROPERTY_KEY_CLUSTERS = "remote.clusters";
    private static final String DEFAULT_PROTOCOL = "http-remoting";


    private final Properties ejbReceiversConfigurationProperties;

    private String endPointName;
    private OptionMap endPointCreationOptions;
    private OptionMap remoteConnectionProviderCreationOptions;
    private CallbackHandler callbackHandler;
    private Collection remotingConnectionConfigurations = new ArrayList();
    private Map clusterConfigurations = new HashMap();
    private long invocationTimeout = 0;
    private long reconnectTasksTimeout = 0;
    private DeploymentNodeSelector deploymentNodeSelector = new RandomDeploymentNodeSelector();

    private static final boolean expandPasswords = Boolean.valueOf(
        System.getProperty("jboss-ejb-client.expandPasswords", "false")).booleanValue();

    public PropertiesBasedEJBClientConfiguration(final Properties properties) {
        final Properties resolvedProperties = new Properties();
        if (properties != null) {
            for (Map.Entry entry : properties.entrySet()) {
                Object value = entry.getValue();
                if (value instanceof String) {
                   boolean propertyIsAPassword = ((String)entry.getKey()).indexOf(PROPERTY_KEY_PASSWORD) >= 0 ? true : false;
                   // if its not a password...expand it
                   // if it is a password and we're supposed to expand it...then do so
                   if( !propertyIsAPassword || ( propertyIsAPassword && expandPasswords ) ) {
                    value = PropertiesValueResolver.replaceProperties((String) value);
                   }
                }
                resolvedProperties.put(entry.getKey(), value);
            }
        }

        this.ejbReceiversConfigurationProperties = resolvedProperties;
        // parse the properties and setup this configuration
        this.parseProperties();
    }

    @Override
    public String getEndpointName() {
        return this.endPointName;
    }

    @Override
    public OptionMap getEndpointCreationOptions() {
        return this.endPointCreationOptions;
    }

    @Override
    public OptionMap getRemoteConnectionProviderCreationOptions() {
        return this.remoteConnectionProviderCreationOptions;
    }

    @Override
    public CallbackHandler getCallbackHandler() {
        return this.callbackHandler;
    }

    @Override
    public Iterator getConnectionConfigurations() {
        return this.remotingConnectionConfigurations.iterator();
    }

    @Override
    public ClusterConfiguration getClusterConfiguration(String clusterName) {
        return this.clusterConfigurations.get(clusterName);
    }

    @Override
    public long getInvocationTimeout() {
        return this.invocationTimeout;
    }

    @Override
    public long getReconnectTasksTimeout() {
        return this.reconnectTasksTimeout;
    }

    @Override
    public DeploymentNodeSelector getDeploymentNodeSelector() {
        return this.deploymentNodeSelector;
    }


    private void parseProperties() {
        // endpoint name
        this.endPointName = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_ENDPOINT_NAME, DEFAULT_ENDPOINT_NAME);

        // callback handler
        this.callbackHandler = this.getDefaultCallbackHandler();

        // endpoint creation options
        final OptionMap endPointCreationOptionsFromConfiguration = getOptionMapFromProperties(ejbReceiversConfigurationProperties, ENDPOINT_CREATION_OPTIONS_PREFIX, getClientClassLoader());
        // merge with defaults
        this.endPointCreationOptions = mergeWithDefaults(DEFAULT_ENDPOINT_CREATION_OPTIONS, endPointCreationOptionsFromConfiguration);

        // invocation timeout
        final String invocationTimeoutValue = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_INVOCATION_TIMEOUT);
        // if a invocation timeout is specified, use it
        if (invocationTimeoutValue != null && !invocationTimeoutValue.trim().isEmpty()) {
            try {
                invocationTimeout = Long.parseLong(invocationTimeoutValue.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectInvocationTimeoutValue(invocationTimeoutValue, String.valueOf(this.invocationTimeout));
            }
        }

        // reconnect tasks timeout
        final String reconnectTasksTimeoutValue = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_RECONNECT_TASKS_TIMEOUT);
        if (reconnectTasksTimeoutValue != null && !reconnectTasksTimeoutValue.trim().isEmpty()) {
            try {
                this.reconnectTasksTimeout = Long.parseLong(reconnectTasksTimeoutValue.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectReconnectTasksTimeoutValue(reconnectTasksTimeoutValue, String.valueOf(this.reconnectTasksTimeout));
            }
        }

        // deployment node selector
        final String deploymentNodeSelectorClassName = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_DEPLOYMENT_NODE_SELECTOR);
        if (deploymentNodeSelectorClassName != null) {
            final ClassLoader classLoader = getClientClassLoader();
            try {
                final Class deploymentNodeSelectorClass = Class.forName(deploymentNodeSelectorClassName.trim(), true, classLoader);
                if (!DeploymentNodeSelector.class.isAssignableFrom(deploymentNodeSelectorClass)) {
                    throw Logs.MAIN.unexpectedDeploymentNodeSelectorClassType(deploymentNodeSelectorClass);
                }
                this.deploymentNodeSelector = (DeploymentNodeSelector) deploymentNodeSelectorClass.newInstance();
            } catch (Exception e) {
                throw Logs.MAIN.couldNotCreateDeploymentNodeSelector(e);
            }
        }

        // remote connection provider creation options
        final OptionMap remoteConnectionProviderOptionsFromConfiguration = getOptionMapFromProperties(ejbReceiversConfigurationProperties, REMOTE_CONNECTION_PROVIDER_CREATE_OPTIONS_PREFIX, getClientClassLoader());
        // merge with defaults
        this.remoteConnectionProviderCreationOptions = mergeWithDefaults(DEFAULT_CONNECTION_PROVIDER_CREATION_OPTIONS, remoteConnectionProviderOptionsFromConfiguration);

        // connection configurations
        this.parseConnectionConfigurations();

        // cluster configurations
        this.parseClusterConfigurations();

    }

    private OptionMap getOptionMapFromProperties(final Properties properties, final String propertyPrefix, final ClassLoader classLoader) {
        final OptionMap.Builder optionMapBuilder = OptionMap.builder().parseAll(properties, propertyPrefix, classLoader);
        final OptionMap optionMap = optionMapBuilder.getMap();
        logger.debugf("%s has the following options %s", propertyPrefix, optionMap);
        return optionMap;
    }

    /**
     * Merges the passed defaults and the overrides to return a combined
     * {@link OptionMap}. If the passed overrides has a {@link org.xnio.Option} for
     * which matches the one in defaults then the default option value is ignored and instead the
     * overridden one is added to the combined {@link OptionMap}. If however, the overrides doesn't
     * contain a option which is present in the defaults, then the default option is added to the
     * combined {@link OptionMap}
     *
     * @param defaults  The default options
     * @param overrides The overridden options
     * @return
     */
    private OptionMap mergeWithDefaults(final OptionMap defaults, final OptionMap overrides) {
        // copy all the overrides
        final OptionMap.Builder combinedOptionsBuilder = OptionMap.builder().addAll(overrides);
        // Skip all the defaults which have been overridden and just add the rest of the defaults
        // to the combined options
        for (final Option defaultOption : defaults) {
            if (combinedOptionsBuilder.getMap().contains(defaultOption)) {
                continue;
            }
            final Object defaultValue = defaults.get(defaultOption);
            combinedOptionsBuilder.set(defaultOption, defaultValue);
        }
        final OptionMap combinedOptions = combinedOptionsBuilder.getMap();
        if (logger.isTraceEnabled()) {
            logger.trace("Options " + overrides + " have been merged with defaults " + defaults + " to form " + combinedOptions);
        }
        return combinedOptions;
    }

    /**
     * If {@link Thread#getContextClassLoader()} is null then returns the classloader which loaded
     * {@link PropertiesBasedEJBClientConfiguration} class. Else returns the {@link Thread#getContextClassLoader()}
     *
     * @return
     */
    private static ClassLoader getClientClassLoader() {
        final ClassLoader tccl = SecurityActions.getContextClassLoader();
        if (tccl != null) {
            return tccl;
        }
        return PropertiesBasedEJBClientConfiguration.class.getClassLoader();
    }

    private void parseClusterConfigurations() {
        final String clusterNames = (String) ejbReceiversConfigurationProperties.get(PROPERTY_KEY_CLUSTERS);
        // no clusters configured, nothing to do!
        if (clusterNames == null || clusterNames.trim().isEmpty()) {
            logger.debug("No clusters configured in properties");
            return;
        }
        // parse the comma separated string of cluster names
        final StringTokenizer tokenizer = new StringTokenizer(clusterNames, ",");
        while (tokenizer.hasMoreTokens()) {
            final String clusterName = tokenizer.nextToken().trim();
            if (clusterName.isEmpty()) {
                continue;
            }
            ClusterConfiguration clusterConfiguration = null;
            try {
                clusterConfiguration = this.createClusterConfiguration(clusterName);
            } catch (Exception e) {
                logger.warn("Could not create cluster configuration for cluster named " + clusterName, e);
            }
            if (clusterConfiguration == null) {
                continue;
            }
            logger.debugf("Cluster configuration for cluster %s successfully created", clusterName);
            // add it to the cluster configuration map
            this.clusterConfigurations.put(clusterName, clusterConfiguration);
        }
    }

    private ClusterConfiguration createClusterConfiguration(final String clusterName) {
        final String clusterSpecificPrefix = this.getClusterSpecificPrefix(clusterName);
        final Map clusterSpecificProperties = this.getPropertiesWithPrefix(clusterSpecificPrefix);
        if (clusterSpecificProperties.isEmpty()) {
            return null;
        }
        // get "max-connected-nodes" for the cluster
        final String maxConnectedNodesStringVal = clusterSpecificProperties.get("max-allowed-connected-nodes");
        long maxAllowedConnectedNodes = 10; // default to 10
        if (maxConnectedNodesStringVal != null && !maxConnectedNodesStringVal.trim().isEmpty()) {
            try {
                maxAllowedConnectedNodes = Long.parseLong(maxConnectedNodesStringVal.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectMaxAllowedConnectedNodesValueForCluster(maxConnectedNodesStringVal, clusterName, String.valueOf(maxAllowedConnectedNodes));
            }
        }
        // get the connection creation options applicable for all the nodes (unless explicitly overridden) in this
        // cluster
        final String connectOptionsPrefix = this.getClusterSpecificConnectOptionsPrefix(clusterName);
        final OptionMap connectOptionsFromConfiguration = getOptionMapFromProperties(ejbReceiversConfigurationProperties, connectOptionsPrefix, getClientClassLoader());
        // merge with defaults
        OptionMap connectOptions = mergeWithDefaults(DEFAULT_CONNECTION_CREATION_OPTIONS, connectOptionsFromConfiguration);

        // get the connection timeout applicable for all nodes (unless explicitly overridden) in this cluster
        long connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS;
        final String connectionTimeoutValue = clusterSpecificProperties.get("connect.timeout");
        // if a connection timeout is specified, use it
        if (connectionTimeoutValue != null && !connectionTimeoutValue.trim().isEmpty()) {
            try {
                connectionTimeout = Long.parseLong(connectionTimeoutValue.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectConnectionTimeoutValueForCluster(connectionTimeoutValue, clusterName, String.valueOf(DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS));
            }
        }
        // cluster node selector for this cluster
        final String clusterNodeSelectorClassName = clusterSpecificProperties.get("clusternode.selector");
        final ClusterNodeSelector clusterNodeSelector;
        if (clusterNodeSelectorClassName != null) {
            final ClassLoader classLoader = getClientClassLoader();
            try {
                final Class clusterNodeSelectorClass = Class.forName(clusterNodeSelectorClassName.trim(), true, classLoader);
                if (!ClusterNodeSelector.class.isAssignableFrom(clusterNodeSelectorClass)) {
                    throw Logs.MAIN.unexpectedClusterNodeSelectorClassType(clusterNodeSelectorClass, clusterName);
                }
                clusterNodeSelector = (ClusterNodeSelector) clusterNodeSelectorClass.newInstance();
            } catch (Exception e) {
                throw Logs.MAIN.couldNotCreateClusterNodeSelector(e, clusterName);
            }
        } else {
            clusterNodeSelector = null;
        }
        // create the CallbackHandler applicable for all nodes (unless explicitly overridden) in this cluster
        final CallbackHandler callbackHandler = createCallbackHandler(clusterSpecificProperties, this.getDefaultCallbackHandler());
        connectOptions = RemotingConnectionUtil.addSilentLocalAuthOptionsIfApplicable(callbackHandler, connectOptions);

        // Channel creation options for this cluster
        final String channelOptionsPrefix = this.getClusterSpecificChannelOptionsPrefix(clusterName);
        final OptionMap channelOptions = getOptionMapFromProperties(ejbReceiversConfigurationProperties, channelOptionsPrefix, getClientClassLoader());
        final ClusterConfigurationImpl clusterConfiguration = new ClusterConfigurationImpl(clusterName, maxAllowedConnectedNodes,
                connectOptions, callbackHandler, connectionTimeout, clusterNodeSelector, channelOptions);
        // parse the node configurations for this cluster
        final Collection nodeConfigurations = this.parseClusterNodeConfigurations(clusterConfiguration, clusterSpecificProperties);
        // add them to the cluster configuration
        clusterConfiguration.addNodeConfigurations(nodeConfigurations);
        // return the cluster configuration
        return clusterConfiguration;
    }

    private Collection parseClusterNodeConfigurations(final ClusterConfiguration clusterConfiguration, final Map clusterSpecificProperties) {
        final Collection nodeConfigurations = new ArrayList();
        final Collection parsedNodes = new HashSet();
        for (final String key : clusterSpecificProperties.keySet()) {
            if (!key.startsWith("node.")) {
                continue;
            }
            final String keyWithoutNodeDotPrefix = key.substring("node.".length());
            final int nextDotIndex = keyWithoutNodeDotPrefix.indexOf(".");
            if (nextDotIndex == -1) {
                continue;
            }
            final String nodeName = keyWithoutNodeDotPrefix.substring(0, nextDotIndex);
            // already parsed, so skip
            if (parsedNodes.contains(nodeName)) {
                continue;
            }
            // create a node configuration for the node name
            final ClusterNodeConfiguration nodeConfiguration = this.createClusterNodeConfiguration(clusterConfiguration, nodeName);
            if (nodeConfiguration == null) {
                continue;
            }
            // add it to the collection to be returned
            nodeConfigurations.add(nodeConfiguration);
            // mark the node as parsed
            parsedNodes.add(nodeConfiguration.getNodeName());
        }
        return nodeConfigurations;
    }

    private ClusterNodeConfiguration createClusterNodeConfiguration(final ClusterConfiguration clusterConfiguration, final String nodeName) {
        final String clusterName = clusterConfiguration.getClusterName();
        final String nodeSpecificPrefix = this.getClusterSpecificPrefix(clusterName) + "node." + nodeName + ".";
        // get the cluster node specific properties
        final Map nodeSpecificProperties = this.getPropertiesWithPrefix(nodeSpecificPrefix);
        if (nodeSpecificProperties.isEmpty()) {
            return null;
        }
        // get the connection creation options for the cluster node
        final String connectOptionsPrefix = this.getClusterNodeSpecificConnectOptionsPrefix(clusterName, nodeName);
        final OptionMap connectOptionsFromConfiguration = getOptionMapFromProperties(ejbReceiversConfigurationProperties, connectOptionsPrefix, getClientClassLoader());
        // merge with defaults (== connection creation options applicable to the entire cluster)
        final OptionMap connectOptions = mergeWithDefaults(clusterConfiguration.getConnectionCreationOptions(), connectOptionsFromConfiguration);

        // get the connection timeout applicable for the cluster node
        long connectionTimeout = clusterConfiguration.getConnectionTimeout(); // default to the timeout applicable to the entire cluster
        final String connectionTimeoutValue = nodeSpecificProperties.get("connect.timeout");
        // if a connection timeout is specified, use it
        if (connectionTimeoutValue != null && !connectionTimeoutValue.trim().isEmpty()) {
            try {
                connectionTimeout = Long.parseLong(connectionTimeoutValue.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectConnectionTimeoutValueForNodeInCluster(connectionTimeoutValue, nodeName, clusterName, String.valueOf(connectionTimeout));
            }
        }

        // create the CallbackHandler applicable for the cluster node (default to the callback handler applicable to the entire cluster)
        final CallbackHandler callbackHandler = createCallbackHandler(nodeSpecificProperties, clusterConfiguration.getCallbackHandler());

        // Channel creation options for this cluster node
        final String channelOptionsPrefix = this.getClusterNodeSpecificChannelOptionsPrefix(clusterName, nodeName);
        final OptionMap channelOptions = getOptionMapFromProperties(ejbReceiversConfigurationProperties, channelOptionsPrefix, getClientClassLoader());
        return new ClusterNodeConfigurationImpl(nodeName, connectOptions, callbackHandler, connectionTimeout, channelOptions);
    }

    private void parseConnectionConfigurations() {
        final String remoteConnectionNames = (String) ejbReceiversConfigurationProperties.get(PROPERTY_KEY_REMOTE_CONNECTIONS);
        // no connections configured, nothing to do!
        if (remoteConnectionNames == null || remoteConnectionNames.trim().isEmpty()) {
            logger.debug("No remoting connections configured in properties");
            return;
        }
        // make note of whether the connection attempts are to be eager or lazy for all listed connections (unless overridden at the specific connection configuration)
        final Object connectEagerValue = ejbReceiversConfigurationProperties.get(PROPERTY_KEY_REMOTE_CONNECTIONS_CONNECT_EAGER);
        final boolean connectEager;
        if (connectEagerValue == null) {
            // by default we connect eagerly
            connectEager = true;
        } else {
            if (connectEagerValue instanceof String) {
                connectEager = Boolean.valueOf(((String) connectEagerValue).trim());
            } else {
                // default to true
                connectEager = true;
            }
        }
        // parse the comma separated string of connection names
        final StringTokenizer tokenizer = new StringTokenizer(remoteConnectionNames, ",");
        while (tokenizer.hasMoreTokens()) {
            final String connectionName = tokenizer.nextToken().trim();
            if (connectionName.isEmpty()) {
                continue;
            }
            RemotingConnectionConfiguration connectionConfiguration = null;
            try {
                connectionConfiguration = this.createConnectionConfiguration(connectionName, connectEager);
            } catch (Exception e) {
                logger.warn("Could not create connection for connection named " + connectionName, e);
            }
            if (connectionConfiguration == null) {
                continue;
            }
            logger.debugf("Connection %s successfully created for connection named %s", connectionConfiguration, connectionName);
            // add it to the collection of connection configurations
            this.remotingConnectionConfigurations.add(connectionConfiguration);
        }
    }

    private RemotingConnectionConfiguration createConnectionConfiguration(final String connectionName, final boolean defaultConnectEager) throws IOException, URISyntaxException {
        final String connectionSpecificPrefix = this.getConnectionSpecificPrefix(connectionName);
        final Map connectionSpecificProps = this.getPropertiesWithPrefix(connectionSpecificPrefix);
        if (connectionSpecificProps.isEmpty()) {
            return null;
        }
        // get "host" for the connection
        final String host = connectionSpecificProps.get("host");
        if (host == null || host.trim().isEmpty()) {
            Logs.MAIN.skippingConnectionCreationDueToMissingHostOrPort(connectionName);
            return null;
        }
        // get "port" for the connection
        final String portStringVal = connectionSpecificProps.get("port");
        if (portStringVal == null || portStringVal.trim().isEmpty()) {
            Logs.MAIN.skippingConnectionCreationDueToMissingHostOrPort(connectionName);
            return null;
        }
        final Integer port;
        try {
            port = Integer.parseInt(portStringVal.trim());
        } catch (NumberFormatException nfe) {
            Logs.MAIN.skippingConnectionCreationDueToInvalidPortNumber(portStringVal, connectionName);
            return null;
        }

        String protocol = connectionSpecificProps.get("protocol");
        if(protocol == null) {
            protocol = DEFAULT_PROTOCOL;
        }

        // get connect options for the connection
        final String connectOptionsPrefix = this.getConnectionSpecificConnectOptionsPrefix(connectionName);
        final OptionMap connectOptionsFromConfiguration = getOptionMapFromProperties(ejbReceiversConfigurationProperties, connectOptionsPrefix, getClientClassLoader());
        // merge with defaults
        OptionMap connectOptions = mergeWithDefaults(DEFAULT_CONNECTION_CREATION_OPTIONS, connectOptionsFromConfiguration);
        long connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS;
        final String connectionTimeoutValue = connectionSpecificProps.get("connect.timeout");
        // if a connection timeout is specified, use it
        if (connectionTimeoutValue != null && !connectionTimeoutValue.trim().isEmpty()) {
            try {
                connectionTimeout = Long.parseLong(connectionTimeoutValue.trim());
            } catch (NumberFormatException nfe) {
                Logs.MAIN.incorrectConnectionTimeoutValueForConnection(connectionTimeoutValue, connectionName, String.valueOf(DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS));
            }
        }
        // connect eagerly or lazily
        final String connectEagerValue = connectionSpecificProps.get("connect.eager");
        final boolean connectEagerly;
        if (connectEagerValue == null || connectEagerValue.trim().isEmpty()) {
            // default to the value that may have been set for all connections
            connectEagerly = defaultConnectEager;
        } else {
            connectEagerly = Boolean.valueOf(connectEagerValue.trim());
        }
        // create the CallbackHandler for this connection configuration
        final CallbackHandler callbackHandler = createCallbackHandler(connectionSpecificProps, this.getDefaultCallbackHandler());
        connectOptions = RemotingConnectionUtil.addSilentLocalAuthOptionsIfApplicable(callbackHandler, connectOptions);

        // Channel creation options for this connection
        final String channelOptionsPrefix = this.getConnectionSpecificChannelOptionsPrefix(connectionName);
        final OptionMap channelOptions = getOptionMapFromProperties(ejbReceiversConfigurationProperties, channelOptionsPrefix, getClientClassLoader());
        return new RemotingConnectionConfigurationImpl(protocol, host, port, connectOptions, connectionTimeout, callbackHandler, channelOptions, connectEagerly);

    }

    private String getConnectionSpecificPrefix(final String connectionName) {
        return "remote.connection." + connectionName + ".";
    }

    private String getConnectionSpecificConnectOptionsPrefix(final String connectionName) {
        return "remote.connection." + connectionName + ".connect.options.";
    }

    private String getConnectionSpecificChannelOptionsPrefix(final String connectionName) {
        return "remote.connection." + connectionName + ".channel.options.";
    }

    private Map getPropertiesWithPrefix(final String prefix) {
        final Map propertiesWithPrefix = new HashMap();
        for (final String fullPropName : this.ejbReceiversConfigurationProperties.stringPropertyNames()) {
            if (fullPropName.startsWith(prefix)) {
                // strip the "prefix" from the full property name and just get the trailing part.
                // Example, If remote.cluster.foo.bar is the full property name,
                // then this step will return "bar" as the property name for the prefix "remote.cluster.foo.".
                String propName = fullPropName.substring(prefix.length());
                // get the value of the (full) property name
                final String propValue = this.ejbReceiversConfigurationProperties.getProperty(fullPropName);
                propertiesWithPrefix.put(propName, propValue);
            }
        }
        return propertiesWithPrefix;
    }

    private String getClusterSpecificPrefix(final String clusterName) {
        return "remote.cluster." + clusterName + ".";
    }

    private String getClusterSpecificConnectOptionsPrefix(final String clusterName) {
        return "remote.cluster." + clusterName + ".connect.options.";
    }

    private String getClusterSpecificChannelOptionsPrefix(final String clusterName) {
        return "remote.cluster." + clusterName + ".channel.options.";
    }

    private String getClusterNodeSpecificConnectOptionsPrefix(final String clusterName, final String nodeName) {
        return "remote.cluster." + clusterName + ".node." + nodeName + ".connect.options.";
    }

    private String getClusterNodeSpecificChannelOptionsPrefix(final String clusterName, final String nodeName) {
        return "remote.cluster." + clusterName + ".node." + nodeName + ".channel.options.";
    }


    /**
     * Creates a callback handler
     *
     * @param properties
     * @return The CallbackHandler
     */
    private CallbackHandler createCallbackHandler(final Map properties, final CallbackHandler defaultCallbackHandler) {
        String callbackClass = properties.get(PROPERTY_KEY_CALLBACK_HANDLER_CLASS);
        String userName = properties.get(PROPERTY_KEY_USERNAME);
        String password = properties.get(PROPERTY_KEY_PASSWORD);
        String passwordBase64 = properties.get(PROPERTY_KEY_PASSWORD_BASE64);
        String realm = properties.get(PROPERTY_KEY_REALM);

        CallbackHandler handler = resolveCallbackHandler(callbackClass, userName, password, passwordBase64, realm);
        if (handler != null) {
            return handler;
        }

        return defaultCallbackHandler;
    }

    private CallbackHandler resolveCallbackHandler(final String callbackClass, final String userName, final String password, final String passwordBase64, final String realm) {

        if (callbackClass != null && (userName != null || password != null)) {
            throw Logs.MAIN.cannotSpecifyBothCallbackHandlerAndUserPass();
        }
        if (callbackClass != null) {
            final ClassLoader classLoader = getClientClassLoader();
            try {
                final Class clazz = Class.forName(callbackClass, true, classLoader);
                return (CallbackHandler) clazz.newInstance();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (userName != null) {
            if (password != null && passwordBase64 != null) {
                throw Logs.MAIN.cannotSpecifyBothPlainTextAndEncodedPassword();
            }

            final String decodedPassword;
            if (passwordBase64 != null) {
                try {
                    decodedPassword = DatatypeConverter.printBase64Binary(passwordBase64.getBytes());
                } catch (Exception e) {
                    throw Logs.MAIN.couldNotDecodeBase64Password(e);
                }
            } else if (password != null) {
                decodedPassword = password;
            } else {
                decodedPassword = null;
            }
            return new AuthenticationCallbackHandler(userName, decodedPassword == null ? null : decodedPassword.toCharArray(), realm);
        }
        return null;
    }

    private CallbackHandler getDefaultCallbackHandler() {
        final String callbackClass = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_CALLBACK_HANDLER_CLASS);
        final String userName = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_USERNAME);
        final String password = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_PASSWORD);
        final String passwordBase64 = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_PASSWORD_BASE64);
        final String realm = this.ejbReceiversConfigurationProperties.getProperty(PROPERTY_KEY_REALM);

        CallbackHandler handler = resolveCallbackHandler(callbackClass, userName, password, passwordBase64, realm);
        if (handler != null) {
            return handler;
        }
        // no auth specified, just use the default
        return new DefaultCallbackHandler();
    }

    private class AuthenticationCallbackHandler implements CallbackHandler {

        private final String realm;
        private final String username;
        private final char[] password;

        private AuthenticationCallbackHandler(final String username, final char[] password, final String realm) {
            this.username = username;
            this.password = password;
            this.realm = realm;
        }

        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

            for (Callback current : callbacks) {
                if (current instanceof RealmCallback) {
                    RealmCallback rcb = (RealmCallback) current;
                    if (realm == null) {
                        String defaultText = rcb.getDefaultText();
                        rcb.setText(defaultText); // For now just use the realm suggested.
                    } else {
                        rcb.setText(realm);
                    }
                } else if (current instanceof NameCallback) {
                    NameCallback ncb = (NameCallback) current;
                    ncb.setName(username);
                } else if (current instanceof PasswordCallback) {
                    PasswordCallback pcb = (PasswordCallback) current;
                    pcb.setPassword(password);
                } else {
                    throw new UnsupportedCallbackException(current);
                }
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            AuthenticationCallbackHandler that = (AuthenticationCallbackHandler) o;

            if (!Arrays.equals(password, that.password)) return false;
            if (realm != null ? !realm.equals(that.realm) : that.realm != null) return false;
            if (username != null ? !username.equals(that.username) : that.username != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = realm != null ? realm.hashCode() : 0;
            result = 31 * result + (username != null ? username.hashCode() : 0);
            result = 31 * result + (password != null ? Arrays.hashCode(password) : 0);
            return result;
        }
    }

    private class RemotingConnectionConfigurationImpl implements RemotingConnectionConfiguration {
        final String protocol;
        final String host;
        final int port;
        final OptionMap connectionCreationOptions;
        final long connectionTimeout;
        final CallbackHandler callbackHandler;
        final OptionMap channelCreationOptions;
        final boolean connectEagerly;

        RemotingConnectionConfigurationImpl(final String protocol, final String host, final int port, final OptionMap connectionCreationOptions,
                                            final long connectionTimeout, final CallbackHandler callbackHandler, final OptionMap channelCreationOptions,
                                            final boolean connectEagerly) {
            this.protocol = protocol;
            this.host = host;
            this.port = port;
            this.connectionCreationOptions = connectionCreationOptions;
            this.connectionTimeout = connectionTimeout;
            this.callbackHandler = callbackHandler;
            this.channelCreationOptions = channelCreationOptions == null ? OptionMap.EMPTY : channelCreationOptions;
            this.connectEagerly = connectEagerly;
        }

        @Override
        public String getHost() {
            return this.host;
        }

        @Override
        public int getPort() {
            return this.port;
        }

        @Override
        public long getConnectionTimeout() {
            return this.connectionTimeout;
        }

        @Override
        public OptionMap getConnectionCreationOptions() {
            return this.connectionCreationOptions;
        }

        @Override
        public CallbackHandler getCallbackHandler() {
            return this.callbackHandler;
        }

        @Override
        public OptionMap getChannelCreationOptions() {
            return this.channelCreationOptions;
        }

        @Override
        public boolean isConnectEagerly() {
            return connectEagerly;
        }

        @Override
        public String getProtocol() {
            return protocol;
        }
    }

    private class ClusterConfigurationImpl implements ClusterConfiguration {

        private final String clusterName;
        private final long maxAllowedConnectedNodes;
        private final Map nodeConfigurations = new HashMap();
        private final CallbackHandler callbackHandler;
        private final OptionMap connectionCreationOptions;
        private final long connectionTimeout;
        private final ClusterNodeSelector clusterNodeSelector;
        private final OptionMap channelCreationOptions;

        ClusterConfigurationImpl(final String clusterName, final long maxAllowedConnectedNodes, final OptionMap connectionCreationOptions,
                                 final CallbackHandler callbackHandler, final long connectionTimeout, final ClusterNodeSelector clusterNodeSelector,
                                 final OptionMap channelCreationOptions) {
            this.clusterName = clusterName;
            this.maxAllowedConnectedNodes = maxAllowedConnectedNodes;
            this.connectionCreationOptions = connectionCreationOptions;
            this.callbackHandler = callbackHandler;
            this.connectionTimeout = connectionTimeout;
            this.clusterNodeSelector = clusterNodeSelector;
            this.channelCreationOptions = channelCreationOptions == null ? OptionMap.EMPTY : channelCreationOptions;
        }

        @Override
        public String getClusterName() {
            return this.clusterName;
        }

        @Override
        public long getMaximumAllowedConnectedNodes() {
            return this.maxAllowedConnectedNodes;
        }

        @Override
        public ClusterNodeConfiguration getNodeConfiguration(String nodeName) {
            return this.nodeConfigurations.get(nodeName);
        }

        @Override
        public OptionMap getConnectionCreationOptions() {
            return this.connectionCreationOptions;
        }

        @Override
        public CallbackHandler getCallbackHandler() {
            return this.callbackHandler;
        }

        @Override
        public long getConnectionTimeout() {
            return this.connectionTimeout;
        }

        @Override
        public ClusterNodeSelector getClusterNodeSelector() {
            return this.clusterNodeSelector;
        }

        void addNodeConfigurations(final Collection nodeConfigurations) {
            if (nodeConfigurations != null) {
                for (final ClusterNodeConfiguration nodeConfiguration : nodeConfigurations) {
                    this.nodeConfigurations.put(nodeConfiguration.getNodeName(), nodeConfiguration);
                }
            }

        }

        @Override
        public OptionMap getChannelCreationOptions() {
            return this.channelCreationOptions;
        }

        @Override
        public boolean isConnectEagerly() {
            // connecting to cluster nodes is always on-demand and not eager. So return false.
            return false;
        }
    }

    private class ClusterNodeConfigurationImpl implements ClusterNodeConfiguration {

        private final String nodeName;
        private final OptionMap connectionCreationOptions;
        private final CallbackHandler callbackHandler;
        private final long connectionTimeout;
        private final OptionMap channelCreationOptions;

        ClusterNodeConfigurationImpl(final String nodeName, final OptionMap connectionCreationOptions, final CallbackHandler callbackHandler,
                                     final long connectionTimeout, OptionMap channelCreationOptions) {
            this.nodeName = nodeName;
            this.connectionCreationOptions = connectionCreationOptions;
            this.callbackHandler = callbackHandler;
            this.connectionTimeout = connectionTimeout;
            this.channelCreationOptions = channelCreationOptions == null ? OptionMap.EMPTY : channelCreationOptions;
        }

        @Override
        public String getNodeName() {
            return this.nodeName;
        }

        @Override
        public OptionMap getConnectionCreationOptions() {
            return this.connectionCreationOptions;
        }

        @Override
        public CallbackHandler getCallbackHandler() {
            return this.callbackHandler;
        }

        @Override
        public long getConnectionTimeout() {
            return this.connectionTimeout;
        }

        @Override
        public OptionMap getChannelCreationOptions() {
            return this.channelCreationOptions;
        }

        @Override
        public boolean isConnectEagerly() {
            // connecting to cluster node is always on-demand and not eager. So return false.
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy