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}