001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.jbi.nmr.flow.jms;
018    
019    import java.util.Map;
020    import java.util.Set;
021    import java.util.concurrent.ConcurrentHashMap;
022    import java.util.concurrent.CopyOnWriteArraySet;
023    import java.util.concurrent.atomic.AtomicBoolean;
024    
025    import javax.jbi.JBIException;
026    import javax.jbi.messaging.MessageExchange;
027    import javax.jbi.messaging.MessageExchange.Role;
028    import javax.jbi.messaging.MessagingException;
029    import javax.jbi.servicedesc.ServiceEndpoint;
030    import javax.jms.Connection;
031    import javax.jms.ConnectionFactory;
032    import javax.jms.DeliveryMode;
033    import javax.jms.JMSException;
034    import javax.jms.Message;
035    import javax.jms.MessageConsumer;
036    import javax.jms.MessageListener;
037    import javax.jms.MessageProducer;
038    import javax.jms.ObjectMessage;
039    import javax.jms.Queue;
040    import javax.jms.Session;
041    import javax.jms.Topic;
042    
043    import org.apache.servicemix.JbiConstants;
044    import org.apache.servicemix.executors.Executor;
045    import org.apache.servicemix.jbi.event.ComponentAdapter;
046    import org.apache.servicemix.jbi.event.ComponentEvent;
047    import org.apache.servicemix.jbi.event.ComponentListener;
048    import org.apache.servicemix.jbi.event.EndpointAdapter;
049    import org.apache.servicemix.jbi.event.EndpointEvent;
050    import org.apache.servicemix.jbi.event.EndpointListener;
051    import org.apache.servicemix.jbi.framework.ComponentMBeanImpl;
052    import org.apache.servicemix.jbi.messaging.MessageExchangeImpl;
053    import org.apache.servicemix.jbi.nmr.Broker;
054    import org.apache.servicemix.jbi.nmr.flow.AbstractFlow;
055    import org.apache.servicemix.jbi.servicedesc.EndpointSupport;
056    import org.apache.servicemix.jbi.servicedesc.InternalEndpoint;
057    
058    /**
059     * Use for message routing among a network of containers. All
060     * routing/registration happens automatically.
061     * 
062     */
063    public abstract class AbstractJMSFlow extends AbstractFlow implements MessageListener {
064    
065        private static final String INBOUND_PREFIX = "org.apache.servicemix.jms.";
066    
067        protected ConnectionFactory connectionFactory;
068        protected Connection connection;
069        protected AtomicBoolean started = new AtomicBoolean(false);
070        protected MessageConsumer monitorMessageConsumer;
071        protected Set<String> subscriberSet = new CopyOnWriteArraySet<String>();
072    
073        private String userName;
074        private String password;
075        private String broadcastDestinationName = "org.apache.servicemix.JMSFlow";
076        private MessageConsumer broadcastConsumer;
077        private Map<String, MessageConsumer> consumerMap = new ConcurrentHashMap<String, MessageConsumer>();
078        private EndpointListener endpointListener;
079        private ComponentListener componentListener;
080        private Executor executor;
081        private String jmsURL = "peer://org.apache.servicemix?persistent=false";
082    
083        /**
084         * The type of Flow
085         * 
086         * @return the type
087         */
088        public String getDescription() {
089            return "jms";
090        }
091    
092        /**
093         * @return Returns the password.
094         */
095        public String getPassword() {
096            return password;
097        }
098    
099        /**
100         * @param password
101         *            The password to set.
102         */
103        public void setPassword(String password) {
104            this.password = password;
105        }
106    
107        /**
108         * @return Returns the userName.
109         */
110        public String getUserName() {
111            return userName;
112        }
113    
114        /**
115         * @param userName
116         *            The userName to set.
117         */
118        public void setUserName(String userName) {
119            this.userName = userName;
120        }
121    
122        /**
123         * @return Returns the connectionFactory.
124         */
125        public ConnectionFactory getConnectionFactory() {
126            return connectionFactory;
127        }
128    
129        /**
130         * @param connectionFactory
131         *            The connectionFactory to set.
132         */
133        public void setConnectionFactory(ConnectionFactory connectionFactory) {
134            this.connectionFactory = connectionFactory;
135        }
136    
137        /**
138         * @return Returns the broadcastDestinationName.
139         */
140        public String getBroadcastDestinationName() {
141            return broadcastDestinationName;
142        }
143    
144        /**
145         * @param broadcastDestinationName
146         *            The broadcastDestinationName to set.
147         */
148        public void setBroadcastDestinationName(String broadcastDestinationName) {
149            this.broadcastDestinationName = broadcastDestinationName;
150        }
151    
152        /**
153         * Check if the flow can support the requested QoS for this exchange
154         * 
155         * @param me
156         *            the exchange to check
157         * @return true if this flow can handle the given exchange
158         */
159        public boolean canHandle(MessageExchange me) {
160            if (isTransacted(me)) {
161                return false;
162            }
163            return true;
164        }
165    
166        /**
167         * Initialize the Region
168         * 
169         * @param broker
170         * @throws JBIException
171         */
172        public void init(Broker broker) throws JBIException {
173            log.debug(broker.getContainer().getName() + ": Initializing jms flow");
174            super.init(broker);
175            // Find executor
176            executor = broker.getContainer().getExecutorFactory().createExecutor("flow.jms");
177            // Create and register endpoint listener
178            endpointListener = new EndpointAdapter() {
179                public void internalEndpointRegistered(EndpointEvent event) {
180                    onInternalEndpointRegistered(event, true);
181                }
182    
183                public void internalEndpointUnregistered(EndpointEvent event) {
184                    onInternalEndpointUnregistered(event, true);
185                }
186            };
187            broker.getContainer().addListener(endpointListener);
188            // Create and register component listener
189            componentListener = new ComponentAdapter() {
190                public void componentStarted(ComponentEvent event) {
191                    onComponentStarted(event);
192                }
193    
194                public void componentStopped(ComponentEvent event) {
195                    onComponentStopped(event);
196                }
197            };
198            broker.getContainer().addListener(componentListener);
199            try {
200                if (connectionFactory == null) {
201                    connectionFactory = createConnectionFactoryFromUrl(jmsURL);
202                }
203                if (userName != null) {
204                    connection = connectionFactory.createConnection(userName, password);
205                } else {
206                    connection = connectionFactory.createConnection();
207                }
208                connection.setClientID(broker.getContainer().getName());
209                connection.start();
210                Session inboundSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
211                Queue queue = inboundSession.createQueue(INBOUND_PREFIX + broker.getContainer().getName());
212                MessageConsumer inboundQueue = inboundSession.createConsumer(queue);
213                inboundQueue.setMessageListener(this);
214            } catch (JMSException e) {
215                log.error("Failed to initialize JMSFlow", e);
216                throw new JBIException(e);
217            }
218        }
219    
220        protected abstract ConnectionFactory createConnectionFactoryFromUrl(String url);
221    
222        /*
223         * The following abstract methods have to be implemented by specialized JMS
224         * Flow providers to monitor consumers on the broadcast topic.
225         */
226    
227        protected abstract void onConsumerMonitorMessage(Message message);
228    
229        public abstract void startConsumerMonitor() throws JMSException;
230    
231        public void stopConsumerMonitor() throws JMSException {
232            monitorMessageConsumer.close();
233        }
234    
235        /**
236         * start the flow
237         * 
238         * @throws JBIException
239         */
240        public void start() throws JBIException {
241            if (started.compareAndSet(false, true)) {
242                log.debug(broker.getContainer().getName() + ": Starting jms flow");
243                super.start();
244                try {
245                    Session broadcastSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
246                    Topic broadcastTopic = broadcastSession.createTopic(broadcastDestinationName);
247                    broadcastConsumer = broadcastSession.createConsumer(broadcastTopic, null, true);
248                    broadcastConsumer.setMessageListener(new MessageListener() {
249                        public void onMessage(Message message) {
250                            try {
251                                Object obj = ((ObjectMessage) message).getObject();
252                                if (obj instanceof EndpointEvent) {
253                                    EndpointEvent event = (EndpointEvent) obj;
254                                    String container = ((InternalEndpoint) event.getEndpoint()).getComponentNameSpace()
255                                            .getContainerName();
256                                    if (!getBroker().getContainer().getName().equals(container)) {
257                                        if (event.getEventType() == EndpointEvent.INTERNAL_ENDPOINT_REGISTERED) {
258                                            onRemoteEndpointRegistered(event);
259                                        } else if (event.getEventType() == EndpointEvent.INTERNAL_ENDPOINT_UNREGISTERED) {
260                                            onRemoteEndpointUnregistered(event);
261                                        }
262                                    }
263                                }
264                            } catch (Exception e) {
265                                log.error("Error processing incoming broadcast message", e);
266                            }
267                        }
268                    });
269    
270                    // Start queue consumers for all components
271                    for (ComponentMBeanImpl cmp : broker.getContainer().getRegistry().getComponents()) {
272                        if (cmp.isStarted()) {
273                            onComponentStarted(new ComponentEvent(cmp, ComponentEvent.COMPONENT_STARTED));
274                        }
275                    }
276                    // Start queue consumers for all endpoints
277                    ServiceEndpoint[] endpoints = broker.getContainer().getRegistry().getEndpointsForInterface(null);
278                    for (int i = 0; i < endpoints.length; i++) {
279                        if (endpoints[i] instanceof InternalEndpoint && ((InternalEndpoint) endpoints[i]).isLocal()) {
280                            onInternalEndpointRegistered(new EndpointEvent(endpoints[i],
281                                    EndpointEvent.INTERNAL_ENDPOINT_REGISTERED), false);
282                        }
283                    }
284    
285                    startConsumerMonitor();
286                } catch (JMSException e) {
287                    JBIException jbiEx = new JBIException("JMSException caught in start: " + e.getMessage());
288                    throw jbiEx;
289                }
290            }
291        }
292    
293        /**
294         * stop the flow
295         * 
296         * @throws JBIException
297         */
298        public void stop() throws JBIException {
299            if (started.compareAndSet(true, false)) {
300                log.debug(broker.getContainer().getName() + ": Stopping jms flow");
301                super.stop();
302                for (String id : subscriberSet) {
303                    removeAllPackets(id);
304                }
305                subscriberSet.clear();
306                try {
307                    stopConsumerMonitor();
308                    broadcastConsumer.close();
309                } catch (JMSException e) {
310                    log.debug("JMSException caught in stop", e);
311                }
312            }
313        }
314    
315        public void shutDown() throws JBIException {
316            super.shutDown();
317            stop();
318            // Remove endpoint listener
319            broker.getContainer().removeListener(endpointListener);
320            // Remove component listener
321            broker.getContainer().removeListener(componentListener);
322            if (this.connection != null) {
323                try {
324                    this.connection.close();
325                } catch (JMSException e) {
326                    log.warn("Error closing JMS Connection", e);
327                }
328            }
329        }
330    
331        /**
332         * useful for testing
333         * 
334         * @return number of containers in the network
335         */
336        public int numberInNetwork() {
337            return subscriberSet.size();
338        }
339    
340        public void onInternalEndpointRegistered(EndpointEvent event, boolean broadcast) {
341            if (!started.get()) {
342                return;
343            }
344            try {
345                String key = EndpointSupport.getKey(event.getEndpoint());
346                if (!consumerMap.containsKey(key)) {
347                    Session inboundSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
348                    Queue queue = inboundSession.createQueue(INBOUND_PREFIX + key);
349                    MessageConsumer consumer = inboundSession.createConsumer(queue);
350                    consumer.setMessageListener(this);
351                    consumerMap.put(key, consumer);
352                }
353                if (broadcast) {
354                    broadcast(event);
355                }
356            } catch (Exception e) {
357                log.error("Cannot create consumer for " + event.getEndpoint(), e);
358            }
359        }
360    
361        public void onInternalEndpointUnregistered(EndpointEvent event, boolean broadcast) {
362            try {
363                String key = EndpointSupport.getKey(event.getEndpoint());
364                MessageConsumer consumer = consumerMap.remove(key);
365                if (consumer != null) {
366                    consumer.close();
367                }
368                if (broadcast) {
369                    broadcast(event);
370                }
371            } catch (Exception e) {
372                log.error("Cannot destroy consumer for " + event, e);
373            }
374        }
375        
376        protected void broadcast(EndpointEvent event) throws Exception {
377            if (log.isDebugEnabled()) {
378                log.debug(broker.getContainer().getName() + ": broadcasting info for " + event);
379            }
380            Session broadcastSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
381            try {
382                ObjectMessage msg = broadcastSession.createObjectMessage(event);
383                Topic broadcastTopic = broadcastSession.createTopic(broadcastDestinationName);
384                MessageProducer topicProducer = broadcastSession.createProducer(broadcastTopic);
385                topicProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
386                topicProducer.send(msg);
387            } finally {
388                broadcastSession.close();
389            }
390        }
391    
392        public void onComponentStarted(ComponentEvent event) {
393            if (!started.get()) {
394                return;
395            }
396            try {
397                String key = event.getComponent().getName();
398                if (!consumerMap.containsKey(key)) {
399                    Session inboundSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
400                    Queue queue = inboundSession.createQueue(INBOUND_PREFIX + key);
401                    MessageConsumer consumer = inboundSession.createConsumer(queue);
402                    consumer.setMessageListener(this);
403                    consumerMap.put(key, consumer);
404                }
405            } catch (Exception e) {
406                log.error("Cannot create consumer for component " + event.getComponent().getName(), e);
407            }
408        }
409    
410        public void onComponentStopped(ComponentEvent event) {
411            try {
412                String key = event.getComponent().getName();
413                MessageConsumer consumer = consumerMap.remove(key);
414                if (consumer != null) {
415                    consumer.close();
416                }
417            } catch (Exception e) {
418                log.error("Cannot destroy consumer for component " + event.getComponent().getName(), e);
419            }
420        }
421    
422        public void onRemoteEndpointRegistered(EndpointEvent event) {
423            log.debug(broker.getContainer().getName() + ": adding remote endpoint: " + event.getEndpoint());
424            broker.getContainer().getRegistry().registerRemoteEndpoint(event.getEndpoint());
425        }
426    
427        public void onRemoteEndpointUnregistered(EndpointEvent event) {
428            log.debug(broker.getContainer().getName() + ": removing remote endpoint: " + event.getEndpoint());
429            broker.getContainer().getRegistry().unregisterRemoteEndpoint(event.getEndpoint());
430        }
431    
432        /**
433         * Distribute an ExchangePacket
434         * 
435         * @param me
436         * @throws MessagingException
437         */
438        protected void doSend(MessageExchangeImpl me) throws MessagingException {
439            doRouting(me);
440        }
441    
442        /**
443         * Distribute an ExchangePacket
444         * 
445         * @param me
446         * @throws MessagingException
447         */
448        public void doRouting(MessageExchangeImpl me) throws MessagingException {
449            // let ActiveMQ do the routing ...
450            try {
451                String destination;
452                if (me.getRole() == Role.PROVIDER) {
453                    if (me.getDestinationId() == null) {
454                        destination = INBOUND_PREFIX + EndpointSupport.getKey(me.getEndpoint());
455                    } else if (Boolean.TRUE.equals(me.getProperty(JbiConstants.STATELESS_PROVIDER)) && !isSynchronous(me)) {
456                        destination = INBOUND_PREFIX + me.getDestinationId().getName();
457                    } else {
458                        destination = INBOUND_PREFIX + me.getDestinationId().getContainerName();
459                    }
460                } else {
461                    if (me.getSourceId() == null) {
462                        throw new IllegalStateException("No sourceId set on the exchange");
463                    } else if (Boolean.TRUE.equals(me.getProperty(JbiConstants.STATELESS_CONSUMER)) && !isSynchronous(me)) {
464                        // If the consumer is stateless and has specified a sender
465                        // endpoint,
466                        // this exchange will be sent to the given endpoint queue,
467                        // so that
468                        // fail-over and load-balancing can be achieved
469                        // This property must have been created using
470                        // EndpointSupport.getKey
471                        if (me.getProperty(JbiConstants.SENDER_ENDPOINT) != null) {
472                            destination = INBOUND_PREFIX + me.getProperty(JbiConstants.SENDER_ENDPOINT);
473                        } else {
474                            destination = INBOUND_PREFIX + me.getSourceId().getName();
475                        }
476                    } else {
477                        destination = INBOUND_PREFIX + me.getSourceId().getContainerName();
478                    }
479                }
480    
481                Session inboundSession = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
482                try {
483                    Queue queue = inboundSession.createQueue(destination);
484                    ObjectMessage msg = inboundSession.createObjectMessage(me);
485                    // Set message priority.
486                    Integer priority = (Integer) me.getProperty(JbiConstants.MESSAGE_PRIORITY);
487                    if (null != priority) {
488                        msg.setJMSPriority(priority);
489                    }
490                    MessageProducer queueProducer = inboundSession.createProducer(queue);
491                    queueProducer.send(msg);
492                } finally {
493                    inboundSession.close();
494                }
495            } catch (JMSException e) {
496                log.error("Failed to send exchange: " + me + " internal JMS Network", e);
497                throw new MessagingException(e);
498            }
499        }
500        
501        /**
502         * MessageListener implementation
503         * 
504         * @param message
505         */
506        public void onMessage(final Message message) {
507            try {
508                if (message != null && started.get()) {
509                    ObjectMessage objMsg = (ObjectMessage) message;
510                    final MessageExchangeImpl me = (MessageExchangeImpl) objMsg.getObject();
511                    // Dispatch the message in another thread so as to free the jms
512                    // session
513                    // else if a component do a sendSync into the jms flow, the
514                    // whole
515                    // flow is deadlocked
516                    executor.execute(new Runnable() {
517                        public void run() {
518                            try {
519                                if (me.getDestinationId() == null) {
520                                    ServiceEndpoint se = me.getEndpoint();
521                                    se = broker.getContainer().getRegistry().getInternalEndpoint(se.getServiceName(),
522                                            se.getEndpointName());
523                                    me.setEndpoint(se);
524                                    me.setDestinationId(((InternalEndpoint) se).getComponentNameSpace());
525                                }
526                                AbstractJMSFlow.super.doRouting(me);
527                            } catch (Throwable e) {
528                                log.error("Caught an exception routing ExchangePacket: ", e);
529                            }
530                        }
531                    });
532                }
533            } catch (JMSException jmsEx) {
534                log.error("Caught an exception unpacking JMS Message: ", jmsEx);
535            }
536        }
537    
538        /**
539         * A new cluster node is announced. Add this node to the subscriber set and
540         * send all our local internal endpoints to this node.
541         * 
542         * @param connectionId
543         */
544        protected void addClusterNode(String connectionId) {
545            subscriberSet.add(connectionId);
546            ServiceEndpoint[] endpoints = broker.getContainer().getRegistry().getEndpointsForInterface(null);
547            for (int i = 0; i < endpoints.length; i++) {
548                if (endpoints[i] instanceof InternalEndpoint && ((InternalEndpoint) endpoints[i]).isLocal()) {
549                    onInternalEndpointRegistered(
550                            new EndpointEvent(endpoints[i], EndpointEvent.INTERNAL_ENDPOINT_REGISTERED), true);
551                }
552            }
553        }
554    
555        /**
556         * A cluster node leaves the cluster. Remove this node from the subscriber
557         * set and remove all packets waiting to be delivered to this node
558         * 
559         * @param connectionId
560         */
561        protected void removeClusterNode(String connectionId) {
562            subscriberSet.remove(connectionId);
563            removeAllPackets(connectionId);
564        }
565    
566        protected void removeAllPackets(String containerName) {
567            // TODO: broker.getRegistry().unregisterRemoteEndpoints(containerName);
568        }
569    
570        public String getJmsURL() {
571            return jmsURL;
572        }
573    
574        public void setJmsURL(String jmsURL) {
575            this.jmsURL = jmsURL;
576        }
577    
578    }