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    }