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.io.UnsupportedEncodingException; 020import java.net.MalformedURLException; 021import java.net.URISyntaxException; 022import java.util.ArrayList; 023import java.util.Iterator; 024import java.util.List; 025import java.util.concurrent.ExecutorService; 026 027import org.apache.camel.CamelContext; 028import org.apache.camel.Endpoint; 029import org.apache.camel.Exchange; 030import org.apache.camel.ExchangePattern; 031import org.apache.camel.Processor; 032import org.apache.camel.Producer; 033import org.apache.camel.impl.ProducerCache; 034import org.apache.camel.processor.aggregate.AggregationStrategy; 035import org.apache.camel.spi.RouteContext; 036import org.apache.camel.util.EndpointHelper; 037import org.apache.camel.util.ExchangeHelper; 038import org.apache.camel.util.MessageHelper; 039import org.apache.camel.util.ServiceHelper; 040import org.apache.camel.util.URISupport; 041import org.slf4j.Logger; 042import org.slf4j.LoggerFactory; 043 044/** 045 * Implements a dynamic <a 046 * href="http://camel.apache.org/recipient-list.html">Recipient List</a> 047 * pattern where the list of actual endpoints to send a message exchange to are 048 * dependent on some dynamic expression. 049 * <p/> 050 * This implementation is a specialized {@link org.apache.camel.processor.MulticastProcessor} which is based 051 * on recipient lists. This implementation have to handle the fact the processors is not known at design time 052 * but evaluated at runtime from the dynamic recipient list. Therefore this implementation have to at runtime 053 * lookup endpoints and create producers which should act as the processors for the multicast processors which 054 * runs under the hood. Also this implementation supports the asynchronous routing engine which makes the code 055 * more trickier. 056 * 057 * @version 058 */ 059public class RecipientListProcessor extends MulticastProcessor { 060 061 private static final Logger LOG = LoggerFactory.getLogger(RecipientListProcessor.class); 062 private final Iterator<Object> iter; 063 private boolean ignoreInvalidEndpoints; 064 private ProducerCache producerCache; 065 066 /** 067 * Class that represent each step in the recipient list to do 068 * <p/> 069 * This implementation ensures the provided producer is being released back in the producer cache when 070 * its done using it. 071 */ 072 static final class RecipientProcessorExchangePair implements ProcessorExchangePair { 073 private final int index; 074 private final Endpoint endpoint; 075 private final Producer producer; 076 private Processor prepared; 077 private final Exchange exchange; 078 private final ProducerCache producerCache; 079 private final ExchangePattern pattern; 080 private volatile ExchangePattern originalPattern; 081 082 private RecipientProcessorExchangePair(int index, ProducerCache producerCache, Endpoint endpoint, Producer producer, 083 Processor prepared, Exchange exchange, ExchangePattern pattern) { 084 this.index = index; 085 this.producerCache = producerCache; 086 this.endpoint = endpoint; 087 this.producer = producer; 088 this.prepared = prepared; 089 this.exchange = exchange; 090 this.pattern = pattern; 091 } 092 093 public int getIndex() { 094 return index; 095 } 096 097 public Exchange getExchange() { 098 return exchange; 099 } 100 101 public Producer getProducer() { 102 return producer; 103 } 104 105 public Processor getProcessor() { 106 return prepared; 107 } 108 109 public void begin() { 110 // we have already acquired and prepare the producer 111 LOG.trace("RecipientProcessorExchangePair #{} begin: {}", index, exchange); 112 exchange.setProperty(Exchange.RECIPIENT_LIST_ENDPOINT, endpoint.getEndpointUri()); 113 // ensure stream caching is reset 114 MessageHelper.resetStreamCache(exchange.getIn()); 115 // if the MEP on the endpoint is different then 116 if (pattern != null) { 117 originalPattern = exchange.getPattern(); 118 LOG.trace("Using exchangePattern: {} on exchange: {}", pattern, exchange); 119 exchange.setPattern(pattern); 120 } 121 } 122 123 public void done() { 124 LOG.trace("RecipientProcessorExchangePair #{} done: {}", index, exchange); 125 try { 126 // preserve original MEP 127 if (originalPattern != null) { 128 exchange.setPattern(originalPattern); 129 } 130 // when we are done we should release back in pool 131 producerCache.releaseProducer(endpoint, producer); 132 } catch (Exception e) { 133 if (LOG.isDebugEnabled()) { 134 LOG.debug("Error releasing producer: " + producer + ". This exception will be ignored.", e); 135 } 136 } 137 } 138 139 } 140 141 public RecipientListProcessor(CamelContext camelContext, ProducerCache producerCache, Iterator<Object> iter) { 142 super(camelContext, null); 143 this.producerCache = producerCache; 144 this.iter = iter; 145 } 146 147 public RecipientListProcessor(CamelContext camelContext, ProducerCache producerCache, Iterator<Object> iter, AggregationStrategy aggregationStrategy) { 148 super(camelContext, null, aggregationStrategy); 149 this.producerCache = producerCache; 150 this.iter = iter; 151 } 152 153 @Deprecated 154 public RecipientListProcessor(CamelContext camelContext, ProducerCache producerCache, Iterator<Object> iter, AggregationStrategy aggregationStrategy, 155 boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, 156 boolean streaming, boolean stopOnException, long timeout, Processor onPrepare, boolean shareUnitOfWork) { 157 super(camelContext, null, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService, 158 streaming, stopOnException, timeout, onPrepare, shareUnitOfWork, false); 159 this.producerCache = producerCache; 160 this.iter = iter; 161 } 162 163 public RecipientListProcessor(CamelContext camelContext, ProducerCache producerCache, Iterator<Object> iter, AggregationStrategy aggregationStrategy, 164 boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, 165 boolean streaming, boolean stopOnException, long timeout, Processor onPrepare, boolean shareUnitOfWork, boolean parallelAggregate) { 166 this(camelContext, producerCache, iter, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService, streaming, stopOnException, timeout, onPrepare, 167 shareUnitOfWork, parallelAggregate, false); 168 } 169 170 public RecipientListProcessor(CamelContext camelContext, ProducerCache producerCache, Iterator<Object> iter, AggregationStrategy aggregationStrategy, 171 boolean parallelProcessing, ExecutorService executorService, boolean shutdownExecutorService, boolean streaming, boolean stopOnException, 172 long timeout, Processor onPrepare, boolean shareUnitOfWork, boolean parallelAggregate, boolean stopOnAggregateException) { 173 super(camelContext, null, aggregationStrategy, parallelProcessing, executorService, shutdownExecutorService, streaming, stopOnException, timeout, onPrepare, 174 shareUnitOfWork, parallelAggregate, stopOnAggregateException); 175 this.producerCache = producerCache; 176 this.iter = iter; 177 } 178 179 public boolean isIgnoreInvalidEndpoints() { 180 return ignoreInvalidEndpoints; 181 } 182 183 public void setIgnoreInvalidEndpoints(boolean ignoreInvalidEndpoints) { 184 this.ignoreInvalidEndpoints = ignoreInvalidEndpoints; 185 } 186 187 @Override 188 protected Iterable<ProcessorExchangePair> createProcessorExchangePairs(Exchange exchange) throws Exception { 189 // here we iterate the recipient lists and create the exchange pair for each of those 190 List<ProcessorExchangePair> result = new ArrayList<>(); 191 192 // at first we must lookup the endpoint and acquire the producer which can send to the endpoint 193 int index = 0; 194 while (iter.hasNext()) { 195 Object recipient = iter.next(); 196 Endpoint endpoint; 197 Producer producer; 198 ExchangePattern pattern; 199 try { 200 endpoint = resolveEndpoint(exchange, recipient); 201 pattern = resolveExchangePattern(recipient); 202 producer = producerCache.acquireProducer(endpoint); 203 } catch (Exception e) { 204 if (isIgnoreInvalidEndpoints()) { 205 if (LOG.isDebugEnabled()) { 206 LOG.debug("Endpoint uri is invalid: " + recipient + ". This exception will be ignored.", e); 207 } 208 continue; 209 } else { 210 // failure so break out 211 throw e; 212 } 213 } 214 215 // then create the exchange pair 216 result.add(createProcessorExchangePair(index++, endpoint, producer, exchange, pattern)); 217 } 218 219 return result; 220 } 221 222 /** 223 * This logic is similar to MulticastProcessor but we have to return a RecipientProcessorExchangePair instead 224 */ 225 protected ProcessorExchangePair createProcessorExchangePair(int index, Endpoint endpoint, Producer producer, Exchange exchange, ExchangePattern pattern) { 226 Processor prepared = producer; 227 228 // copy exchange, and do not share the unit of work 229 Exchange copy = ExchangeHelper.createCorrelatedCopy(exchange, false); 230 231 // if we share unit of work, we need to prepare the child exchange 232 if (isShareUnitOfWork()) { 233 prepareSharedUnitOfWork(copy, exchange); 234 } 235 236 // set property which endpoint we send to 237 setToEndpoint(copy, prepared); 238 239 // rework error handling to support fine grained error handling 240 RouteContext routeContext = exchange.getUnitOfWork() != null ? exchange.getUnitOfWork().getRouteContext() : null; 241 prepared = createErrorHandler(routeContext, copy, prepared); 242 243 // invoke on prepare on the exchange if specified 244 if (onPrepare != null) { 245 try { 246 onPrepare.process(copy); 247 } catch (Exception e) { 248 copy.setException(e); 249 } 250 } 251 252 // and create the pair 253 return new RecipientProcessorExchangePair(index, producerCache, endpoint, producer, prepared, copy, pattern); 254 } 255 256 protected static Endpoint resolveEndpoint(Exchange exchange, Object recipient) { 257 // trim strings as end users might have added spaces between separators 258 if (recipient instanceof String) { 259 recipient = ((String) recipient).trim(); 260 } 261 return ExchangeHelper.resolveEndpoint(exchange, recipient); 262 } 263 264 protected ExchangePattern resolveExchangePattern(Object recipient) throws UnsupportedEncodingException, URISyntaxException, MalformedURLException { 265 // trim strings as end users might have added spaces between separators 266 if (recipient instanceof String) { 267 String s = ((String) recipient).trim(); 268 // see if exchangePattern is a parameter in the url 269 s = URISupport.normalizeUri(s); 270 return EndpointHelper.resolveExchangePatternFromUrl(s); 271 } 272 return null; 273 } 274 275 protected void doStart() throws Exception { 276 super.doStart(); 277 if (producerCache == null) { 278 producerCache = new ProducerCache(this, getCamelContext()); 279 } 280 ServiceHelper.startService(producerCache); 281 } 282 283 protected void doStop() throws Exception { 284 ServiceHelper.stopService(producerCache); 285 super.doStop(); 286 } 287 288 protected void doShutdown() throws Exception { 289 ServiceHelper.stopAndShutdownService(producerCache); 290 super.doShutdown(); 291 } 292 293 @Override 294 public String toString() { 295 return "RecipientList"; 296 } 297 298 @Override 299 public String getTraceLabel() { 300 return "recipientList"; 301 } 302}