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 }