/**
*
* Copyright 2005 LogicBlaze, Inc. http://www.logicblaze.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.servicemix.jbi.nmr.flow.jca;
import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
import org.activemq.advisories.ConsumerAdvisor;
import org.activemq.advisories.ConsumerAdvisoryEvent;
import org.activemq.advisories.ConsumerAdvisoryEventListener;
import org.activemq.message.ActiveMQTopic;
import org.activemq.message.ConsumerInfo;
import org.activemq.ra.ActiveMQActivationSpec;
import org.activemq.ra.ActiveMQManagedConnectionFactory;
import org.activemq.ra.ActiveMQResourceAdapter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.connector.BootstrapContextImpl;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.SinglePool;
import org.apache.geronimo.connector.outbound.connectionmanagerconfig.XATransactions;
import org.apache.geronimo.connector.work.GeronimoWorkManager;
import org.apache.geronimo.transaction.context.TransactionContextManager;
import org.jencks.JCAConnector;
import org.jencks.SingletonEndpointFactory;
import org.jencks.factory.ConnectionManagerFactoryBean;
import org.servicemix.jbi.framework.ComponentConnector;
import org.servicemix.jbi.framework.ComponentNameSpace;
import org.servicemix.jbi.framework.ComponentPacket;
import org.servicemix.jbi.framework.ComponentPacketEvent;
import org.servicemix.jbi.framework.ComponentPacketEventListener;
import org.servicemix.jbi.framework.LocalComponentConnector;
import org.servicemix.jbi.messaging.MessageExchangeImpl;
import org.servicemix.jbi.nmr.Broker;
import org.servicemix.jbi.nmr.flow.AbstractFlow;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import javax.jbi.JBIException;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.MessageExchange.Role;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.resource.spi.BootstrapContext;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.ResourceAdapterInternalException;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Use for message routing among a network of containers. All routing/registration happens automatically.
*
* @version $Revision: 749 $
*/
public class JCAFlow extends AbstractFlow implements ConsumerAdvisoryEventListener, MessageListener, ComponentPacketEventListener {
private static final Log log = LogFactory.getLog(JCAFlow.class);
private static final String INBOUND_PREFIX = "org.servicemix.inbound.";
private String jmsURL = "tcp://localhost:61616";
private String userName;
private String password;
private ConnectionFactory connectionFactory;
private Connection connection;
private String broadcastDestinationName = "org.servicemix.JMSFlow";
private Topic broadcastTopic;
private ConsumerAdvisor advisor;
private Map networkNodeKeyMap = new ConcurrentHashMap();
private Map networkComponentKeyMap = new ConcurrentHashMap();
private Map connectorMap = new ConcurrentHashMap();
private AtomicBoolean started = new AtomicBoolean(false);
private TransactionContextManager transactionContextManager;
private ConnectionManager connectionManager;
private JmsTemplate jmsTemplate;
private JmsTemplate jmsPersistentTemplate;
private BootstrapContext bootstrapContext;
private ResourceAdapter resourceAdapter;
private JCAConnector containerConnector;
private JCAConnector broadcastConnector;
/**
* The type of Flow
*
* @return the type
*/
public String getDescription() {
return "jms";
}
/**
* @return Returns the jmsURL.
*/
public String getJmsURL() {
return jmsURL;
}
/**
* @param jmsURL The jmsURL to set.
*/
public void setJmsURL(String jmsURL) {
this.jmsURL = jmsURL;
}
/**
* @return Returns the password.
*/
public String getPassword() {
return password;
}
/**
* @param password The password to set.
*/
public void setPassword(String password) {
this.password = password;
}
/**
* @return Returns the userName.
*/
public String getUserName() {
return userName;
}
/**
* @param userName The userName to set.
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
* @return Returns the connectionFactory.
*/
public ConnectionFactory getConnectionFactory() {
return connectionFactory;
}
/**
* @param connectionFactory The connectionFactory to set.
*/
public void setConnectionFactory(ConnectionFactory connectoFactory) {
this.connectionFactory = connectoFactory;
}
/**
* @return Returns the broadcastDestinationName.
*/
public String getBroadcastDestinationName() {
return broadcastDestinationName;
}
/**
* @param broadcastDestinationName The broadcastDestinationName to set.
*/
public void setBroadcastDestinationName(String broadcastDestinationName) {
this.broadcastDestinationName = broadcastDestinationName;
}
protected ResourceAdapter createResourceAdapter() throws ResourceAdapterInternalException {
ActiveMQResourceAdapter ra = new ActiveMQResourceAdapter();
ra.setServerUrl(jmsURL);
ra.start(getBootstrapContext());
return ra;
}
public TransactionManager getTransactionManager() {
return (TransactionManager) broker.getContainer().getTransactionManager();
}
/**
* Initialize the Region
*
* @param broker
* @throws JBIException
*/
public void init(Broker broker, String subType) throws JBIException {
super.init(broker, subType);
broker.getRegistry().addComponentPacketListener(this);
try {
resourceAdapter = createResourceAdapter();
// Inbound connector
ActiveMQActivationSpec ac = new ActiveMQActivationSpec();
ac.setDestinationType("javax.jms.Queue");
ac.setDestination(INBOUND_PREFIX + broker.getContainerName());
containerConnector = new JCAConnector();
containerConnector.setBootstrapContext(getBootstrapContext());
containerConnector.setActivationSpec(ac);
containerConnector.setResourceAdapter(resourceAdapter);
containerConnector.setEndpointFactory(new SingletonEndpointFactory(this, getTransactionManager()));
containerConnector.afterPropertiesSet();
// Outbound connector
ActiveMQManagedConnectionFactory mcf = new ActiveMQManagedConnectionFactory();
mcf.setResourceAdapter(resourceAdapter);
ConnectionFactory cf = (ConnectionFactory) mcf.createConnectionFactory(getConnectionManager());
jmsTemplate = new JmsTemplate(cf);
jmsTemplate.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
jmsPersistentTemplate = new JmsTemplate(cf);
jmsPersistentTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
// Inbound broadcast
ac = new ActiveMQActivationSpec();
ac.setDestinationType("javax.jms.Topic");
ac.setDestination(broadcastDestinationName);
broadcastConnector = new JCAConnector();
broadcastConnector.setBootstrapContext(getBootstrapContext());
broadcastConnector.setActivationSpec(ac);
broadcastConnector.setResourceAdapter(resourceAdapter);
broadcastConnector.setEndpointFactory(new SingletonEndpointFactory(this));
broadcastConnector.afterPropertiesSet();
// Outbound broadcast
connection = ((ActiveMQResourceAdapter) resourceAdapter).makeConnection();
connection.start();
broadcastTopic = new ActiveMQTopic(broadcastDestinationName);
advisor = new ConsumerAdvisor(connection, broadcastTopic);
advisor.addListener(this);
}
catch (Exception e) {
log.error("Failed t0 initialize JCAFlow", e);
throw new JBIException(e);
}
}
/**
* start the flow
*
* @throws JBIException
*/
public void start() throws JBIException {
if (started.compareAndSet(false, true)) {
super.start();
try {
advisor.start();
}
catch (JMSException e) {
throw new JBIException("JMSException caught in start: " + e.getMessage(), e);
}
}
}
/**
* stop the flow
*
* @throws JBIException
*/
public void stop() throws JBIException {
if (started.compareAndSet(true, false)) {
super.stop();
try {
advisor.stop();
}
catch (JMSException e) {
JBIException jbiEx = new JBIException("JMSException caught in stop: " + e.getMessage());
throw jbiEx;
}
}
}
public void shutDown() throws JBIException {
super.shutDown();
stop();
// Destroy connectors
while (!connectorMap.isEmpty()) {
JCAConnector connector = (JCAConnector) connectorMap.remove(connectorMap.keySet().iterator().next());
try {
connector.destroy();
} catch (Exception e) {
log.warn("error closing jca connector", e);
}
}
try {
containerConnector.destroy();
} catch (Exception e) {
log.warn("error closing jca connector", e);
}
try {
broadcastConnector.destroy();
} catch (Exception e) {
log.warn("error closing jca connector", e);
}
// Destroy the resource adapter
resourceAdapter.stop();
if (this.connection != null) {
try {
this.connection.close();
}
catch (JMSException e) {
log.warn("error closing JMS Connection", e);
}
}
}
/**
* useful for testing
*
* @return number of containers in the network
*/
public int numberInNetwork() {
return advisor.activeConsumers(broadcastTopic).size();
}
/**
* Ability for this flow to persist exchanges.
*
* @return <code>true</code> if this flow can persist messages
*/
protected boolean canPersist() {
return true;
}
/**
* Process state changes in Components
*
* @param event
*/
public void onEvent(final ComponentPacketEvent event) {
try {
String componentName = event.getPacket().getComponentNameSpace().getName();
if (event.getStatus() == ComponentPacketEvent.ACTIVATED){
ActiveMQActivationSpec ac = new ActiveMQActivationSpec();
ac.setDestinationType("javax.jms.Queue");
ac.setDestination(INBOUND_PREFIX + componentName);
JCAConnector connector = new JCAConnector();
connector.setBootstrapContext(getBootstrapContext());
connector.setActivationSpec(ac);
connector.setResourceAdapter(resourceAdapter);
connector.setEndpointFactory(new SingletonEndpointFactory(this, getTransactionManager()));
connector.afterPropertiesSet();
connectorMap.put(componentName, connector);
} else if (event.getStatus() == ComponentPacketEvent.DEACTIVATED){
JCAConnector connector = (JCAConnector) connectorMap.remove(componentName);
if (connector != null){
connector.destroy();
}
}
// broadcast change to the network
log.info("broadcast to internal JMS network: " + event);
jmsTemplate.send(broadcastTopic, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(event);
}
});
}
catch (Exception e) {
log.error("failed to broadcast to the internal JMS network: " + event, e);
}
}
/**
* ConsumerAdvisoerEventListener implementation
*
* @param event
*/
public void onEvent(ConsumerAdvisoryEvent event) {
if (started.get()) {
ConsumerInfo info = event.getInfo();
if (info.isStarted()) {
for (Iterator i = broker.getRegistry().getLocalComponentConnectors().iterator();i.hasNext();) {
LocalComponentConnector lcc = (LocalComponentConnector) i.next();
ComponentPacket packet = lcc.getPacket();
ComponentPacketEvent cpe = new ComponentPacketEvent(packet, ComponentPacketEvent.ACTIVATED);
onEvent(cpe);
}
}
else {
removeAllPackets(info.getClientId());
}
}
}
/**
* Distribute an ExchangePacket
*
* @param packet
* @throws MessagingException
*/
protected void doSend(MessageExchangeImpl me) throws MessagingException {
doRouting(me);
}
/**
* Distribute an ExchangePacket
*
* @param packet
* @throws MessagingException
*/
public void doRouting(final MessageExchangeImpl me) throws MessagingException {
ComponentNameSpace id = me.getRole() == Role.PROVIDER ? me.getDestinationId() : me.getSourceId();
ComponentConnector cc = broker.getRegistry().getComponentConnector(id);
if (cc != null) {
if (me.getMirror().getSyncState() != MessageExchangeImpl.SYNC_STATE_ASYNC) {
throw new IllegalStateException("sendSync can not be used on jca flow with external components");
}
try {
final String componentName = cc.getComponentNameSpace().getName();
JmsTemplate jt = isPersistent(me) ? jmsPersistentTemplate : jmsTemplate;
String destination = "";
if (me.getRole() == Role.PROVIDER){
destination = INBOUND_PREFIX + componentName;
}else {
destination = INBOUND_PREFIX + id.getContainerName();
}
jt.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(me);
}
});
} catch (Exception e) {
log.error("Failed to send exchange: " + me + " internal JMS Network", e);
throw new MessagingException(e);
}
} else {
throw new MessagingException("No component with id (" + id + ") - Couldn't route MessageExchange " + me);
}
}
/**
* MessageListener implementation
*
* @param message
*/
public void onMessage(Message message) {
try {
if (message != null && message instanceof ObjectMessage) {
ObjectMessage objMsg = (ObjectMessage) message;
Object obj = objMsg.getObject();
if (obj != null) {
if (obj instanceof ComponentPacketEvent) {
ComponentPacketEvent event = (ComponentPacketEvent) obj;
String containerName = event.getPacket().getComponentNameSpace().getContainerName();
processInBoundPacket(containerName, event);
}
else if (obj instanceof MessageExchangeImpl) {
MessageExchangeImpl me = (MessageExchangeImpl) obj;
TransactionManager tm = (TransactionManager) getTransactionManager();
if (tm != null) {
me.setTransactionContext(tm.getTransaction());
}
super.doRouting(me);
}
}
}
}
catch (JMSException jmsEx) {
log.error("Caught an exception unpacking JMS Message: ", jmsEx);
}
catch (MessagingException e) {
log.error("Caught an exception routing ExchangePacket: ", e);
}
catch (SystemException e) {
log.error("Caught an exception acessing transaction context: ", e);
}
}
/**
* Process Inbound packets
*
* @param containerName
* @param event
*/
protected void processInBoundPacket(String containerName, ComponentPacketEvent event) {
ComponentPacket packet = event.getPacket();
if (!packet.getComponentNameSpace().getContainerName().equals(broker.getContainerName())) {
log.info("received from internal JMS network: " + event);
if (event.getStatus() == ComponentPacketEvent.ACTIVATED) {
addRemotePacket(containerName, packet);
}
else if (event.getStatus() == ComponentPacketEvent.DEACTIVATED) {
removeRemotePacket(containerName, packet);
}
else if (event.getStatus() == ComponentPacketEvent.STATE_CHANGE) {
updateRemotePacket(containerName, packet);
}
else {
log.warn("Unable to determine ComponentPacketEvent type: " + event.getStatus() + " for packet: "
+ packet);
}
}
}
private void addRemotePacket(String containerName, ComponentPacket packet) {
networkComponentKeyMap.put(packet.getComponentNameSpace(), containerName);
Set set = (Set) networkNodeKeyMap.get(containerName);
if (set == null) {
set = new CopyOnWriteArraySet();
networkNodeKeyMap.put(containerName, set);
}
ComponentConnector cc = new ComponentConnector(packet);
log.info("Adding Remote Component: " + cc);
broker.getRegistry().addRemoteComponentConnector(cc);
set.add(packet);
}
private void updateRemotePacket(String containerName, ComponentPacket packet) {
Set set = (Set) networkNodeKeyMap.get(containerName);
if (set != null) {
set.remove(packet);
set.add(packet);
}
ComponentConnector cc = new ComponentConnector(packet);
log.info("Updating remote Component: " + cc);
broker.getRegistry().updateRemoteComponentConnector(cc);
}
private void removeRemotePacket(String containerName, ComponentPacket packet) {
networkComponentKeyMap.remove(packet.getComponentNameSpace());
Set set = (Set) networkNodeKeyMap.get(containerName);
if (set != null) {
set.remove(packet);
ComponentConnector cc = new ComponentConnector(packet);
log.info("Removing remote Component: " + cc);
broker.getRegistry().removeRemoteComponentConnector(cc);
if (set.isEmpty()) {
networkNodeKeyMap.remove(containerName);
}
}
}
private void removeAllPackets(String containerName) {
Set set = (Set) networkNodeKeyMap.remove(containerName);
if (set != null) {
for (Iterator i = set.iterator();i.hasNext();) {
ComponentPacket packet = (ComponentPacket) i.next();
ComponentConnector cc = new ComponentConnector(packet);
log.info("Network node: " + containerName + " Stopped. Removing remote Component: " + cc);
broker.getRegistry().removeRemoteComponentConnector(cc);
networkComponentKeyMap.remove(packet.getComponentNameSpace());
}
}
}
public ConnectionManager getConnectionManager() throws Exception {
if (connectionManager == null) {
ConnectionManagerFactoryBean cmfb = new ConnectionManagerFactoryBean();
cmfb.setTransactionContextManager(getTransactionContextManager());
cmfb.setPoolingSupport(new SinglePool(
16, // max size
0, // min size
100, // blockingTimeoutMilliseconds
1, // idleTimeoutMinutes
true, // matchOne
true, // matchAll
true)); // selectOneAssumeMatch
cmfb.setTransactionSupport(new XATransactions(
true, // useTransactionCaching
false)); // useThreadCaching
cmfb.afterPropertiesSet();
connectionManager = (ConnectionManager) cmfb.getObject();
}
return connectionManager;
}
public void setConnectionManager(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
public TransactionContextManager getTransactionContextManager() {
return transactionContextManager;
}
public void setTransactionContextManager(
TransactionContextManager transactionContextManager) {
this.transactionContextManager = transactionContextManager;
}
public BootstrapContext getBootstrapContext() {
if (bootstrapContext == null) {
GeronimoWorkManager wm = (GeronimoWorkManager) broker.getWorkManager();
bootstrapContext = new BootstrapContextImpl(wm);
}
return bootstrapContext;
}
public void setBootstrapContext(BootstrapContext bootstrapContext) {
this.bootstrapContext = bootstrapContext;
}
public String toString(){
return broker.getContainerName() + " JCAFlow";
}
}