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 */ 017package org.apache.camel.processor; 018 019import java.util.Iterator; 020import java.util.concurrent.ExecutorService; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.AsyncProcessor; 024import org.apache.camel.CamelContext; 025import org.apache.camel.Endpoint; 026import org.apache.camel.Exchange; 027import org.apache.camel.Expression; 028import org.apache.camel.Processor; 029import org.apache.camel.impl.EmptyProducerCache; 030import org.apache.camel.impl.ProducerCache; 031import org.apache.camel.processor.aggregate.AggregationStrategy; 032import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy; 033import org.apache.camel.spi.EndpointUtilizationStatistics; 034import org.apache.camel.spi.IdAware; 035import org.apache.camel.support.ServiceSupport; 036import org.apache.camel.util.AsyncProcessorHelper; 037import org.apache.camel.util.ExchangeHelper; 038import org.apache.camel.util.ObjectHelper; 039import org.apache.camel.util.ServiceHelper; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import static org.apache.camel.util.ObjectHelper.notNull; 044 045/** 046 * Implements a dynamic <a 047 * href="http://camel.apache.org/recipient-list.html">Recipient List</a> 048 * pattern where the list of actual endpoints to send a message exchange to are 049 * dependent on some dynamic expression. 050 * 051 * @version 052 */ 053public class RecipientList extends ServiceSupport implements AsyncProcessor, IdAware { 054 055 private static final Logger LOG = LoggerFactory.getLogger(RecipientList.class); 056 private static final String IGNORE_DELIMITER_MARKER = "false"; 057 private final CamelContext camelContext; 058 private String id; 059 private ProducerCache producerCache; 060 private Expression expression; 061 private final String delimiter; 062 private boolean parallelProcessing; 063 private boolean parallelAggregate; 064 private boolean stopOnAggregateException; 065 private boolean stopOnException; 066 private boolean ignoreInvalidEndpoints; 067 private boolean streaming; 068 private long timeout; 069 private int cacheSize; 070 private Processor onPrepare; 071 private boolean shareUnitOfWork; 072 private ExecutorService executorService; 073 private boolean shutdownExecutorService; 074 private ExecutorService aggregateExecutorService; 075 private AggregationStrategy aggregationStrategy = new UseLatestAggregationStrategy(); 076 077 public RecipientList(CamelContext camelContext) { 078 // use comma by default as delimiter 079 this(camelContext, ","); 080 } 081 082 public RecipientList(CamelContext camelContext, String delimiter) { 083 notNull(camelContext, "camelContext"); 084 ObjectHelper.notEmpty(delimiter, "delimiter"); 085 this.camelContext = camelContext; 086 this.delimiter = delimiter; 087 } 088 089 public RecipientList(CamelContext camelContext, Expression expression) { 090 // use comma by default as delimiter 091 this(camelContext, expression, ","); 092 } 093 094 public RecipientList(CamelContext camelContext, Expression expression, String delimiter) { 095 notNull(camelContext, "camelContext"); 096 ObjectHelper.notNull(expression, "expression"); 097 ObjectHelper.notEmpty(delimiter, "delimiter"); 098 this.camelContext = camelContext; 099 this.expression = expression; 100 this.delimiter = delimiter; 101 } 102 103 @Override 104 public String toString() { 105 return "RecipientList[" + (expression != null ? expression : "") + "]"; 106 } 107 108 public String getId() { 109 return id; 110 } 111 112 public void setId(String id) { 113 this.id = id; 114 } 115 116 public void process(Exchange exchange) throws Exception { 117 AsyncProcessorHelper.process(this, exchange); 118 } 119 120 public boolean process(Exchange exchange, AsyncCallback callback) { 121 if (!isStarted()) { 122 throw new IllegalStateException("RecipientList has not been started: " + this); 123 } 124 125 // use the evaluate expression result if exists 126 Object recipientList = exchange.removeProperty(Exchange.EVALUATE_EXPRESSION_RESULT); 127 if (recipientList == null && expression != null) { 128 // fallback and evaluate the expression 129 recipientList = expression.evaluate(exchange, Object.class); 130 } 131 132 return sendToRecipientList(exchange, recipientList, callback); 133 } 134 135 /** 136 * Sends the given exchange to the recipient list 137 */ 138 public boolean sendToRecipientList(Exchange exchange, Object recipientList, AsyncCallback callback) { 139 Iterator<Object> iter; 140 141 if (delimiter != null && delimiter.equalsIgnoreCase(IGNORE_DELIMITER_MARKER)) { 142 iter = ObjectHelper.createIterator(recipientList, null); 143 } else { 144 iter = ObjectHelper.createIterator(recipientList, delimiter); 145 } 146 147 RecipientListProcessor rlp = new RecipientListProcessor(exchange.getContext(), producerCache, iter, getAggregationStrategy(), 148 isParallelProcessing(), getExecutorService(), isShutdownExecutorService(), 149 isStreaming(), isStopOnException(), getTimeout(), getOnPrepare(), isShareUnitOfWork(), isParallelAggregate(), 150 isStopOnAggregateException()) { 151 @Override 152 protected synchronized ExecutorService createAggregateExecutorService(String name) { 153 // use a shared executor service to avoid creating new thread pools 154 if (aggregateExecutorService == null) { 155 aggregateExecutorService = super.createAggregateExecutorService("RecipientList-AggregateTask"); 156 } 157 return aggregateExecutorService; 158 } 159 }; 160 rlp.setIgnoreInvalidEndpoints(isIgnoreInvalidEndpoints()); 161 162 // start the service 163 try { 164 ServiceHelper.startService(rlp); 165 } catch (Exception e) { 166 exchange.setException(e); 167 callback.done(true); 168 return true; 169 } 170 171 // now let the multicast process the exchange 172 return rlp.process(exchange, callback); 173 } 174 175 protected Endpoint resolveEndpoint(Exchange exchange, Object recipient) { 176 // trim strings as end users might have added spaces between separators 177 if (recipient instanceof String) { 178 recipient = ((String)recipient).trim(); 179 } 180 return ExchangeHelper.resolveEndpoint(exchange, recipient); 181 } 182 183 public EndpointUtilizationStatistics getEndpointUtilizationStatistics() { 184 return producerCache.getEndpointUtilizationStatistics(); 185 } 186 187 protected void doStart() throws Exception { 188 if (producerCache == null) { 189 if (cacheSize < 0) { 190 producerCache = new EmptyProducerCache(this, camelContext); 191 LOG.debug("RecipientList {} is not using ProducerCache", this); 192 } else if (cacheSize == 0) { 193 producerCache = new ProducerCache(this, camelContext); 194 LOG.debug("RecipientList {} using ProducerCache with default cache size", this); 195 } else { 196 producerCache = new ProducerCache(this, camelContext, cacheSize); 197 LOG.debug("RecipientList {} using ProducerCache with cacheSize={}", this, cacheSize); 198 } 199 } 200 ServiceHelper.startServices(aggregationStrategy, producerCache); 201 } 202 203 protected void doStop() throws Exception { 204 ServiceHelper.stopServices(producerCache, aggregationStrategy); 205 } 206 207 protected void doShutdown() throws Exception { 208 ServiceHelper.stopAndShutdownServices(producerCache, aggregationStrategy); 209 210 if (shutdownExecutorService && executorService != null) { 211 camelContext.getExecutorServiceManager().shutdownNow(executorService); 212 } 213 } 214 215 public Expression getExpression() { 216 return expression; 217 } 218 219 public String getDelimiter() { 220 return delimiter; 221 } 222 223 public boolean isStreaming() { 224 return streaming; 225 } 226 227 public void setStreaming(boolean streaming) { 228 this.streaming = streaming; 229 } 230 231 public boolean isIgnoreInvalidEndpoints() { 232 return ignoreInvalidEndpoints; 233 } 234 235 public void setIgnoreInvalidEndpoints(boolean ignoreInvalidEndpoints) { 236 this.ignoreInvalidEndpoints = ignoreInvalidEndpoints; 237 } 238 239 public boolean isParallelProcessing() { 240 return parallelProcessing; 241 } 242 243 public void setParallelProcessing(boolean parallelProcessing) { 244 this.parallelProcessing = parallelProcessing; 245 } 246 247 public boolean isParallelAggregate() { 248 return parallelAggregate; 249 } 250 251 public void setParallelAggregate(boolean parallelAggregate) { 252 this.parallelAggregate = parallelAggregate; 253 } 254 255 public boolean isStopOnAggregateException() { 256 return stopOnAggregateException; 257 } 258 259 public void setStopOnAggregateException(boolean stopOnAggregateException) { 260 this.stopOnAggregateException = stopOnAggregateException; 261 } 262 263 public boolean isStopOnException() { 264 return stopOnException; 265 } 266 267 public void setStopOnException(boolean stopOnException) { 268 this.stopOnException = stopOnException; 269 } 270 271 public ExecutorService getExecutorService() { 272 return executorService; 273 } 274 275 public void setExecutorService(ExecutorService executorService) { 276 this.executorService = executorService; 277 } 278 279 public boolean isShutdownExecutorService() { 280 return shutdownExecutorService; 281 } 282 283 public void setShutdownExecutorService(boolean shutdownExecutorService) { 284 this.shutdownExecutorService = shutdownExecutorService; 285 } 286 287 public AggregationStrategy getAggregationStrategy() { 288 return aggregationStrategy; 289 } 290 291 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) { 292 this.aggregationStrategy = aggregationStrategy; 293 } 294 295 public long getTimeout() { 296 return timeout; 297 } 298 299 public void setTimeout(long timeout) { 300 this.timeout = timeout; 301 } 302 303 public Processor getOnPrepare() { 304 return onPrepare; 305 } 306 307 public void setOnPrepare(Processor onPrepare) { 308 this.onPrepare = onPrepare; 309 } 310 311 public boolean isShareUnitOfWork() { 312 return shareUnitOfWork; 313 } 314 315 public void setShareUnitOfWork(boolean shareUnitOfWork) { 316 this.shareUnitOfWork = shareUnitOfWork; 317 } 318 319 public int getCacheSize() { 320 return cacheSize; 321 } 322 323 public void setCacheSize(int cacheSize) { 324 this.cacheSize = cacheSize; 325 } 326}