org.fusesource.fabric.bridge.internal.SourceConnector Maven / Gradle / Ivy
/**
* Copyright (C) FuseSource, Inc.
* http://fusesource.com
*
* Licensed 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.fusesource.fabric.bridge.internal;
import org.apache.activemq.pool.PooledConnectionFactory;
import org.fusesource.fabric.bridge.model.BridgeDestinationsConfig;
import org.fusesource.fabric.bridge.model.BridgedDestination;
import org.fusesource.fabric.bridge.model.BrokerConfig;
import org.fusesource.fabric.bridge.model.DispatchPolicy;
import org.springframework.beans.BeanUtils;
import org.springframework.jms.JmsException;
import org.springframework.jms.UncategorizedJmsException;
import org.springframework.jms.listener.AbstractMessageListenerContainer;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.jms.support.destination.DestinationResolver;
import javax.jms.*;
import java.beans.PropertyDescriptor;
import java.lang.IllegalStateException;
import java.util.*;
import java.util.Map.Entry;
/**
* Connects destinations on local broker to a staging queue on remote broker.
*
* @author Dhiraj Bokde
*
*/
public class SourceConnector extends AbstractConnector {
private BrokerConfig localBrokerConfig;
private BrokerConfig remoteBrokerConfig;
private BridgeDestinationsConfig outboundDestinations;
private boolean reuseSession;
private boolean reuseMessage;
private Destination stagingQueue;
private ConnectionFactory localConnectionFactory;
private ConnectionFactory remoteConnectionFactory;
private Map listenerMap = new HashMap();
@Override
public void afterPropertiesSet() throws Exception {
if (outboundDestinations == null || localBrokerConfig == null) {
throw new IllegalArgumentException("Both outboundDestinations and localBrokerConfig properties must be set");
}
if (remoteBrokerConfig == null && !outboundDestinations.isUseStagingQueue()) {
throw new IllegalArgumentException("Property remoteBrokerConfig is missing but property useStagingQueue is false");
}
if (remoteBrokerConfig == null && outboundDestinations.isDefaultStagingLocation()) {
throw new IllegalArgumentException("Property remoteBrokerConfig is missing but property defaultStagingLocation is true");
}
if (remoteBrokerConfig != null && !outboundDestinations.isDefaultStagingLocation()) {
throw new IllegalArgumentException("Property remoteBrokerConfig is set but property defaultStagingLocation is false");
}
if ((localBrokerConfig.getBrokerUrl() == null && localBrokerConfig.getConnectionFactory() == null) ||
(localBrokerConfig.getBrokerUrl() != null && localBrokerConfig.getConnectionFactory() != null)) {
throw new IllegalArgumentException("Either a local broker url or connection factory must be provided");
}
if (remoteBrokerConfig != null) {
if ((remoteBrokerConfig.getBrokerUrl() == null && remoteBrokerConfig.getConnectionFactory() == null) ||
(remoteBrokerConfig.getBrokerUrl() != null && remoteBrokerConfig.getConnectionFactory() != null)) {
throw new IllegalArgumentException("Either a remote broker url or connection factory must be provided");
}
}
}
protected void doInitialize() {
// get local and remote connection factories
localConnectionFactory = getConnectionFactory(localBrokerConfig);
LOG.debug("Using local connection factory " + localConnectionFactory);
if (remoteBrokerConfig != null && outboundDestinations.isDefaultStagingLocation()) {
remoteConnectionFactory = getConnectionFactory(remoteBrokerConfig);
LOG.debug("Using remote connection factory " + remoteConnectionFactory);
reuseSession = false;
} else {
// Note that this is sub-optimal, as consumer connections from
// remote broker to local broker can't be 'occasionally connected'
// like producer connections from local to remote broker that are
// only used when messages are being sent.
// Set the defaultStagingLocation to false if it hasn't been set
// already since the staging queue will be created locally
LOG.warn("Using local broker for staging queue");
outboundDestinations.setDefaultStagingLocation(false);
remoteConnectionFactory = localConnectionFactory;
reuseSession = true;
}
reuseMessage = false;
Connection localConnection = null;
Connection remoteConnection = null;
try {
// check if messages need to be copied for different JMS providers
localConnection = localConnectionFactory.createConnection();
Session localSession = localConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Message localMessage = localSession.createMessage();
remoteConnection = remoteConnectionFactory.createConnection();
Session remoteSession = remoteConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Message remoteMessage = remoteSession.createMessage();
if (localMessage.getClass().isInstance(remoteMessage)) {
reuseMessage = true;
}
// also get the stagingQueue from remoteSession
stagingQueue = getStagingQueue(remoteSession);
} catch (JMSException e) {
String msg = "Error checking whether local and remote broker providers are the same: " + e.getMessage();
LOG.error(msg, e);
throw new IllegalStateException(msg, e);
} finally {
if (localConnection != null) {
try {
localConnection.close();
} catch (JMSException e) {}
}
if (remoteConnection != null) {
try {
remoteConnection.close();
} catch (JMSException e) {}
}
}
for (BridgedDestination destination : outboundDestinations.getDestinations()) {
try {
listenerMap.put(destination, createListenerContainer(destination));
} catch (Exception e) {
final String msg = "Error creating listener for destination: " + destination;
LOG.error(msg, e);
throw new IllegalStateException(msg, e);
}
}
LOG.info("Initialized");
}
private Destination getStagingQueue(Session remoteSession) throws JMSException {
// check if a staging queue is required
if (!outboundDestinations.isUseStagingQueue()) {
return null;
}
DestinationResolver destinationResolver = null;
if (remoteBrokerConfig != null) {
destinationResolver = remoteBrokerConfig.getDestinationResolver();
} else {
destinationResolver = localBrokerConfig.getDestinationResolver();
}
return destinationResolver.resolveDestinationName(remoteSession, outboundDestinations.getStagingQueueName(), false);
}
protected void doStart() {
JmsException failedException = null;
// start all the listener containers
for (Entry entry : listenerMap.entrySet()) {
final BridgedDestination destination = entry.getKey();
final AbstractMessageListenerContainer listener = entry.getValue();
if (!listener.isRunning()) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting listener for " + destination);
}
listener.start();
if (LOG.isDebugEnabled()) {
LOG.debug("Listener started for " + destination);
}
} catch (JmsException e) {
failedException = e;
LOG.error("Error stopping connector for listener " + listener + ": " + e.getMessage(), e);
break;
}
}
}
if (failedException != null) {
// stop all listeners
stop();
throw failedException;
}
LOG.info("Started");
}
protected void doStop() {
// stop all listener containers
for (Entry entry : listenerMap.entrySet()) {
final BridgedDestination destination = entry.getKey();
final AbstractMessageListenerContainer listener = entry.getValue();
if (listener.isRunning()) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Stopping listener for " + destination);
}
listener.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Listener stopped for " + destination);
}
} catch (JmsException e) {
LOG.error("Error stopping connector for listener " + listener + ": " + e.getMessage(), e);
}
}
}
LOG.info("Stopped");
}
protected void doDestroy() {
// destroy all listener containers
for (Entry entry : listenerMap.entrySet()) {
final BridgedDestination destination = entry.getKey();
final AbstractMessageListenerContainer listener = entry.getValue();
if (listener.isActive()) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Destroying listener for " + destination);
}
listener.destroy();
if (LOG.isDebugEnabled()) {
LOG.debug("Listener destroyed for " + destination);
}
} catch (JmsException e) {
LOG.error("Error destroying listener for destination "
+ destination.getName() + " : " + e.getMessage(), e);
}
}
}
listenerMap.clear();
// check if we created pooled connection factories
if (localBrokerConfig.getConnectionFactory() == null && localConnectionFactory != null) {
((PooledConnectionFactory)localConnectionFactory).stop();
}
if (remoteBrokerConfig != null && remoteBrokerConfig.getConnectionFactory() == null
&& remoteConnectionFactory != null) {
((PooledConnectionFactory)remoteConnectionFactory).stop();
}
LOG.info("Destroyed");
}
protected AbstractMessageListenerContainer createListenerContainer(BridgedDestination destination) {
// create listener container using localConnectionFactory
// and DeliveryHandler using remoteConnectionFactory
DispatchPolicy resolvedPolicy = getResolvedPolicy(destination.getDispatchPolicy(), outboundDestinations.getDispatchPolicy());
if (LOG.isDebugEnabled()) {
LOG.debug("Resolved policy for destination " + destination + " is : " + resolvedPolicy);
}
// both batch size and timeout are required to enable batch listener
// set them both to <= zero to use the Spring default listener
DefaultMessageListenerContainer listenerContainer;
AbstractDeliveryHandler deliveryHandler = createDeliveryHandler(
resolvedPolicy,
(destination.getTargetName() != null) ? destination.getTargetName() : destination.getName(),
destination.isPubSubDomain());
if (resolvedPolicy.getBatchSize() <= 0 || resolvedPolicy.getBatchTimeout() <= 0) {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating default message listener container for " + destination.getName());
}
listenerContainer = new DefaultMessageListenerContainer();
configureListenerContainer(listenerContainer, resolvedPolicy, true, localBrokerConfig.getDestinationResolver());
listenerContainer.setMessageListener(deliveryHandler);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Creating batch message listener container for " + destination.getName());
}
listenerContainer = new BatchMessageListenerContainer();
configureListenerContainer((BatchMessageListenerContainer)listenerContainer, resolvedPolicy, true, localBrokerConfig.getDestinationResolver());
((BatchMessageListenerContainer) listenerContainer).setBatchMessageListener(deliveryHandler);
}
listenerContainer.setConnectionFactory(localConnectionFactory);
listenerContainer.setDestinationName(destination.getName());
listenerContainer.setPubSubDomain(destination.isPubSubDomain());
listenerContainer.setClientId(localBrokerConfig.getClientId());
listenerContainer.setDurableSubscriptionName(destination.getDurableSubscriptionName());
listenerContainer.setSubscriptionDurable(destination.isSubscriptionDurable());
// initialize the listener
listenerContainer.afterPropertiesSet();
return listenerContainer;
}
protected DispatchPolicy getResolvedPolicy(DispatchPolicy dispatchPolicy,
DispatchPolicy defaultDispatchPolicy) {
if (dispatchPolicy == null) {
return defaultDispatchPolicy;
}
DispatchPolicy resolvedPolicy = new DispatchPolicy();
BeanUtils.copyProperties(defaultDispatchPolicy, resolvedPolicy);
Set propertiesSet = dispatchPolicy.getPropertiesSet();
PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(DispatchPolicy.class);
Set ignoreProperties = new HashSet();
for (PropertyDescriptor property : propertyDescriptors) {
if (!propertiesSet.contains(property.getName())) {
ignoreProperties.add(property.getName());
}
}
BeanUtils.copyProperties(dispatchPolicy, resolvedPolicy,
ignoreProperties.toArray(new String[ignoreProperties.size()]));
return resolvedPolicy;
}
protected AbstractDeliveryHandler createDeliveryHandler(DispatchPolicy resolvedPolicy, String destinationName, boolean pubSubDomain) {
// create delivery handler for staging queue using remoteConnectionFactory
// delivery is always done in the same thread as the receiving destination
SourceDeliveryHandler deliveryHandler = new SourceDeliveryHandler();
deliveryHandler.setDispatchPolicy(resolvedPolicy);
deliveryHandler.setDestinationNameHeader(outboundDestinations.getDestinationNameHeader());
deliveryHandler.setDestinationTypeHeader(outboundDestinations.getDestinationTypeHeader());
deliveryHandler.setDestinationName(destinationName);
deliveryHandler.setPubSubDomain(pubSubDomain);
deliveryHandler.setReuseSession(reuseSession);
deliveryHandler.setReuseMessage(reuseMessage);
deliveryHandler.setTargetConnectionFactory(remoteConnectionFactory);
// check if a staging queue is used or not
if (stagingQueue != null) {
deliveryHandler.setStagingDestination(stagingQueue);
} else {
// creating a connection for every remote destination resolve seems inefficient,
// but we cannot make that stateful, also pooling makes it a moot point
Connection remoteConnection = null;
try {
remoteConnection = remoteConnectionFactory.createConnection();
Session remoteSession = remoteConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
deliveryHandler.setStagingDestination(
remoteBrokerConfig.getDestinationResolver().resolveDestinationName(
remoteSession, destinationName, pubSubDomain));
} catch (JMSException e) {
String msg = "Error resolving remote destination " + destinationName + " : " + e.getMessage();
LOG.error(msg, e);
throw new IllegalStateException(msg, e);
} finally {
try {
remoteConnection.close();
} catch (JMSException e) {}
}
}
return deliveryHandler;
}
@Override
public BridgeDestinationsConfig getDestinationsConfig() {
synchronized (lifecycleMonitor) {
return outboundDestinations;
}
}
@Override
public void setDestinationsConfig(BridgeDestinationsConfig destinationsConfig) {
synchronized (lifecycleMonitor) {
if (destinationsConfig == null || destinationsConfig.getDestinations() == null) {
throw new UncategorizedJmsException("Invalid destinations config");
}
// remember current state
boolean wasRunning = isRunning();
// destroy and recreate the connector
try {
destroy();
} catch (Exception e) {
LOG.error("Error destorying connector: " + e.getMessage(), e);
// ignore and keep going??
}
this.outboundDestinations = destinationsConfig;
try {
afterPropertiesSet();
} catch (Exception e) {
throw new UncategorizedJmsException(e.getMessage(), e);
}
if (wasRunning) {
start();
}
}
}
@Override
public void addDestinations(List destinations) {
synchronized (lifecycleMonitor) {
final Set outboundDestinationNames = getOutboundDestinationNames();
// remove any duplicate destinations
final List newDestinations = new ArrayList();
for (BridgedDestination destination : destinations) {
if (!outboundDestinationNames.contains(destination.getName())) {
newDestinations.add(destination);
} else {
LOG.warn("Ignoring destination " + destination.getName() + " as it already exists as an outbound destination");
}
}
// process new destinations depending upon current state
if (!isInitialized()) {
// just add destinations to current config
outboundDestinations.getDestinations().addAll(newDestinations);
} else {
// create listeners for new destinations
Map newListenerMap = new HashMap();
for (BridgedDestination destination : newDestinations) {
try {
newListenerMap.put(destination, createListenerContainer(destination));
} catch (Exception e) {
final String msg = "Error creating listener for new destination: " + destination;
LOG.error(msg, e);
// destroy any listeners created so far
for (Entry entry : newListenerMap.entrySet()) {
try {
entry.getValue().destroy();
} catch (Exception ex) {
LOG.warn("Error destorying listener for " + entry.getKey(), ex);
}
}
newListenerMap.clear();
newDestinations.clear();
throw new IllegalStateException(msg, e);
}
}
// add destinations to current config
outboundDestinations.getDestinations().addAll(newDestinations);
// add listeners to current config
listenerMap.putAll(newListenerMap);
// do we need to start the new listeners??
if (isRunning()) {
start();
}
}
}
}
@Override
public void removeDestinations(List destinations) {
synchronized (lifecycleMonitor) {
Set outboundDestinationNames = getOutboundDestinationNames();
// remove any missing destinations
final List currentDestinations = new ArrayList();
for (BridgedDestination destination : destinations) {
if (outboundDestinationNames.contains(destination.getName())) {
currentDestinations.add(destination);
} else {
LOG.warn("Ignoring destination " + destination + " as it does not exist as an outbound destination");
}
}
// process current destinations depending upon current state
if (!isInitialized()) {
// just remove destinations from current config
outboundDestinations.getDestinations().removeAll(currentDestinations);
} else {
// destroy listeners for current destinations
for (BridgedDestination destination : currentDestinations) {
try {
// remove listener from current config
AbstractMessageListenerContainer listener = listenerMap.remove(destination);
// this will also stop the listener before destroying it
listener.destroy();
} catch (Exception e) {
final String msg = "Error destroying listener for destination: " + destination;
LOG.warn(msg, e);
// ignore and keep processing the rest??
}
}
// remove destinations from current config
outboundDestinations.getDestinations().removeAll(currentDestinations);
}
}
}
private Set getOutboundDestinationNames() {
final Set outboundDestinationNames = new HashSet();
for (BridgedDestination dest : outboundDestinations.getDestinations()) {
outboundDestinationNames.add(dest.getName());
}
return outboundDestinationNames;
}
public BrokerConfig getLocalBrokerConfig() {
return localBrokerConfig;
}
public void setLocalBrokerConfig(BrokerConfig localBrokerConfig) {
this.localBrokerConfig = localBrokerConfig;
}
public BrokerConfig getRemoteBrokerConfig() {
return remoteBrokerConfig;
}
public void setRemoteBrokerConfig(BrokerConfig remoteBrokerConfig) {
this.remoteBrokerConfig = remoteBrokerConfig;
}
public BridgeDestinationsConfig getOutboundDestinations() {
return outboundDestinations;
}
public void setOutboundDestinations(BridgeDestinationsConfig outboundDestinations) {
this.outboundDestinations = outboundDestinations;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy