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