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 }