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    
020    
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.concurrent.ArrayBlockingQueue;
026    import java.util.concurrent.CountDownLatch;
027    import java.util.concurrent.RejectedExecutionException;
028    import java.util.concurrent.RejectedExecutionHandler;
029    import java.util.concurrent.ThreadPoolExecutor;
030    import java.util.concurrent.TimeUnit;
031    import java.util.concurrent.atomic.AtomicBoolean;
032    
033    import org.apache.camel.AsyncCallback;
034    import org.apache.camel.Endpoint;
035    import org.apache.camel.Exchange;
036    import org.apache.camel.Processor;
037    import org.apache.camel.impl.ServiceSupport;
038    import org.apache.camel.processor.aggregate.AggregationStrategy;
039    import org.apache.camel.util.ExchangeHelper;
040    import org.apache.camel.util.ServiceHelper;
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: 36865 $
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        }
070    
071        private Collection<Processor> processors;
072        private AggregationStrategy aggregationStrategy;
073        private boolean isParallelProcessing;
074        private ThreadPoolExecutor executor;
075        private final AtomicBoolean shutdown = new AtomicBoolean(true);
076    
077        public MulticastProcessor(Collection<Processor> processors) {
078            this(processors, null);
079        }
080    
081        public MulticastProcessor(Collection<Processor> processors, AggregationStrategy aggregationStrategy) {
082            this(processors, aggregationStrategy, false, null);
083        }
084    
085        public MulticastProcessor(Collection<Processor> processors, AggregationStrategy aggregationStrategy, boolean parallelProcessing, ThreadPoolExecutor executor) {
086            notNull(processors, "processors");
087            this.processors = processors;
088            this.aggregationStrategy = aggregationStrategy;
089            this.isParallelProcessing = parallelProcessing;
090            if (isParallelProcessing) {
091                if (executor != null) {
092                    this.executor = executor;
093                } else { // setup default Executor
094                    this.executor = new ThreadPoolExecutor(1, processors.size(), 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(processors.size()));
095                }
096    
097            }
098    
099        }
100    
101        /**
102         * A helper method to convert a list of endpoints into a list of processors
103         */
104        public static <E extends Exchange> Collection<Processor> toProducers(Collection<Endpoint> endpoints)
105            throws Exception {
106            Collection<Processor> answer = new ArrayList<Processor>();
107            for (Endpoint endpoint : endpoints) {
108                answer.add(endpoint.createProducer());
109            }
110            return answer;
111        }
112    
113        @Override
114        public String toString() {
115            return "Multicast" + getProcessors();
116        }
117    
118        class ProcessCall implements Runnable {
119            private final Exchange exchange;
120            private final AsyncCallback callback;
121            private final Processor processor;
122    
123            public ProcessCall(Exchange exchange, Processor processor, AsyncCallback callback) {
124                this.exchange = exchange;
125                this.callback = callback;
126                this.processor = processor;
127            }
128    
129            public void run() {
130                if (shutdown.get()) {
131                    exchange.setException(new RejectedExecutionException());
132                    callback.done(false);
133                } else {
134                    try {
135                        processor.process(exchange);
136                    } catch (Exception ex) {
137                        exchange.setException(ex);
138                    }
139                    callback.done(false);
140                }
141            }
142        }
143    
144        public void process(Exchange exchange) throws Exception {
145            Exchange result = null;
146    
147            List<ProcessorExchangePair> pairs = createProcessorExchangePairs(exchange);
148            
149            // Parallel Processing the producer
150            if (isParallelProcessing) {
151                Exchange[] exchanges = new Exchange[pairs.size()];
152                final CountDownLatch completedExchanges = new CountDownLatch(pairs.size());
153                int i = 0;
154                for (ProcessorExchangePair pair : pairs) {
155                    Processor producer = pair.getProcessor();
156                    exchanges[i] = pair.getExchange();
157                    updateNewExchange(exchanges[i], i, pairs);
158                    ProcessCall call = new ProcessCall(exchanges[i], producer, new AsyncCallback() {
159                        public void done(boolean doneSynchronously) {
160                            completedExchanges.countDown();
161                        }
162    
163                    });
164                    executor.execute(call);
165                    i++;
166                }
167                completedExchanges.await();
168                if (aggregationStrategy != null) {
169                    for (Exchange resultExchange : exchanges) {
170                        if (result == null) {
171                            result = resultExchange;
172                        } else {
173                            result = aggregationStrategy.aggregate(result, resultExchange);
174                        }
175                    }
176                }
177    
178            } else {
179                // we call the producer one by one sequentially
180                int i = 0;
181                for (ProcessorExchangePair pair : pairs) {
182                    Processor producer = pair.getProcessor();
183                    Exchange subExchange = pair.getExchange();
184                    updateNewExchange(subExchange, i, pairs);
185                    
186                    producer.process(subExchange);
187                    if (aggregationStrategy != null) {
188                        if (result == null) {
189                            result = subExchange;
190                        } else {
191                            result = aggregationStrategy.aggregate(result, subExchange);
192                        }
193                    }
194                    i++;
195                }
196            }
197            if (result != null) {
198                ExchangeHelper.copyResults(exchange, result);
199            }
200        }
201    
202        protected void updateNewExchange(Exchange exchange, int i, List<ProcessorExchangePair> allPairs) {
203            // No updates needed
204        }
205    
206        protected List<ProcessorExchangePair> createProcessorExchangePairs(
207            Exchange exchange) {
208            List<ProcessorExchangePair> result = new ArrayList<ProcessorExchangePair>(processors.size());
209            Processor[] processorsArray = processors.toArray(new Processor[0]);
210            for (int i = 0; i < processorsArray.length; i++) {
211                result.add(new ProcessorExchangePair(processorsArray[i], exchange.copy()));
212            }
213            return result;
214        }
215    
216        protected void doStop() throws Exception {
217            shutdown.set(true);
218            if (executor != null) {
219                executor.shutdown();
220                executor.awaitTermination(0, TimeUnit.SECONDS);
221            }
222            ServiceHelper.stopServices(processors);
223        }
224    
225        protected void doStart() throws Exception {
226            shutdown.set(false);
227            if (executor != null) {
228                executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
229                    public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
230                        ProcessCall call = (ProcessCall)runnable;
231                        call.exchange.setException(new RejectedExecutionException());
232                        call.callback.done(false);
233                    }
234                });
235            }
236            ServiceHelper.startServices(processors);
237        }
238    
239        /**
240         * Returns the producers to multicast to
241         */
242        public Collection<Processor> getProcessors() {
243            return processors;
244        }
245    
246        public AggregationStrategy getAggregationStrategy() {
247            return aggregationStrategy;
248        }
249    }