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.impl;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import org.apache.camel.CamelContext;
023import org.apache.camel.CamelContextAware;
024import org.apache.camel.Component;
025import org.apache.camel.Consumer;
026import org.apache.camel.Endpoint;
027import org.apache.camel.EndpointConfiguration;
028import org.apache.camel.Exchange;
029import org.apache.camel.ExchangePattern;
030import org.apache.camel.PollingConsumer;
031import org.apache.camel.ResolveEndpointFailedException;
032import org.apache.camel.spi.ExceptionHandler;
033import org.apache.camel.spi.HasId;
034import org.apache.camel.spi.UriParam;
035import org.apache.camel.support.ServiceSupport;
036import org.apache.camel.util.EndpointHelper;
037import org.apache.camel.util.IntrospectionSupport;
038import org.apache.camel.util.ObjectHelper;
039import org.apache.camel.util.URISupport;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 * A default endpoint useful for implementation inheritance.
045 * <p/>
046 * Components which leverages <a
047 * href="http://camel.apache.org/asynchronous-routing-engine.html">asynchronous
048 * processing model</a> should check the {@link #isSynchronous()} to determine
049 * if asynchronous processing is allowed. The <tt>synchronous</tt> option on the
050 * endpoint allows Camel end users to dictate whether they want the asynchronous
051 * model or not. The option is default <tt>false</tt> which means asynchronous
052 * processing is allowed.
053 * 
054 * @version
055 */
056public abstract class DefaultEndpoint extends ServiceSupport implements Endpoint, HasId, CamelContextAware {
057
058    private static final Logger LOG = LoggerFactory.getLogger(DefaultEndpoint.class);
059    private final String id = EndpointHelper.createEndpointId();
060    private transient String endpointUriToString;
061    private String endpointUri;
062    private EndpointConfiguration endpointConfiguration;
063    private CamelContext camelContext;
064    private Component component;
065    @UriParam(label = "consumer", optionalPrefix = "consumer.", description = "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while"
066                    + " the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler."
067                    + " By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored.")
068    private boolean bridgeErrorHandler;
069    @UriParam(label = "consumer,advanced", optionalPrefix = "consumer.", description = "To let the consumer use a custom ExceptionHandler."
070            + " Notice if the option bridgeErrorHandler is enabled then this options is not in use."
071            + " By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored.")
072    private ExceptionHandler exceptionHandler;
073    @UriParam(label = "consumer,advanced",
074            description = "Sets the exchange pattern when the consumer creates an exchange.")
075    // no default value set on @UriParam as the MEP is sometimes InOnly or InOut depending on the component in use
076    private ExchangePattern exchangePattern = ExchangePattern.InOnly;
077    // option to allow end user to dictate whether async processing should be
078    // used or not (if possible)
079    @UriParam(defaultValue = "false", label = "advanced",
080            description = "Sets whether synchronous processing should be strictly used, or Camel is allowed to use asynchronous processing (if supported).")
081    private boolean synchronous;
082    // these options are not really in use any option related to the consumer has a specific option on the endpoint
083    // and consumerProperties was added from the very start of Camel.
084    private Map<String, Object> consumerProperties;
085    // pooling consumer options only related to EventDrivenPollingConsumer which are very seldom in use
086    // so lets not expose them in the component docs as it will be included in every component
087    private int pollingConsumerQueueSize = 1000;
088    private boolean pollingConsumerBlockWhenFull = true;
089    private long pollingConsumerBlockTimeout;
090
091    /**
092     * Constructs a fully-initialized DefaultEndpoint instance. This is the
093     * preferred method of constructing an object from Java code (as opposed to
094     * Spring beans, etc.).
095     * 
096     * @param endpointUri the full URI used to create this endpoint
097     * @param component the component that created this endpoint
098     */
099    protected DefaultEndpoint(String endpointUri, Component component) {
100        this.camelContext = component == null ? null : component.getCamelContext();
101        this.component = component;
102        this.setEndpointUri(endpointUri);
103    }
104
105    /**
106     * Constructs a DefaultEndpoint instance which has <b>not</b> been created
107     * using a {@link Component}.
108     * <p/>
109     * <b>Note:</b> It is preferred to create endpoints using the associated
110     * component.
111     * 
112     * @param endpointUri the full URI used to create this endpoint
113     * @param camelContext the Camel Context in which this endpoint is operating
114     */
115    @Deprecated
116    protected DefaultEndpoint(String endpointUri, CamelContext camelContext) {
117        this(endpointUri);
118        this.camelContext = camelContext;
119    }
120
121    /**
122     * Constructs a partially-initialized DefaultEndpoint instance.
123     * <p/>
124     * <b>Note:</b> It is preferred to create endpoints using the associated
125     * component.
126     * 
127     * @param endpointUri the full URI used to create this endpoint
128     */
129    @Deprecated
130    protected DefaultEndpoint(String endpointUri) {
131        this.setEndpointUri(endpointUri);
132    }
133
134    /**
135     * Constructs a partially-initialized DefaultEndpoint instance. Useful when
136     * creating endpoints manually (e.g., as beans in Spring).
137     * <p/>
138     * Please note that the endpoint URI must be set through properties (or
139     * overriding {@link #createEndpointUri()} if one uses this constructor.
140     * <p/>
141     * <b>Note:</b> It is preferred to create endpoints using the associated
142     * component.
143     */
144    protected DefaultEndpoint() {
145    }
146
147    public int hashCode() {
148        return getEndpointUri().hashCode() * 37 + 1;
149    }
150
151    @Override
152    public boolean equals(Object object) {
153        if (object instanceof DefaultEndpoint) {
154            DefaultEndpoint that = (DefaultEndpoint)object;
155            // must also match the same CamelContext in case we compare endpoints from different contexts
156            String thisContextName = this.getCamelContext() != null ? this.getCamelContext().getName() : null;
157            String thatContextName = that.getCamelContext() != null ? that.getCamelContext().getName() : null;
158            return ObjectHelper.equal(this.getEndpointUri(), that.getEndpointUri()) && ObjectHelper.equal(thisContextName, thatContextName);
159        }
160        return false;
161    }
162
163    @Override
164    public String toString() {
165        if (endpointUriToString == null) {
166            String value = null;
167            try {
168                value = getEndpointUri();
169            } catch (RuntimeException e) {
170                // ignore any exception and use null for building the string value
171            }
172            // ensure to sanitize uri so we do not show sensitive information such as passwords
173            endpointUriToString = URISupport.sanitizeUri(value);
174        }
175        return endpointUriToString;
176    }
177
178    /**
179     * Returns a unique String ID which can be used for aliasing without having
180     * to use the whole URI which is not unique
181     */
182    public String getId() {
183        return id;
184    }
185
186    public String getEndpointUri() {
187        if (endpointUri == null) {
188            endpointUri = createEndpointUri();
189            if (endpointUri == null) {
190                throw new IllegalArgumentException("endpointUri is not specified and " + getClass().getName()
191                    + " does not implement createEndpointUri() to create a default value");
192            }
193        }
194        return endpointUri;
195    }
196
197    public EndpointConfiguration getEndpointConfiguration() {
198        if (endpointConfiguration == null) {
199            endpointConfiguration = createEndpointConfiguration(getEndpointUri());
200        }
201        return endpointConfiguration;
202    }
203
204    /**
205     * Sets a custom {@link EndpointConfiguration}
206     *
207     * @param endpointConfiguration a custom endpoint configuration to be used.
208     */
209    @Deprecated
210    public void setEndpointConfiguration(EndpointConfiguration endpointConfiguration) {
211        this.endpointConfiguration = endpointConfiguration;
212    }
213
214    public String getEndpointKey() {
215        if (isLenientProperties()) {
216            // only use the endpoint uri without parameters as the properties is
217            // lenient
218            String uri = getEndpointUri();
219            if (uri.indexOf('?') != -1) {
220                return ObjectHelper.before(uri, "?");
221            } else {
222                return uri;
223            }
224        } else {
225            // use the full endpoint uri
226            return getEndpointUri();
227        }
228    }
229
230    public CamelContext getCamelContext() {
231        return camelContext;
232    }
233
234    /**
235     * Returns the component that created this endpoint.
236     * 
237     * @return the component that created this endpoint, or <tt>null</tt> if
238     *         none set
239     */
240    public Component getComponent() {
241        return component;
242    }
243
244    public void setCamelContext(CamelContext camelContext) {
245        this.camelContext = camelContext;
246    }
247
248    public PollingConsumer createPollingConsumer() throws Exception {
249        // should not call configurePollingConsumer when its EventDrivenPollingConsumer
250        if (LOG.isDebugEnabled()) {
251            LOG.debug("Creating EventDrivenPollingConsumer with queueSize: {} blockWhenFull: {} blockTimeout: {}",
252                    new Object[]{getPollingConsumerQueueSize(), isPollingConsumerBlockWhenFull(), getPollingConsumerBlockTimeout()});
253        }
254        EventDrivenPollingConsumer consumer = new EventDrivenPollingConsumer(this, getPollingConsumerQueueSize());
255        consumer.setBlockWhenFull(isPollingConsumerBlockWhenFull());
256        consumer.setBlockTimeout(getPollingConsumerBlockTimeout());
257        return consumer;
258    }
259
260    public Exchange createExchange(Exchange exchange) {
261        return exchange.copy();
262    }
263
264    public Exchange createExchange() {
265        return createExchange(getExchangePattern());
266    }
267
268    public Exchange createExchange(ExchangePattern pattern) {
269        return new DefaultExchange(this, pattern);
270    }
271
272    /**
273     * Returns the default exchange pattern to use when creating an exchange.
274     */
275    public ExchangePattern getExchangePattern() {
276        return exchangePattern;
277    }
278
279    /**
280     * Sets the default exchange pattern when creating an exchange.
281     */
282    public void setExchangePattern(ExchangePattern exchangePattern) {
283        this.exchangePattern = exchangePattern;
284    }
285
286    /**
287     * Returns whether synchronous processing should be strictly used.
288     */
289    public boolean isSynchronous() {
290        return synchronous;
291    }
292
293    /**
294     * Sets whether synchronous processing should be strictly used, or Camel is
295     * allowed to use asynchronous processing (if supported).
296     * 
297     * @param synchronous <tt>true</tt> to enforce synchronous processing
298     */
299    public void setSynchronous(boolean synchronous) {
300        this.synchronous = synchronous;
301    }
302
303    public boolean isBridgeErrorHandler() {
304        return bridgeErrorHandler;
305    }
306
307    /**
308     * Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions occurred while
309     * the consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and
310     * handled by the routing Error Handler.
311     * <p/>
312     * By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions,
313     * that will be logged at WARN/ERROR level and ignored.
314     */
315    public void setBridgeErrorHandler(boolean bridgeErrorHandler) {
316        this.bridgeErrorHandler = bridgeErrorHandler;
317    }
318
319    public ExceptionHandler getExceptionHandler() {
320        return exceptionHandler;
321    }
322
323    /**
324     * To let the consumer use a custom ExceptionHandler.
325     + Notice if the option bridgeErrorHandler is enabled then this options is not in use.
326     + By default the consumer will deal with exceptions, that will be logged at WARN/ERROR level and ignored.
327     */
328    public void setExceptionHandler(ExceptionHandler exceptionHandler) {
329        this.exceptionHandler = exceptionHandler;
330    }
331
332    /**
333     * Gets the {@link org.apache.camel.PollingConsumer} queue size, when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
334     * is being used. Notice some Camel components may have their own implementation of {@link org.apache.camel.PollingConsumer} and
335     * therefore not using the default {@link org.apache.camel.impl.EventDrivenPollingConsumer} implementation.
336     * <p/>
337     * The default value is <tt>1000</tt>
338     */
339    public int getPollingConsumerQueueSize() {
340        return pollingConsumerQueueSize;
341    }
342
343    /**
344     * Sets the {@link org.apache.camel.PollingConsumer} queue size, when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
345     * is being used. Notice some Camel components may have their own implementation of {@link org.apache.camel.PollingConsumer} and
346     * therefore not using the default {@link org.apache.camel.impl.EventDrivenPollingConsumer} implementation.
347     * <p/>
348     * The default value is <tt>1000</tt>
349     */
350    public void setPollingConsumerQueueSize(int pollingConsumerQueueSize) {
351        this.pollingConsumerQueueSize = pollingConsumerQueueSize;
352    }
353
354    /**
355     * Whether to block when adding to the internal queue off when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
356     * is being used. Notice some Camel components may have their own implementation of {@link org.apache.camel.PollingConsumer} and
357     * therefore not using the default {@link org.apache.camel.impl.EventDrivenPollingConsumer} implementation.
358     * <p/>
359     * Setting this option to <tt>false</tt>, will result in an {@link java.lang.IllegalStateException} being thrown
360     * when trying to add to the queue, and its full.
361     * <p/>
362     * The default value is <tt>true</tt> which will block the producer queue until the queue has space.
363     */
364    public boolean isPollingConsumerBlockWhenFull() {
365        return pollingConsumerBlockWhenFull;
366    }
367
368    /**
369     * Set whether to block when adding to the internal queue off when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
370     * is being used. Notice some Camel components may have their own implementation of {@link org.apache.camel.PollingConsumer} and
371     * therefore not using the default {@link org.apache.camel.impl.EventDrivenPollingConsumer} implementation.
372     * <p/>
373     * Setting this option to <tt>false</tt>, will result in an {@link java.lang.IllegalStateException} being thrown
374     * when trying to add to the queue, and its full.
375     * <p/>
376     * The default value is <tt>true</tt> which will block the producer queue until the queue has space.
377     */
378    public void setPollingConsumerBlockWhenFull(boolean pollingConsumerBlockWhenFull) {
379        this.pollingConsumerBlockWhenFull = pollingConsumerBlockWhenFull;
380    }
381
382    /**
383     * Sets the timeout in millis to use when adding to the internal queue off when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
384     * is being used.
385     *
386     * @see #setPollingConsumerBlockWhenFull(boolean)
387     */
388    public long getPollingConsumerBlockTimeout() {
389        return pollingConsumerBlockTimeout;
390    }
391
392    /**
393     * Sets the timeout in millis to use when adding to the internal queue off when {@link org.apache.camel.impl.EventDrivenPollingConsumer}
394     * is being used.
395     *
396     * @see #setPollingConsumerBlockWhenFull(boolean)
397     */
398    public void setPollingConsumerBlockTimeout(long pollingConsumerBlockTimeout) {
399        this.pollingConsumerBlockTimeout = pollingConsumerBlockTimeout;
400    }
401
402    public void configureProperties(Map<String, Object> options) {
403        Map<String, Object> consumerProperties = IntrospectionSupport.extractProperties(options, "consumer.");
404        if (consumerProperties != null && !consumerProperties.isEmpty()) {
405            setConsumerProperties(consumerProperties);
406        }
407    }
408
409    /**
410     * Sets the bean properties on the given bean.
411     * <p/>
412     * This is the same logical implementation as {@link DefaultComponent#setProperties(Object, java.util.Map)}
413     *
414     * @param bean  the bean
415     * @param parameters  properties to set
416     */
417    protected void setProperties(Object bean, Map<String, Object> parameters) throws Exception {
418        // set reference properties first as they use # syntax that fools the regular properties setter
419        EndpointHelper.setReferenceProperties(getCamelContext(), bean, parameters);
420        EndpointHelper.setProperties(getCamelContext(), bean, parameters);
421    }
422
423    /**
424     * A factory method to lazily create the endpointUri if none is specified
425     */
426    protected String createEndpointUri() {
427        return null;
428    }
429
430    /**
431     * A factory method to lazily create the endpoint configuration if none is specified
432     */
433    @Deprecated
434    protected EndpointConfiguration createEndpointConfiguration(String uri) {
435        // using this factory method to be backwards compatible with the old code
436        if (getComponent() != null) {
437            // prefer to use component endpoint configuration
438            try {
439                return getComponent().createConfiguration(uri);
440            } catch (Exception e) {
441                throw ObjectHelper.wrapRuntimeCamelException(e);
442            }
443        } else if (getCamelContext() != null) {
444            // fallback and use a mapped endpoint configuration
445            return new MappedEndpointConfiguration(getCamelContext(), uri);
446        }
447        // not configuration possible
448        return null;
449    }
450
451    /**
452     * Sets the endpointUri if it has not been specified yet via some kind of
453     * dependency injection mechanism. This allows dependency injection
454     * frameworks such as Spring or Guice to set the default endpoint URI in
455     * cases where it has not been explicitly configured using the name/context
456     * in which an Endpoint is created.
457     */
458    public void setEndpointUriIfNotSpecified(String value) {
459        if (endpointUri == null) {
460            setEndpointUri(value);
461        }
462    }
463
464    /**
465     * Sets the URI that created this endpoint.
466     */
467    protected void setEndpointUri(String endpointUri) {
468        this.endpointUri = endpointUri;
469    }
470
471    public boolean isLenientProperties() {
472        // default should be false for most components
473        return false;
474    }
475
476    public Map<String, Object> getConsumerProperties() {
477        if (consumerProperties == null) {
478            // must create empty if none exists
479            consumerProperties = new HashMap<String, Object>();
480        }
481        return consumerProperties;
482    }
483
484    public void setConsumerProperties(Map<String, Object> consumerProperties) {
485        // append consumer properties
486        if (consumerProperties != null && !consumerProperties.isEmpty()) {
487            if (this.consumerProperties == null) {
488                this.consumerProperties = new HashMap<String, Object>(consumerProperties);
489            } else {
490                this.consumerProperties.putAll(consumerProperties);
491            }
492        }
493    }
494
495    protected void configureConsumer(Consumer consumer) throws Exception {
496        // inject CamelContext
497        if (consumer instanceof CamelContextAware) {
498            ((CamelContextAware) consumer).setCamelContext(getCamelContext());
499        }
500
501        if (consumerProperties != null) {
502            // use a defensive copy of the consumer properties as the methods below will remove the used properties
503            // and in case we restart routes, we need access to the original consumer properties again
504            Map<String, Object> copy = new HashMap<String, Object>(consumerProperties);
505
506            // set reference properties first as they use # syntax that fools the regular properties setter
507            EndpointHelper.setReferenceProperties(getCamelContext(), consumer, copy);
508            EndpointHelper.setProperties(getCamelContext(), consumer, copy);
509
510            // special consumer.bridgeErrorHandler option
511            Object bridge = copy.remove("bridgeErrorHandler");
512            if (bridge != null && "true".equals(bridge)) {
513                if (consumer instanceof DefaultConsumer) {
514                    DefaultConsumer defaultConsumer = (DefaultConsumer) consumer;
515                    defaultConsumer.setExceptionHandler(new BridgeExceptionHandlerToErrorHandler(defaultConsumer));
516                } else {
517                    throw new IllegalArgumentException("Option consumer.bridgeErrorHandler is only supported by endpoints,"
518                            + " having their consumer extend DefaultConsumer. The consumer is a " + consumer.getClass().getName() + " class.");
519                }
520            }
521
522            if (!this.isLenientProperties() && copy.size() > 0) {
523                throw new ResolveEndpointFailedException(this.getEndpointUri(), "There are " + copy.size()
524                    + " parameters that couldn't be set on the endpoint consumer."
525                    + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
526                    + " Unknown consumer parameters=[" + copy + "]");
527            }
528        }
529    }
530
531    protected void configurePollingConsumer(PollingConsumer consumer) throws Exception {
532        configureConsumer(consumer);
533    }
534
535    @Override
536    protected void doStart() throws Exception {
537        // the bridgeErrorHandler/exceptionHandler was originally configured with consumer. prefix, such as consumer.bridgeErrorHandler=true
538        // so if they have been configured on the endpoint then map to the old naming style
539        if (bridgeErrorHandler) {
540            getConsumerProperties().put("bridgeErrorHandler", "true");
541        }
542        if (exceptionHandler != null) {
543            getConsumerProperties().put("exceptionHandler", exceptionHandler);
544        }
545    }
546
547    @Override
548    protected void doStop() throws Exception {
549        // noop
550    }
551}