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

org.apache.activemq.network.jms.JmsConnector Maven / Gradle / Ivy

There is a newer version: 6.1.2
Show newest version
/**
 * 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.activemq.network.jms;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.jms.Connection;
import javax.jms.Destination;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.Service;
import org.apache.activemq.broker.BrokerService;
import org.apache.activemq.util.LRUCache;
import org.apache.activemq.util.ThreadPoolUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This bridge joins the gap between foreign JMS providers and ActiveMQ As some
 * JMS providers are still only in compliance with JMS v1.0.1 , this bridge itself
 * aimed to be in compliance with the JMS 1.0.2 specification.
 */
public abstract class JmsConnector implements Service {

    private static int nextId;
    private static final Logger LOG = LoggerFactory.getLogger(JmsConnector.class);

    protected boolean preferJndiDestinationLookup = false;
    protected JndiLookupFactory jndiLocalTemplate;
    protected JndiLookupFactory jndiOutboundTemplate;
    protected JmsMesageConvertor inboundMessageConvertor;
    protected JmsMesageConvertor outboundMessageConvertor;
    protected AtomicBoolean initialized = new AtomicBoolean(false);
    protected AtomicBoolean localSideInitialized = new AtomicBoolean(false);
    protected AtomicBoolean foreignSideInitialized = new AtomicBoolean(false);
    protected AtomicBoolean started = new AtomicBoolean(false);
    protected AtomicBoolean failed = new AtomicBoolean();
    protected AtomicReference foreignConnection = new AtomicReference();
    protected AtomicReference localConnection = new AtomicReference();
    protected ActiveMQConnectionFactory embeddedConnectionFactory;
    protected int replyToDestinationCacheSize = 10000;
    protected String outboundUsername;
    protected String outboundPassword;
    protected String localUsername;
    protected String localPassword;
    protected String outboundClientId;
    protected String localClientId;
    protected LRUCache replyToBridges = createLRUCache();

    private ReconnectionPolicy policy = new ReconnectionPolicy();
    protected ThreadPoolExecutor connectionSerivce;
    private final List inboundBridges = new CopyOnWriteArrayList();
    private final List outboundBridges = new CopyOnWriteArrayList();
    private String name;

    private static LRUCache createLRUCache() {
        return new LRUCache() {
            private static final long serialVersionUID = -7446792754185879286L;

            @Override
            protected boolean removeEldestEntry(Map.Entry enty) {
                if (size() > maxCacheSize) {
                    Iterator> iter = entrySet().iterator();
                    Map.Entry lru = iter.next();
                    remove(lru.getKey());
                    DestinationBridge bridge = lru.getValue();
                    try {
                        bridge.stop();
                        LOG.info("Expired bridge: {}", bridge);
                    } catch (Exception e) {
                        LOG.warn("Stopping expired bridge {} caused an exception", bridge, e);
                    }
                }
                return false;
            }
        };
    }

    public boolean init() {
        boolean result = initialized.compareAndSet(false, true);
        if (result) {
            if (jndiLocalTemplate == null) {
                jndiLocalTemplate = new JndiLookupFactory();
            }
            if (jndiOutboundTemplate == null) {
                jndiOutboundTemplate = new JndiLookupFactory();
            }
            if (inboundMessageConvertor == null) {
                inboundMessageConvertor = new SimpleJmsMessageConvertor();
            }
            if (outboundMessageConvertor == null) {
                outboundMessageConvertor = new SimpleJmsMessageConvertor();
            }
            replyToBridges.setMaxCacheSize(getReplyToDestinationCacheSize());

            connectionSerivce = createExecutor();

            // Subclasses can override this to customize their own it.
            result = doConnectorInit();
        }
        return result;
    }

    protected boolean doConnectorInit() {

        // We try to make a connection via a sync call first so that the
        // JmsConnector is fully initialized before the start call returns
        // in order to avoid missing any messages that are dispatched
        // immediately after startup.  If either side fails we queue an
        // asynchronous task to manage the reconnect attempts.

        try {
            initializeLocalConnection();
            localSideInitialized.set(true);
        } catch(Exception e) {
            // Queue up the task to attempt the local connection.
            scheduleAsyncLocalConnectionReconnect();
        }

        try {
            initializeForeignConnection();
            foreignSideInitialized.set(true);
        } catch(Exception e) {
            // Queue up the task for the foreign connection now.
            scheduleAsyncForeignConnectionReconnect();
        }

        return true;
    }

    @Override
    public void start() throws Exception {
        if (started.compareAndSet(false, true)) {
            init();
            for (DestinationBridge bridge : inboundBridges) {
                bridge.start();
            }
            for (DestinationBridge bridge : outboundBridges) {
                bridge.start();
            }
            LOG.info("JMS Connector {} started", getName());
        }
    }

    @Override
    public void stop() throws Exception {
        if (started.compareAndSet(true, false)) {

            ThreadPoolUtils.shutdown(connectionSerivce);
            connectionSerivce = null;

            if (foreignConnection.get() != null) {
                try {
                    foreignConnection.get().close();
                } catch (Exception e) {
                }
            }

            if (localConnection.get() != null) {
                try {
                    localConnection.get().close();
                } catch (Exception e) {
                }
            }

            for (DestinationBridge bridge : inboundBridges) {
                bridge.stop();
            }
            for (DestinationBridge bridge : outboundBridges) {
                bridge.stop();
            }
            LOG.info("JMS Connector {} stopped", getName());
        }
    }

    public void clearBridges() {
        inboundBridges.clear();
        outboundBridges.clear();
        replyToBridges.clear();
    }

    protected abstract Destination createReplyToBridge(Destination destination, Connection consumerConnection, Connection producerConnection);

    /**
     * One way to configure the local connection - this is called by The
     * BrokerService when the Connector is embedded
     *
     * @param service
     */
    public void setBrokerService(BrokerService service) {
        embeddedConnectionFactory = new ActiveMQConnectionFactory(service.getVmConnectorURI());
    }

    public Connection getLocalConnection() {
        return this.localConnection.get();
    }

    public Connection getForeignConnection() {
        return this.foreignConnection.get();
    }

    /**
     * @return Returns the jndiTemplate.
     */
    public JndiLookupFactory getJndiLocalTemplate() {
        return jndiLocalTemplate;
    }

    /**
     * @param jndiTemplate The jndiTemplate to set.
     */
    public void setJndiLocalTemplate(JndiLookupFactory jndiTemplate) {
        this.jndiLocalTemplate = jndiTemplate;
    }

    /**
     * @return Returns the jndiOutboundTemplate.
     */
    public JndiLookupFactory getJndiOutboundTemplate() {
        return jndiOutboundTemplate;
    }

    /**
     * @param jndiOutboundTemplate The jndiOutboundTemplate to set.
     */
    public void setJndiOutboundTemplate(JndiLookupFactory jndiOutboundTemplate) {
        this.jndiOutboundTemplate = jndiOutboundTemplate;
    }

    /**
     * @return Returns the inboundMessageConvertor.
     */
    public JmsMesageConvertor getInboundMessageConvertor() {
        return inboundMessageConvertor;
    }

    /**
     * @param inboundMessageConvertor The inboundMessageConvertor to set.
     */
    public void setInboundMessageConvertor(JmsMesageConvertor jmsMessageConvertor) {
        this.inboundMessageConvertor = jmsMessageConvertor;
    }

    /**
     * @return Returns the outboundMessageConvertor.
     */
    public JmsMesageConvertor getOutboundMessageConvertor() {
        return outboundMessageConvertor;
    }

    /**
     * @param outboundMessageConvertor The outboundMessageConvertor to set.
     */
    public void setOutboundMessageConvertor(JmsMesageConvertor outboundMessageConvertor) {
        this.outboundMessageConvertor = outboundMessageConvertor;
    }

    /**
     * @return Returns the replyToDestinationCacheSize.
     */
    public int getReplyToDestinationCacheSize() {
        return replyToDestinationCacheSize;
    }

    /**
     * @param replyToDestinationCacheSize The replyToDestinationCacheSize to set.
     */
    public void setReplyToDestinationCacheSize(int replyToDestinationCacheSize) {
        this.replyToDestinationCacheSize = replyToDestinationCacheSize;
    }

    /**
     * @return Returns the localPassword.
     */
    public String getLocalPassword() {
        return localPassword;
    }

    /**
     * @param localPassword The localPassword to set.
     */
    public void setLocalPassword(String localPassword) {
        this.localPassword = localPassword;
    }

    /**
     * @return Returns the localUsername.
     */
    public String getLocalUsername() {
        return localUsername;
    }

    /**
     * @param localUsername The localUsername to set.
     */
    public void setLocalUsername(String localUsername) {
        this.localUsername = localUsername;
    }

    /**
     * @return Returns the outboundPassword.
     */
    public String getOutboundPassword() {
        return outboundPassword;
    }

    /**
     * @param outboundPassword The outboundPassword to set.
     */
    public void setOutboundPassword(String outboundPassword) {
        this.outboundPassword = outboundPassword;
    }

    /**
     * @return Returns the outboundUsername.
     */
    public String getOutboundUsername() {
        return outboundUsername;
    }

    /**
     * @param outboundUsername The outboundUsername to set.
     */
    public void setOutboundUsername(String outboundUsername) {
        this.outboundUsername = outboundUsername;
    }

    /**
     * @return the outboundClientId
     */
    public String getOutboundClientId() {
        return outboundClientId;
    }

    /**
     * @param outboundClientId the outboundClientId to set
     */
    public void setOutboundClientId(String outboundClientId) {
        this.outboundClientId = outboundClientId;
    }

    /**
     * @return the localClientId
     */
    public String getLocalClientId() {
        return localClientId;
    }

    /**
     * @param localClientId the localClientId to set
     */
    public void setLocalClientId(String localClientId) {
        this.localClientId = localClientId;
    }

    /**
     * @return the currently configured reconnection policy.
     */
    public ReconnectionPolicy getReconnectionPolicy() {
        return this.policy;
    }

    /**
     * @param policy The new reconnection policy this {@link JmsConnector} should use.
     */
    public void setReconnectionPolicy(ReconnectionPolicy policy) {
        this.policy = policy;
    }

    /**
     * @return the preferJndiDestinationLookup
     */
    public boolean isPreferJndiDestinationLookup() {
        return preferJndiDestinationLookup;
    }

    /**
     * Sets whether the connector should prefer to first try to find a destination in JNDI before
     * using JMS semantics to create a Destination.  By default the connector will first use JMS
     * semantics and then fall-back to JNDI lookup, setting this value to true will reverse that
     * ordering.
     *
     * @param preferJndiDestinationLookup the preferJndiDestinationLookup to set
     */
    public void setPreferJndiDestinationLookup(boolean preferJndiDestinationLookup) {
        this.preferJndiDestinationLookup = preferJndiDestinationLookup;
    }

    /**
     * @return returns true if the {@link JmsConnector} is connected to both brokers.
     */
    public boolean isConnected() {
        return localConnection.get() != null && foreignConnection.get() != null;
    }

    protected void addInboundBridge(DestinationBridge bridge) {
        if (!inboundBridges.contains(bridge)) {
            inboundBridges.add(bridge);
        }
    }

    protected void addOutboundBridge(DestinationBridge bridge) {
        if (!outboundBridges.contains(bridge)) {
            outboundBridges.add(bridge);
        }
    }

    protected void removeInboundBridge(DestinationBridge bridge) {
        inboundBridges.remove(bridge);
    }

    protected void removeOutboundBridge(DestinationBridge bridge) {
        outboundBridges.remove(bridge);
    }

    public String getName() {
        if (name == null) {
            name = "Connector:" + getNextId();
        }
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static synchronized int getNextId() {
        return nextId++;
    }

    public boolean isFailed() {
        return this.failed.get();
    }

    /**
     * Performs the work of connection to the local side of the Connection.
     * 

* This creates the initial connection to the local end of the {@link JmsConnector} * and then sets up all the destination bridges with the information needed to bridge * on the local side of the connection. * * @throws Exception if the connection cannot be established for any reason. */ protected abstract void initializeLocalConnection() throws Exception; /** * Performs the work of connection to the foreign side of the Connection. *

* This creates the initial connection to the foreign end of the {@link JmsConnector} * and then sets up all the destination bridges with the information needed to bridge * on the foreign side of the connection. * * @throws Exception if the connection cannot be established for any reason. */ protected abstract void initializeForeignConnection() throws Exception; /** * Callback method that the Destination bridges can use to report an exception to occurs * during normal bridging operations. * * @param connection * The connection that was in use when the failure occured. */ void handleConnectionFailure(Connection connection) { // Can happen if async exception listener kicks in at the same time. if (connection == null || !this.started.get()) { return; } LOG.info("JmsConnector handling loss of connection [{}]", connection.toString()); // TODO - How do we handle the re-wiring of replyToBridges in this case. replyToBridges.clear(); if (this.foreignConnection.compareAndSet(connection, null)) { // Stop the inbound bridges when the foreign connection is dropped since // the bridge has no consumer and needs to be restarted once a new connection // to the foreign side is made. for (DestinationBridge bridge : inboundBridges) { try { bridge.stop(); } catch(Exception e) { } } // We got here first and cleared the connection, now we queue a reconnect. this.connectionSerivce.execute(new Runnable() { @Override public void run() { try { doInitializeConnection(false); } catch (Exception e) { LOG.error("Failed to initialize foreign connection for the JMSConnector", e); } } }); } else if (this.localConnection.compareAndSet(connection, null)) { // Stop the outbound bridges when the local connection is dropped since // the bridge has no consumer and needs to be restarted once a new connection // to the local side is made. for (DestinationBridge bridge : outboundBridges) { try { bridge.stop(); } catch(Exception e) { } } // We got here first and cleared the connection, now we queue a reconnect. this.connectionSerivce.execute(new Runnable() { @Override public void run() { try { doInitializeConnection(true); } catch (Exception e) { LOG.error("Failed to initialize local connection for the JMSConnector", e); } } }); } } private void scheduleAsyncLocalConnectionReconnect() { this.connectionSerivce.execute(new Runnable() { @Override public void run() { try { doInitializeConnection(true); } catch (Exception e) { LOG.error("Failed to initialize local connection for the JMSConnector", e); } } }); } private void scheduleAsyncForeignConnectionReconnect() { this.connectionSerivce.execute(new Runnable() { @Override public void run() { try { doInitializeConnection(false); } catch (Exception e) { LOG.error("Failed to initialize foreign connection for the JMSConnector", e); } } }); } private void doInitializeConnection(boolean local) throws Exception { int attempt = 0; final int maxRetries; if (local) { maxRetries = !localSideInitialized.get() ? policy.getMaxInitialConnectAttempts() : policy.getMaxReconnectAttempts(); } else { maxRetries = !foreignSideInitialized.get() ? policy.getMaxInitialConnectAttempts() : policy.getMaxReconnectAttempts(); } do { if (attempt > 0) { try { Thread.sleep(policy.getNextDelay(attempt)); } catch(InterruptedException e) { } } if (connectionSerivce.isTerminating()) { return; } try { if (local) { initializeLocalConnection(); localSideInitialized.set(true); } else { initializeForeignConnection(); foreignSideInitialized.set(true); } // Once we are connected we ensure all the bridges are started. if (localConnection.get() != null && foreignConnection.get() != null) { for (DestinationBridge bridge : inboundBridges) { bridge.start(); } for (DestinationBridge bridge : outboundBridges) { bridge.start(); } } return; } catch(Exception e) { LOG.debug("Failed to establish initial {} connection for JmsConnector [{}]", new Object[]{ (local ? "local" : "foreign"), attempt }, e); } } while (maxRetries < ++attempt && !connectionSerivce.isTerminating()); this.failed.set(true); } private final ThreadFactory factory = new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "JmsConnector Async Connection Task: "); thread.setDaemon(true); return thread; } }; private ThreadPoolExecutor createExecutor() { ThreadPoolExecutor exec = new ThreadPoolExecutor(0, 2, 30, TimeUnit.SECONDS, new LinkedBlockingQueue(), factory); exec.allowCoreThreadTimeOut(true); return exec; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy