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.common;
018
019 import java.lang.reflect.Method;
020 import java.util.Map;
021
022 import javax.jbi.JBIException;
023 import javax.jbi.component.ComponentContext;
024 import javax.jbi.component.ComponentLifeCycle;
025 import javax.jbi.management.LifeCycleMBean;
026 import javax.jbi.messaging.DeliveryChannel;
027 import javax.jbi.messaging.ExchangeStatus;
028 import javax.jbi.messaging.MessageExchange;
029 import javax.jbi.messaging.MessagingException;
030 import javax.jbi.messaging.MessageExchange.Role;
031 import javax.jbi.servicedesc.ServiceEndpoint;
032 import javax.management.MBeanServer;
033 import javax.management.ObjectName;
034 import javax.transaction.Status;
035 import javax.transaction.Transaction;
036 import javax.transaction.TransactionManager;
037 import javax.xml.namespace.QName;
038
039 import org.apache.commons.logging.Log;
040 import org.apache.servicemix.JbiConstants;
041 import org.apache.servicemix.executors.Executor;
042 import org.apache.servicemix.executors.ExecutorFactory;
043 import org.apache.servicemix.executors.impl.ExecutorFactoryImpl;
044
045 import java.util.concurrent.ConcurrentHashMap;
046 import java.util.concurrent.atomic.AtomicBoolean;
047
048 /**
049 * Base class for life cycle management of components. This class may be used as
050 * is.
051 *
052 * @author Guillaume Nodet
053 * @version $Revision: 399873 $
054 * @since 3.0
055 */
056 public class AsyncBaseLifeCycle implements ComponentLifeCycle {
057
058 public static final String INITIALIZED = "Initialized";
059
060 protected transient Log logger;
061
062 protected ServiceMixComponent component;
063
064 protected ComponentContext context;
065
066 protected ObjectName mbeanName;
067
068 protected ExecutorFactory executorFactory;
069
070 protected Executor executor;
071
072 protected AtomicBoolean running;
073
074 protected DeliveryChannel channel;
075
076 protected Thread poller;
077
078 protected AtomicBoolean polling;
079
080 protected TransactionManager transactionManager;
081
082 protected boolean workManagerCreated;
083
084 protected Map<String, ExchangeProcessor> processors;
085
086 protected ThreadLocal<String> correlationId;
087
088 protected String currentState = LifeCycleMBean.UNKNOWN;
089
090 public AsyncBaseLifeCycle() {
091 this.running = new AtomicBoolean(false);
092 this.polling = new AtomicBoolean(false);
093 this.processors = new ConcurrentHashMap<String, ExchangeProcessor>();
094 this.correlationId = new ThreadLocal<String>();
095 }
096
097 public AsyncBaseLifeCycle(ServiceMixComponent component) {
098 this();
099 setComponent(component);
100 }
101
102 protected void setComponent(ServiceMixComponent component) {
103 this.component = component;
104 this.logger = component.getLogger();
105 }
106
107 /*
108 * (non-Javadoc)
109 *
110 * @see javax.jbi.component.ComponentLifeCycle#getExtensionMBeanName()
111 */
112 public ObjectName getExtensionMBeanName() {
113 return mbeanName;
114 }
115
116 protected Object getExtensionMBean() throws Exception {
117 return null;
118 }
119
120 protected ObjectName createExtensionMBeanName() throws Exception {
121 return this.context.getMBeanNames().createCustomComponentMBeanName("Configuration");
122 }
123
124 public QName getEPRServiceName() {
125 return null;
126 }
127
128 public String getCurrentState() {
129 return currentState;
130 }
131
132 protected void setCurrentState(String currentState) {
133 this.currentState = currentState;
134 }
135
136 public boolean isStarted(){
137 return currentState != null && currentState.equals(LifeCycleMBean.STARTED);
138 }
139
140 /**
141 * @return true if the object is stopped
142 */
143 public boolean isStopped(){
144 return currentState != null && currentState.equals(LifeCycleMBean.STOPPED);
145 }
146
147 /**
148 * @return true if the object is shutDown
149 */
150 public boolean isShutDown(){
151 return currentState != null && currentState.equals(LifeCycleMBean.SHUTDOWN);
152 }
153
154 /**
155 * @return true if the object is shutDown
156 */
157 public boolean isInitialized(){
158 return currentState != null && currentState.equals(INITIALIZED);
159 }
160
161 /**
162 * @return true if the object is shutDown
163 */
164 public boolean isUnknown(){
165 return currentState == null || currentState.equals(LifeCycleMBean.UNKNOWN);
166 }
167
168 /*
169 * (non-Javadoc)
170 *
171 * @see javax.jbi.component.ComponentLifeCycle#init(javax.jbi.component.ComponentContext)
172 */
173 public void init(ComponentContext context) throws JBIException {
174 try {
175 if (logger.isDebugEnabled()) {
176 logger.debug("Initializing component");
177 }
178 this.context = context;
179 this.channel = context.getDeliveryChannel();
180 try {
181 this.transactionManager = (TransactionManager) context.getTransactionManager();
182 } catch (Throwable e) {
183 // Ignore, this is just a safeguard against non compliant
184 // JBI implementation which throws an exception instead of
185 // return null
186 }
187 doInit();
188 setCurrentState(INITIALIZED);
189 if (logger.isDebugEnabled()) {
190 logger.debug("Component initialized");
191 }
192 } catch (JBIException e) {
193 throw e;
194 } catch (Exception e) {
195 throw new JBIException("Error calling init", e);
196 }
197 }
198
199 protected void doInit() throws Exception {
200 // Register extension mbean
201 Object mbean = getExtensionMBean();
202 if (mbean != null) {
203 MBeanServer server = this.context.getMBeanServer();
204 if (server == null) {
205 // TODO: log a warning ?
206 // throw new JBIException("null mBeanServer");
207 } else {
208 this.mbeanName = createExtensionMBeanName();
209 if (server.isRegistered(this.mbeanName)) {
210 server.unregisterMBean(this.mbeanName);
211 }
212 server.registerMBean(mbean, this.mbeanName);
213 }
214 }
215 // Obtain or create the work manager
216 // When using the WorkManager from ServiceMix,
217 // some class loader problems can appear when
218 // trying to uninstall the components.
219 // Some threads owned by the work manager have a
220 // security context referencing the component class loader
221 // so that every loaded classes are locked
222 // this.workManager = findWorkManager();
223 if (this.executorFactory == null) {
224 this.executorFactory = findExecutorFactory();
225 }
226 if (this.executorFactory == null) {
227 this.executorFactory = createExecutorFactory();
228 }
229 this.executor = this.executorFactory.createExecutor("component." + getContext().getComponentName());
230 }
231
232 /*
233 * (non-Javadoc)
234 *
235 * @see javax.jbi.component.ComponentLifeCycle#shutDown()
236 */
237 public void shutDown() throws JBIException {
238 try {
239 if (logger.isDebugEnabled()) {
240 logger.debug("Shutting down component");
241 }
242 doShutDown();
243 setCurrentState(LifeCycleMBean.SHUTDOWN);
244 this.context = null;
245 if (logger.isDebugEnabled()) {
246 logger.debug("Component shut down");
247 }
248 } catch (JBIException e) {
249 throw e;
250 } catch (Exception e) {
251 throw new JBIException("Error calling shutdown", e);
252 }
253 }
254
255 protected void doShutDown() throws Exception {
256 // Unregister mbean
257 if (this.mbeanName != null) {
258 MBeanServer server = this.context.getMBeanServer();
259 if (server == null) {
260 throw new JBIException("null mBeanServer");
261 }
262 if (server.isRegistered(this.mbeanName)) {
263 server.unregisterMBean(this.mbeanName);
264 }
265 }
266 // Destroy excutor
267 executor.shutdown();
268 executor = null;
269 }
270
271 /*
272 * (non-Javadoc)
273 *
274 * @see javax.jbi.component.ComponentLifeCycle#start()
275 */
276 public void start() throws JBIException {
277 try {
278 if (logger.isDebugEnabled()) {
279 logger.debug("Starting component");
280 }
281 if (this.running.compareAndSet(false, true)) {
282 doStart();
283 setCurrentState(LifeCycleMBean.STARTED);
284 }
285 if (logger.isDebugEnabled()) {
286 logger.debug("Component started");
287 }
288 } catch (JBIException e) {
289 throw e;
290 } catch (Exception e) {
291 throw new JBIException("Error calling start", e);
292 }
293 }
294
295 protected void doStart() throws Exception {
296 synchronized (this.polling) {
297 executor.execute(new Runnable() {
298 public void run() {
299 poller = Thread.currentThread();
300 pollDeliveryChannel();
301 }
302 });
303 polling.wait();
304 }
305 }
306
307 protected void pollDeliveryChannel() {
308 synchronized (polling) {
309 polling.set(true);
310 polling.notify();
311 }
312 while (running.get()) {
313 try {
314 final MessageExchange exchange = channel.accept(1000L);
315 if (exchange != null) {
316 final Transaction tx = (Transaction) exchange
317 .getProperty(MessageExchange.JTA_TRANSACTION_PROPERTY_NAME);
318 if (tx != null) {
319 if (transactionManager == null) {
320 throw new IllegalStateException(
321 "Exchange is enlisted in a transaction, but no transaction manager is available");
322 }
323 transactionManager.suspend();
324 }
325 executor.execute(new Runnable() {
326 public void run() {
327 processExchangeInTx(exchange, tx);
328 }
329 });
330 }
331 } catch (Throwable t) {
332 if (running.get() == false) {
333 // Should have been interrupted, discard the throwable
334 if (logger.isDebugEnabled()) {
335 logger.debug("Polling thread will stop");
336 }
337 } else {
338 logger.error("Error polling delivery channel", t);
339 }
340 }
341 }
342 synchronized (polling) {
343 polling.set(false);
344 polling.notify();
345 }
346 }
347
348 /*
349 * (non-Javadoc)
350 *
351 * @see javax.jbi.component.ComponentLifeCycle#stop()
352 */
353 public void stop() throws JBIException {
354 try {
355 if (logger.isDebugEnabled()) {
356 logger.debug("Stopping component");
357 }
358 if (this.running.compareAndSet(true, false)) {
359 doStop();
360 setCurrentState(LifeCycleMBean.STOPPED);
361 }
362 if (logger.isDebugEnabled()) {
363 logger.debug("Component stopped");
364 }
365 } catch (JBIException e) {
366 throw e;
367 } catch (Exception e) {
368 throw new JBIException("Error calling stop", e);
369 }
370 }
371
372 protected void doStop() throws Exception {
373 // Interrupt the polling thread and await termination
374 try {
375 synchronized (polling) {
376 if (polling.get()) {
377 poller.interrupt();
378 polling.wait();
379 }
380 }
381 } finally {
382 poller = null;
383 }
384 }
385
386 /**
387 * @return Returns the context.
388 */
389 public ComponentContext getContext() {
390 return context;
391 }
392
393 public Executor getExecutor() {
394 return executor;
395 }
396
397 protected ExecutorFactory createExecutorFactory() {
398 // Create a very simple one
399 return new ExecutorFactoryImpl();
400 }
401
402 protected ExecutorFactory findExecutorFactory() {
403 // If inside ServiceMix, retrieve its executor factory
404 try {
405 Method getContainerMth = context.getClass().getMethod("getContainer", new Class[0]);
406 Object container = getContainerMth.invoke(context, new Object[0]);
407 Method getWorkManagerMth = container.getClass().getMethod("getExecutorFactory", new Class[0]);
408 return (ExecutorFactory) getWorkManagerMth.invoke(container, new Object[0]);
409 } catch (Throwable t) {
410 if (logger.isDebugEnabled()) {
411 logger.debug("JBI container is not ServiceMix. Will create our own ExecutorFactory", t);
412 }
413 }
414 // TODO: should look in jndi for an existing ExecutorFactory
415 return null;
416 }
417
418 protected void processExchangeInTx(MessageExchange exchange, Transaction tx) {
419 try {
420 if (tx != null) {
421 transactionManager.resume(tx);
422 }
423 processExchange(exchange);
424 } catch (Exception e) {
425 logger.error("Error processing exchange " + exchange, e);
426 try {
427 // If we are transacted, check if this exception should
428 // rollback the transaction
429 if (transactionManager != null && transactionManager.getStatus() == Status.STATUS_ACTIVE
430 && exceptionShouldRollbackTx(e)) {
431 transactionManager.setRollbackOnly();
432 }
433 exchange.setError(e);
434 channel.send(exchange);
435 } catch (Exception inner) {
436 logger.error("Error setting exchange status to ERROR", inner);
437 }
438 } finally {
439 try {
440 // Check transaction status
441 if (tx != null) {
442 int status = transactionManager.getStatus();
443 // We use pull delivery, so the transaction should already
444 // have been transfered to another thread because the
445 // component
446 // must have answered.
447 if (status != Status.STATUS_NO_TRANSACTION) {
448 logger.error("Transaction is still active after exchange processing. Trying to rollback transaction.");
449 try {
450 transactionManager.rollback();
451 } catch (Throwable t) {
452 logger.error("Error trying to rollback transaction.", t);
453 }
454 }
455 }
456 } catch (Throwable t) {
457 logger.error("Error checking transaction status.", t);
458 }
459 }
460 }
461
462 protected boolean exceptionShouldRollbackTx(Exception e) {
463 return false;
464 }
465
466 protected void processExchange(MessageExchange exchange) throws Exception {
467 if (logger.isDebugEnabled()) {
468 logger.debug("Received exchange: status: " + exchange.getStatus() + ", role: "
469 + (exchange.getRole() == Role.CONSUMER ? "consumer" : "provider"));
470 }
471 if (exchange.getRole() == Role.PROVIDER) {
472 boolean dynamic = false;
473 ServiceEndpoint endpoint = exchange.getEndpoint();
474 String key = EndpointSupport.getKey(exchange.getEndpoint());
475 Endpoint ep = (Endpoint) this.component.getRegistry().getEndpoint(key);
476 if (ep == null) {
477 if (endpoint.getServiceName().equals(getEPRServiceName())) {
478 ep = getResolvedEPR(exchange.getEndpoint());
479 dynamic = true;
480 }
481 if (ep == null) {
482 throw new IllegalStateException("Endpoint not found: " + key);
483 }
484 }
485 ExchangeProcessor processor = ep.getProcessor();
486 if (processor == null) {
487 throw new IllegalStateException("No processor found for endpoint: " + key);
488 }
489 try {
490 doProcess(ep, processor, exchange);
491 } finally {
492 // If the endpoint is dynamic, deactivate it
493 if (dynamic) {
494 ep.deactivate();
495 }
496 }
497 } else {
498 ExchangeProcessor processor = null;
499 Endpoint ep = null;
500 if (exchange.getProperty(JbiConstants.SENDER_ENDPOINT) != null) {
501 String key = exchange.getProperty(JbiConstants.SENDER_ENDPOINT).toString();
502 ep = (Endpoint) this.component.getRegistry().getEndpoint(key);
503 if (ep != null) {
504 processor = ep.getProcessor();
505 }
506 } else {
507 processor = processors.remove(exchange.getExchangeId());
508 }
509 if (processor == null) {
510 throw new IllegalStateException("No processor found for: " + exchange.getExchangeId());
511 }
512 doProcess(ep, processor, exchange);
513 }
514
515 }
516
517 /**
518 * Thin wrapper around the call to the processor to ensure that the Endpoints
519 * classloader is used where available
520 *
521 */
522 private void doProcess(Endpoint ep, ExchangeProcessor processor, MessageExchange exchange) throws Exception {
523 ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
524 try {
525 ClassLoader cl = (ep != null) ? ep.getServiceUnit().getConfigurationClassLoader() : null;
526 if (cl != null) {
527 Thread.currentThread().setContextClassLoader(cl);
528 }
529 // Read the correlation id from the exchange and set it in the correlation id property
530 String correlationID = (String)exchange.getProperty(JbiConstants.CORRELATION_ID);
531 if (correlationID != null) {
532 // Set the id in threadlocal variable
533 correlationId.set(correlationID);
534 }
535 if (logger.isDebugEnabled()) {
536 logger.debug("Retrieved correlation id: " + correlationID);
537 }
538 processor.process(exchange);
539 } finally {
540 Thread.currentThread().setContextClassLoader(oldCl);
541 // Clean the threadlocal variable
542 correlationId.set(null);
543 }
544 }
545
546 /**
547 *
548 * @param exchange
549 * @param processor
550 * @throws MessagingException
551 * @deprecated use sendConsumerExchange(MessageExchange, Endpoint) instead
552 */
553 public void sendConsumerExchange(MessageExchange exchange, ExchangeProcessor processor) throws MessagingException {
554 // If the exchange is not ACTIVE, no answer is expected
555 if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
556 processors.put(exchange.getExchangeId(), processor);
557 }
558 channel.send(exchange);
559 }
560
561 /**
562 * This method allows the component to keep no state in memory so that
563 * components can be clustered and provide fail-over and load-balancing.
564 *
565 * @param exchange
566 * @param endpoint
567 * @throws MessagingException
568 */
569 public void sendConsumerExchange(MessageExchange exchange, Endpoint endpoint) throws MessagingException {
570 prepareConsumerExchange(exchange, endpoint);
571 // Send the exchange
572 channel.send(exchange);
573 }
574
575 public void prepareConsumerExchange(MessageExchange exchange, Endpoint endpoint) {
576 // Check if a correlation id is already set on the exchange, otherwise create it
577 String correlationIDValue = (String) exchange.getProperty(JbiConstants.CORRELATION_ID);
578 if (correlationIDValue == null) {
579 // Retrieve correlation id from thread local variable, if exist
580 correlationIDValue = correlationId.get();
581 if (correlationIDValue == null) {
582 // Set a correlation id property that have to be propagated in all components
583 // to trace the process instance
584 correlationIDValue = exchange.getExchangeId();
585 exchange.setProperty(JbiConstants.CORRELATION_ID, exchange.getExchangeId());
586 if (logger.isDebugEnabled()) {
587 logger.debug("Created correlation id: " + correlationIDValue);
588 }
589 } else {
590 // Use correlation id retrieved from previous message exchange
591 exchange.setProperty(JbiConstants.CORRELATION_ID, correlationIDValue);
592 if (logger.isDebugEnabled()) {
593 logger.debug("Correlation id retrieved from ThreadLocal: " + correlationIDValue);
594 }
595 }
596 }
597 // Set the sender endpoint property
598 String key = EndpointSupport.getKey(endpoint);
599 exchange.setProperty(JbiConstants.SENDER_ENDPOINT, key);
600 }
601
602 /**
603 * Handle an exchange sent to an EPR resolved by this component
604 *
605 * @param ep the service endpoint
606 * @return an endpoint to use for handling the exchange
607 * @throws Exception
608 */
609 protected Endpoint getResolvedEPR(ServiceEndpoint ep) throws Exception {
610 throw new UnsupportedOperationException("Component does not handle EPR exchanges");
611 }
612
613 }