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