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.seda;
018    
019    import java.util.Map;
020    import java.util.concurrent.ConcurrentHashMap;
021    import java.util.concurrent.atomic.AtomicBoolean;
022    
023    import javax.jbi.JBIException;
024    import javax.jbi.management.LifeCycleMBean;
025    import javax.jbi.messaging.ExchangeStatus;
026    import javax.jbi.messaging.MessageExchange;
027    import javax.jbi.messaging.MessagingException;
028    import javax.management.JMException;
029    import javax.management.MBeanAttributeInfo;
030    import javax.management.ObjectName;
031    import javax.transaction.Transaction;
032    import javax.transaction.TransactionManager;
033    
034    import org.apache.servicemix.jbi.event.ComponentAdapter;
035    import org.apache.servicemix.jbi.event.ComponentEvent;
036    import org.apache.servicemix.jbi.event.ComponentListener;
037    import org.apache.servicemix.jbi.framework.ComponentNameSpace;
038    import org.apache.servicemix.jbi.management.AttributeInfoHelper;
039    import org.apache.servicemix.jbi.messaging.MessageExchangeImpl;
040    import org.apache.servicemix.jbi.nmr.Broker;
041    import org.apache.servicemix.jbi.nmr.flow.AbstractFlow;
042    import org.apache.servicemix.jbi.servicedesc.AbstractServiceEndpoint;
043    
044    /**
045     * The SedaFlow introduces a simple event staging between the internal processes 
046     * in the NMR Broker. A Seda flow (the default) is suited for general deployment, 
047     * as the additional staging is well suited buffering exchanges between heavily 
048     * routed to components (where state may be being used) for example.
049     * 
050     * @version $Revision: 665811 $
051     * @org.apache.xbean.XBean element="sedaFlow"
052     */
053    public class SedaFlow extends AbstractFlow {
054    
055        protected Map<ComponentNameSpace, SedaQueue> queueMap = new ConcurrentHashMap<ComponentNameSpace, SedaQueue>();
056        protected AtomicBoolean started = new AtomicBoolean(false);
057        protected ComponentListener listener;
058    
059        /**
060         * The type of Flow
061         * 
062         * @return the type
063         */
064        public String getDescription() {
065            return "seda";
066        }
067    
068        /**
069         * Initialize the Region
070         * 
071         * @param broker
072         * @throws JBIException
073         */
074        public void init(Broker broker) throws JBIException {
075            super.init(broker);
076            listener = new ComponentAdapter() {
077                public void componentShutDown(ComponentEvent event) {
078                    onComponentShutdown(event.getComponent().getComponentNameSpace());
079                }
080            };
081            broker.getContainer().addListener(listener);
082        }
083    
084        /**
085         * Check if the flow can support the requested QoS for this exchange
086         * @param me the exchange to check
087         * @return true if this flow can handle the given exchange
088         */
089        public boolean canHandle(MessageExchange me) {
090            if (isPersistent(me)) {
091                return false;
092            }
093            if (isClustered(me)) {
094                return false;
095            }
096            // we have the mirror, so the role is the one for the target component
097            if (!broker.getContainer().isUseNewTransactionModel() 
098                    && isTransacted(me) && !isSynchronous(me) && me.getStatus() == ExchangeStatus.ACTIVE) {
099                return false;
100            }
101            return true;
102        }
103        
104        /**
105         * start the flow
106         * 
107         * @throws JBIException
108         */
109        public void start() throws JBIException {
110            if (started.compareAndSet(false, true)) {
111                for (SedaQueue queue : queueMap.values()) {
112                    queue.start();
113                }
114            }
115            super.start();
116        }
117    
118        /**
119         * stop the flow
120         * 
121         * @throws JBIException
122         */
123        public void stop() throws JBIException {
124            if (started.compareAndSet(true, false)) {
125                for (SedaQueue queue : queueMap.values()) {
126                    queue.stop();
127                }
128            }
129            super.stop();
130        }
131    
132        /**
133         * shutDown the flow
134         * 
135         * @throws JBIException
136         */
137        public void shutDown() throws JBIException {
138            broker.getContainer().removeListener(listener);
139            for (SedaQueue queue : queueMap.values()) {
140                queue.shutDown();
141                unregisterQueue(queue);
142            }
143            super.shutDown();
144        }
145    
146        /**
147         * Distribute an ExchangePacket
148         * 
149         * @param packet
150         * @throws JBIException
151         */
152        protected void doSend(MessageExchangeImpl me) throws JBIException {
153            if (me.getDestinationId() == null) {
154                me.setDestinationId(((AbstractServiceEndpoint) me.getEndpoint()).getComponentNameSpace());
155            }
156            if (isTransacted(me)) {
157                me.setTxState(MessageExchangeImpl.TX_STATE_CONVEYED);
158            }
159            // If the message has been sent synchronously, do not use seda
160            // as it would consume threads from the work manager in a useless
161            // way.  This could lead to deadlocks.
162            suspendTx(me);
163            enqueuePacket(me);
164        }
165        
166        protected void doRouting(MessageExchangeImpl me) throws MessagingException {
167            resumeTx(me);
168            super.doRouting(me);
169        }
170    
171        /**
172         * Put the packet in the queue for later processing. 
173         * @param packet
174         * @throws JBIException
175         */
176        protected void enqueuePacket(MessageExchangeImpl me) throws JBIException {
177            ComponentNameSpace cns = me.getDestinationId();
178            SedaQueue queue = queueMap.get(cns);
179            if (queue == null) {
180                queue = createQueue(cns);
181            }
182            try {
183                queue.enqueue(me);
184            } catch (InterruptedException e) {
185                throw new MessagingException(queue + " Failed to enqueue exchange: " + me, e);
186            }
187        }
188        
189        protected synchronized SedaQueue createQueue(ComponentNameSpace cns) throws JBIException {
190            SedaQueue queue = queueMap.get(cns);
191            if (queue == null) {
192                queue = new SedaQueue(cns);
193                queue.init(this);
194                registerQueue(cns, queue);
195                if (started.get()) {
196                    queue.start();
197                }
198                queueMap.put(cns, queue);
199            }
200            return queue;
201        }
202        
203        /**
204         * Process state changes in Components
205         * 
206         * @param event
207         */
208        public synchronized void onComponentShutdown(ComponentNameSpace cns) {
209            SedaQueue queue = queueMap.remove(cns);
210            if (queue != null) {
211                try {
212                    queue.shutDown();
213                    unregisterQueue(queue);
214                } catch (JBIException e) {
215                    log.error("Failed to stop SedaQueue: " + queue + ": " + e);
216                    if (log.isDebugEnabled()) {
217                        log.debug("Failed to stop SedaQueue: " + queue, e);
218                    }
219                }
220            }
221        }
222    
223        /**
224         * release a queue
225         * 
226         * @param queue
227         */
228        public synchronized void release(SedaQueue queue) {
229            if (queue != null) {
230                queueMap.remove(queue.getComponentNameSpace());
231                unregisterQueue(queue);
232            }
233        }
234    
235        /**
236         * Get Queue number
237         * 
238         * @return number of running Queues
239         */
240        public int getQueueNumber() {
241            return queueMap.size();
242        }
243    
244        protected void registerQueue(ComponentNameSpace cns, SedaQueue queue) {
245            try {
246                ObjectName objectName = broker.getContainer().getManagementContext().createObjectName(queue);
247                if (getSubType() != null) {
248                    objectName = new ObjectName(objectName + ",subtype=" + getSubType());
249                }
250                queue.setObjectName(objectName);
251                broker.getContainer().getManagementContext().registerMBean(objectName, queue, LifeCycleMBean.class);
252            } catch (JMException e) {
253                log.error("Failed to register SedaQueue: " + queue + " with the ManagementContext: " + e);
254                if (log.isDebugEnabled()) {
255                    log.debug("Failed to register SedaQueue: " + queue + " with the ManagementContext", e);
256                }
257            }
258        }
259    
260        protected void unregisterQueue(SedaQueue queue) {
261            try {
262                broker.getContainer().getManagementContext().unregisterMBean(queue.getObjectName());
263            } catch (JBIException e) {
264                log.error("Failed to unregister SedaQueue: " + queue + " from the ManagementContext");
265                if (log.isDebugEnabled()) {
266                    log.debug("Failed to unregister SedaQueue: " + queue + " with the ManagementContext", e);
267                }
268            }
269        }
270    
271        /**
272         * Get an array of MBeanAttributeInfo
273         * 
274         * @return array of AttributeInfos
275         * @throws JMException
276         */
277        public MBeanAttributeInfo[] getAttributeInfos() throws JMException {
278            AttributeInfoHelper helper = new AttributeInfoHelper();
279            helper.addAttribute(getObjectToManage(), "queueNumber", "number of running SedaQueues");
280            return AttributeInfoHelper.join(super.getAttributeInfos(), helper.getAttributeInfos());
281        }
282    
283        protected void suspendTx(MessageExchangeImpl me) throws MessagingException {
284            if (broker.getContainer().isUseNewTransactionModel()) {
285                return;
286            }
287            try {
288                Transaction oldTx = me.getTransactionContext();
289                if (oldTx != null) {
290                    TransactionManager tm = (TransactionManager) getBroker().getContainer().getTransactionManager();
291                    if (tm != null) {
292                        if (log.isDebugEnabled()) {
293                            log.debug("Suspending transaction for " + me.getExchangeId() + " in " + this);
294                        }
295                        Transaction tx = tm.suspend();
296                        if (tx != oldTx) {
297                            throw new IllegalStateException("the transaction context set in "
298                                    + "the messageExchange is not bound to the current thread");
299                        }
300                    }
301                }
302            } catch (Exception e) {
303                throw new MessagingException(e);
304            }
305        }
306    
307        protected void resumeTx(MessageExchangeImpl me) throws MessagingException {
308            if (broker.getContainer().isUseNewTransactionModel()) {
309                return;
310            }
311            try {
312                Transaction oldTx = me.getTransactionContext();
313                if (oldTx != null) {
314                    TransactionManager tm = (TransactionManager) getBroker().getContainer().getTransactionManager();
315                    if (tm != null) {
316                        if (log.isDebugEnabled()) {
317                            log.debug("Resuming transaction for " + me.getExchangeId() + " in " + this);
318                        }
319                        tm.resume(oldTx);
320                    }
321                }
322            } catch (Exception e) {
323                throw new MessagingException(e);
324            }
325        }
326    
327    }