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