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