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.processor;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.LinkedList;
022    import java.util.List;
023    import java.util.concurrent.ArrayBlockingQueue;
024    import java.util.concurrent.RejectedExecutionException;
025    import java.util.concurrent.RejectedExecutionHandler;
026    import java.util.concurrent.ThreadPoolExecutor;
027    import java.util.concurrent.TimeUnit;
028    import java.util.concurrent.atomic.AtomicBoolean;
029    
030    import org.apache.camel.AsyncCallback;
031    import org.apache.camel.Endpoint;
032    import org.apache.camel.Exchange;
033    import org.apache.camel.Processor;
034    import org.apache.camel.impl.ServiceSupport;
035    import org.apache.camel.processor.aggregate.AggregationStrategy;
036    import org.apache.camel.util.ExchangeHelper;
037    import org.apache.camel.util.ServiceHelper;
038    import org.apache.camel.util.concurrent.AtomicExchange;
039    import org.apache.camel.util.concurrent.CountingLatch;
040    
041    import static org.apache.camel.util.ObjectHelper.notNull;
042    
043    /**
044     * Implements the Multicast pattern to send a message exchange to a number of
045     * endpoints, each endpoint receiving a copy of the message exchange.
046     *
047     * @see Pipeline
048     * @version $Revision: 2038 $
049     */
050    public class MulticastProcessor extends ServiceSupport implements Processor {
051        static class ProcessorExchangePair {
052            private final Processor processor;
053            private final Exchange exchange;
054    
055            public ProcessorExchangePair(Processor processor, Exchange exchange) {
056                this.processor = processor;
057                this.exchange = exchange;
058            }
059    
060            public Processor getProcessor() {
061                return processor;
062            }
063    
064            public Exchange getExchange() {
065                return exchange;
066            }
067        }
068    
069        private Collection<Processor> processors;
070        private AggregationStrategy aggregationStrategy;
071        private boolean isParallelProcessing;
072        private ThreadPoolExecutor executor;
073        private final boolean streaming;
074        private final AtomicBoolean shutdown = new AtomicBoolean(true);
075    
076        public MulticastProcessor(Collection<Processor> processors) {
077            this(processors, null);
078        }
079    
080        public MulticastProcessor(Collection<Processor> processors, AggregationStrategy aggregationStrategy) {
081            this(processors, aggregationStrategy, false, null);
082        }
083        
084        public MulticastProcessor(Collection<Processor> processors, AggregationStrategy aggregationStrategy, boolean parallelProcessing, ThreadPoolExecutor executor) {
085            this(processors, aggregationStrategy, parallelProcessing, executor, false);
086        }
087    
088        public MulticastProcessor(Collection<Processor> processors, AggregationStrategy aggregationStrategy, boolean parallelProcessing, ThreadPoolExecutor executor, boolean streaming) {
089            notNull(processors, "processors");
090            this.processors = processors;
091            this.aggregationStrategy = aggregationStrategy;
092            this.isParallelProcessing = parallelProcessing;
093            if (isParallelProcessing) {
094                if (executor != null) {
095                    this.executor = executor;
096                } else { 
097                    // setup default Executor
098                    this.executor = new ThreadPoolExecutor(processors.size(), processors.size(), 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(processors.size()));
099                }
100            }
101            this.streaming = streaming;
102        }
103    
104        /**
105         * A helper method to convert a list of endpoints into a list of processors
106         */
107        public static <E extends Exchange> Collection<Processor> toProducers(Collection<Endpoint> endpoints)
108            throws Exception {
109            Collection<Processor> answer = new ArrayList<Processor>();
110            for (Endpoint endpoint : endpoints) {
111                answer.add(endpoint.createProducer());
112            }
113            return answer;
114        }
115    
116        @Override
117        public String toString() {
118            return "Multicast" + getProcessors();
119        }
120    
121        class ProcessCall implements Runnable {
122            private final Exchange exchange;
123            private final AsyncCallback callback;
124            private final Processor processor;
125    
126            public ProcessCall(Exchange exchange, Processor processor, AsyncCallback callback) {
127                this.exchange = exchange;
128                this.callback = callback;
129                this.processor = processor;
130            }
131    
132            public void run() {
133                if (shutdown.get()) {
134                    exchange.setException(new RejectedExecutionException());
135                    callback.done(false);
136                } else {
137                    try {
138                        processor.process(exchange);
139                    } catch (Exception ex) {
140                        exchange.setException(ex);
141                    }
142                    callback.done(false);
143                }
144            }
145        }
146    
147        public void process(Exchange exchange) throws Exception {
148            final AtomicExchange result = new AtomicExchange();
149    
150            Iterable<ProcessorExchangePair> pairs = createProcessorExchangePairs(exchange);
151            
152            // Parallel Processing the producer
153            if (isParallelProcessing) {
154                List<Exchange> exchanges = new LinkedList<Exchange>();
155                final CountingLatch completedExchanges = new CountingLatch();
156                int i = 0;
157                for (ProcessorExchangePair pair : pairs) {
158                    Processor producer = pair.getProcessor();
159                    final Exchange subExchange = pair.getExchange();
160                    updateNewExchange(subExchange, i, pairs);
161                    exchanges.add(subExchange);
162                    completedExchanges.increment(); 
163                    ProcessCall call = new ProcessCall(subExchange, producer, new AsyncCallback() {
164                        public void done(boolean doneSynchronously) {
165                            if (streaming && aggregationStrategy != null) {
166                                doAggregate(result, subExchange);
167                            }
168                            completedExchanges.decrement();
169                        }
170    
171                    });
172                    executor.execute(call);
173                    i++;
174                }
175                completedExchanges.await();
176                if (!streaming && aggregationStrategy != null) {
177                    for (Exchange resultExchange : exchanges) {
178                        doAggregate(result, resultExchange);
179                    }
180                }
181    
182            } else {
183                // we call the producer one by one sequentially
184                int i = 0;
185                for (ProcessorExchangePair pair : pairs) {
186                    Processor producer = pair.getProcessor();
187                    Exchange subExchange = pair.getExchange();
188                    updateNewExchange(subExchange, i, pairs);
189                    try {
190                        producer.process(subExchange);
191                    } catch (Exception exception) {
192                        subExchange.setException(exception);
193                    }
194                    doAggregate(result, subExchange);
195                    i++;
196                }
197            }
198            if (result.get() != null) {
199                ExchangeHelper.copyResults(exchange, result.get());
200            }
201        }
202    
203        /**
204         * Aggregate the {@link Exchange} with the current result
205         *
206         * @param result the current result
207         * @param exchange the exchange to be added to the result
208         */
209        protected synchronized void doAggregate(AtomicExchange result, Exchange exchange) {
210            if (aggregationStrategy != null) {
211                if (result.get() == null) {
212                    result.set(exchange);
213                } else {
214                    result.set(aggregationStrategy.aggregate(result.get(), exchange));
215                }
216            }
217        }
218    
219        protected void updateNewExchange(Exchange exchange, int i, Iterable<ProcessorExchangePair> allPairs) {
220            // No updates needed
221        }
222    
223        protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) {
224            List<ProcessorExchangePair> result = new ArrayList<ProcessorExchangePair>(processors.size());
225            Processor[] processorsArray = processors.toArray(new Processor[processors.size()]);
226            for (int i = 0; i < processorsArray.length; i++) {
227                result.add(new ProcessorExchangePair(processorsArray[i], exchange.copy()));
228            }
229            return result;
230        }
231    
232        protected void doStop() throws Exception {
233            shutdown.set(true);
234            if (executor != null) {
235                executor.shutdown();
236                executor.awaitTermination(0, TimeUnit.SECONDS);
237            }
238            ServiceHelper.stopServices(processors);
239        }
240    
241        protected void doStart() throws Exception {
242            shutdown.set(false);
243            if (executor != null) {
244                executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
245                    public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
246                        ProcessCall call = (ProcessCall)runnable;
247                        call.exchange.setException(new RejectedExecutionException());
248                        call.callback.done(false);
249                    }
250                });
251            }
252            ServiceHelper.startServices(processors);
253        }
254        
255        /**
256         * Is the multicast processor working in streaming mode?
257         * 
258         * In streaming mode:
259         * <ul>
260         * <li>we use {@link Iterable} to ensure we can send messages as soon as the data becomes available</li>
261         * <li>for parallel processing, we start aggregating responses as they get send back to the processor;
262         * this means the {@link org.apache.camel.processor.aggregate.AggregationStrategy} has to take care of handling out-of-order arrival of exchanges</li>
263         * </ul>
264         */
265        protected boolean isStreaming() {
266            return streaming;
267        }
268    
269        /**
270         * Returns the producers to multicast to
271         */
272        public Collection<Processor> getProcessors() {
273            return processors;
274        }
275    
276        public AggregationStrategy getAggregationStrategy() {
277            return aggregationStrategy;
278        }
279    }