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 }