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.camel.component.seda;
018    
019    import java.util.ArrayList;
020    import java.util.List;
021    import java.util.concurrent.BlockingQueue;
022    import java.util.concurrent.ExecutorService;
023    import java.util.concurrent.TimeUnit;
024    
025    import org.apache.camel.Consumer;
026    import org.apache.camel.Endpoint;
027    import org.apache.camel.Exchange;
028    import org.apache.camel.Processor;
029    import org.apache.camel.ShutdownRunningTask;
030    import org.apache.camel.impl.LoggingExceptionHandler;
031    import org.apache.camel.impl.ServiceSupport;
032    import org.apache.camel.processor.MulticastProcessor;
033    import org.apache.camel.spi.ExceptionHandler;
034    import org.apache.camel.spi.ShutdownAware;
035    import org.apache.camel.util.ServiceHelper;
036    import org.apache.camel.util.concurrent.ExecutorServiceHelper;
037    import org.apache.commons.logging.Log;
038    import org.apache.commons.logging.LogFactory;
039    
040    /**
041     * A Consumer for the SEDA component.
042     *
043     * @version $Revision: 893983 $
044     */
045    public class SedaConsumer extends ServiceSupport implements Consumer, Runnable, ShutdownAware {
046        private static final transient Log LOG = LogFactory.getLog(SedaConsumer.class);
047    
048        private SedaEndpoint endpoint;
049        private Processor processor;
050        private ExecutorService executor;
051        private Processor multicast;
052        private ExceptionHandler exceptionHandler;
053    
054        public SedaConsumer(SedaEndpoint endpoint, Processor processor) {
055            this.endpoint = endpoint;
056            this.processor = processor;
057        }
058    
059        @Override
060        public String toString() {
061            return "SedaConsumer[" + endpoint.getEndpointUri() + "]";
062        }
063    
064        public Endpoint getEndpoint() {
065            return endpoint;
066        }
067    
068        public ExceptionHandler getExceptionHandler() {
069            if (exceptionHandler == null) {
070                exceptionHandler = new LoggingExceptionHandler(getClass());
071            }
072            return exceptionHandler;
073        }
074    
075        public void setExceptionHandler(ExceptionHandler exceptionHandler) {
076            this.exceptionHandler = exceptionHandler;
077        }
078    
079        public Processor getProcessor() {
080            return processor;
081        }
082    
083        public boolean deferShutdown(ShutdownRunningTask shutdownRunningTask) {
084            // deny stopping on shutdown as we want seda consumers to run in case some other queues
085            // depend on this consumer to run, so it can complete its exchanges
086            return true;
087        }
088    
089        public int getPendingExchangesSize() {
090            // number of pending messages on the queue
091            return endpoint.getQueue().size();
092        }
093    
094        public void run() {
095            BlockingQueue<Exchange> queue = endpoint.getQueue();
096            while (queue != null && isRunAllowed()) {
097                final Exchange exchange;
098                try {
099                    exchange = queue.poll(1000, TimeUnit.MILLISECONDS);
100                } catch (InterruptedException e) {
101                    if (LOG.isDebugEnabled()) {
102                        LOG.debug("Sleep interrupted, are we stopping? " + (isStopping() || isStopped()));
103                    }
104                    continue;
105                }
106                if (exchange != null) {
107                    if (isRunAllowed()) {
108                        try {
109                            sendToConsumers(exchange);
110    
111                            // log exception if an exception occurred and was not handled
112                            if (exchange.getException() != null) {
113                                getExceptionHandler().handleException("Error processing exchange", exchange, exchange.getException());
114                            }
115                        } catch (Exception e) {
116                            getExceptionHandler().handleException("Error processing exchange", exchange, e);
117                        }
118                    } else {
119                        if (LOG.isWarnEnabled()) {
120                            LOG.warn("This consumer is stopped during polling an exchange, so putting it back on the seda queue: " + exchange);
121                        }
122                        try {
123                            queue.put(exchange);
124                        } catch (InterruptedException e) {
125                            if (LOG.isDebugEnabled()) {
126                                LOG.debug("Sleep interrupted, are we stopping? " + (isStopping() || isStopped()));
127                            }
128                            continue;
129                        }
130                    }
131                }
132            }
133        }
134    
135        /**
136         * Send the given {@link Exchange} to the consumer(s).
137         * <p/>
138         * If multiple consumers then they will each receive a copy of the Exchange.
139         * A multicast processor will send the exchange in parallel to the multiple consumers.
140         * <p/>
141         * If there is only a single consumer then its dispatched directly to it using same thread.
142         * 
143         * @param exchange the exchange
144         * @throws Exception can be thrown if processing of the exchange failed
145         */
146        protected void sendToConsumers(Exchange exchange) throws Exception {
147            int size = endpoint.getConsumers().size();
148    
149            // if there are multiple consumers then multicast to them
150            if (size > 1) {
151    
152                if (LOG.isDebugEnabled()) {
153                    LOG.debug("Multicasting to " + endpoint.getConsumers().size() + " consumers for Exchange: " + exchange);
154                }
155    
156                // use a multicast processor to process it
157                Processor mp = getMulticastProcessor();
158                mp.process(exchange);
159            } else {
160                // use the regular processor
161                processor.process(exchange);
162            }
163        }
164    
165        protected synchronized Processor getMulticastProcessor() {
166            if (multicast == null) {
167                int size = endpoint.getConsumers().size();
168    
169                List<Processor> processors = new ArrayList<Processor>(size);
170                for (SedaConsumer consumer : endpoint.getConsumers()) {
171                    processors.add(consumer.getProcessor());
172                }
173    
174                ExecutorService multicastExecutor = ExecutorServiceHelper.newFixedThreadPool(size, endpoint.getEndpointUri() + "(multicast)", true);
175                multicast = new MulticastProcessor(processors, null, true, multicastExecutor, false, false);
176            }
177            return multicast;
178        }
179    
180        protected void doStart() throws Exception {
181            int poolSize = endpoint.getConcurrentConsumers();
182            executor = ExecutorServiceHelper.newFixedThreadPool(poolSize, endpoint.getEndpointUri(), true);
183            for (int i = 0; i < poolSize; i++) {
184                executor.execute(this);
185            }
186            endpoint.onStarted(this);
187        }
188    
189        protected void doStop() throws Exception {
190            endpoint.onStopped(this);
191            executor.shutdownNow();
192            executor = null;
193    
194            if (multicast != null) {
195                ServiceHelper.stopServices(multicast);
196            }
197        }
198    
199    }