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 }