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 }