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     */
017    package org.apache.camel.impl;
018    
019    import java.util.HashMap;
020    import java.util.Map;
021    
022    import org.apache.camel.CamelContext;
023    import org.apache.camel.CamelContextAware;
024    import org.apache.camel.Component;
025    import org.apache.camel.Consumer;
026    import org.apache.camel.Endpoint;
027    import org.apache.camel.EndpointConfiguration;
028    import org.apache.camel.Exchange;
029    import org.apache.camel.ExchangePattern;
030    import org.apache.camel.PollingConsumer;
031    import org.apache.camel.ResolveEndpointFailedException;
032    import org.apache.camel.spi.HasId;
033    import org.apache.camel.spi.UriParam;
034    import org.apache.camel.support.ServiceSupport;
035    import org.apache.camel.util.EndpointHelper;
036    import org.apache.camel.util.IntrospectionSupport;
037    import org.apache.camel.util.ObjectHelper;
038    import org.apache.camel.util.URISupport;
039    
040    /**
041     * A default endpoint useful for implementation inheritance.
042     * <p/>
043     * Components which leverages <a
044     * href="http://camel.apache.org/asynchronous-routing-engine.html">asynchronous
045     * processing model</a> should check the {@link #isSynchronous()} to determine
046     * if asynchronous processing is allowed. The <tt>synchronous</tt> option on the
047     * endpoint allows Camel end users to dictate whether they want the asynchronous
048     * model or not. The option is default <tt>false</tt> which means asynchronous
049     * processing is allowed.
050     * 
051     * @version
052     */
053    public abstract class DefaultEndpoint extends ServiceSupport implements Endpoint, HasId, CamelContextAware {
054    
055        private String endpointUri;
056        private EndpointConfiguration endpointConfiguration;
057        private CamelContext camelContext;
058        private Component component;
059        @UriParam
060        private ExchangePattern exchangePattern = ExchangePattern.InOnly;
061        // option to allow end user to dictate whether async processing should be
062        // used or not (if possible)
063        @UriParam
064        private boolean synchronous;
065        private final String id = EndpointHelper.createEndpointId();
066        private Map<String, Object> consumerProperties;
067    
068        /**
069         * Constructs a fully-initialized DefaultEndpoint instance. This is the
070         * preferred method of constructing an object from Java code (as opposed to
071         * Spring beans, etc.).
072         * 
073         * @param endpointUri the full URI used to create this endpoint
074         * @param component the component that created this endpoint
075         */
076        protected DefaultEndpoint(String endpointUri, Component component) {
077            this.camelContext = component == null ? null : component.getCamelContext();
078            this.component = component;
079            this.setEndpointUri(endpointUri);
080        }
081    
082        /**
083         * Constructs a DefaultEndpoint instance which has <b>not</b> been created
084         * using a {@link Component}.
085         * <p/>
086         * <b>Note:</b> It is preferred to create endpoints using the associated
087         * component.
088         * 
089         * @param endpointUri the full URI used to create this endpoint
090         * @param camelContext the Camel Context in which this endpoint is operating
091         */
092        @Deprecated
093        protected DefaultEndpoint(String endpointUri, CamelContext camelContext) {
094            this(endpointUri);
095            this.camelContext = camelContext;
096        }
097    
098        /**
099         * Constructs a partially-initialized DefaultEndpoint instance.
100         * <p/>
101         * <b>Note:</b> It is preferred to create endpoints using the associated
102         * component.
103         * 
104         * @param endpointUri the full URI used to create this endpoint
105         */
106        @Deprecated
107        protected DefaultEndpoint(String endpointUri) {
108            this.setEndpointUri(endpointUri);
109        }
110    
111        /**
112         * Constructs a partially-initialized DefaultEndpoint instance. Useful when
113         * creating endpoints manually (e.g., as beans in Spring).
114         * <p/>
115         * Please note that the endpoint URI must be set through properties (or
116         * overriding {@link #createEndpointUri()} if one uses this constructor.
117         * <p/>
118         * <b>Note:</b> It is preferred to create endpoints using the associated
119         * component.
120         */
121        protected DefaultEndpoint() {
122        }
123    
124        public int hashCode() {
125            return getEndpointUri().hashCode() * 37 + 1;
126        }
127    
128        @Override
129        public boolean equals(Object object) {
130            if (object instanceof DefaultEndpoint) {
131                DefaultEndpoint that = (DefaultEndpoint)object;
132                return ObjectHelper.equal(this.getEndpointUri(), that.getEndpointUri());
133            }
134            return false;
135        }
136    
137        @Override
138        public String toString() {
139            return String.format("Endpoint[%s]", URISupport.sanitizeUri(getEndpointUri()));
140        }
141    
142        /**
143         * Returns a unique String ID which can be used for aliasing without having
144         * to use the whole URI which is not unique
145         */
146        public String getId() {
147            return id;
148        }
149    
150        public String getEndpointUri() {
151            if (endpointUri == null) {
152                endpointUri = createEndpointUri();
153                if (endpointUri == null) {
154                    throw new IllegalArgumentException("endpointUri is not specified and " + getClass().getName()
155                        + " does not implement createEndpointUri() to create a default value");
156                }
157            }
158            return endpointUri;
159        }
160    
161        public EndpointConfiguration getEndpointConfiguration() {
162            if (endpointConfiguration == null) {
163                endpointConfiguration = createEndpointConfiguration(getEndpointUri());
164            }
165            return endpointConfiguration;
166        }
167    
168        /**
169         * Sets a custom {@link EndpointConfiguration}
170         *
171         * @param endpointConfiguration a custom endpoint configuration to be used.
172         */
173        public void setEndpointConfiguration(EndpointConfiguration endpointConfiguration) {
174            this.endpointConfiguration = endpointConfiguration;
175        }
176    
177        public String getEndpointKey() {
178            if (isLenientProperties()) {
179                // only use the endpoint uri without parameters as the properties is
180                // lenient
181                String uri = getEndpointUri();
182                if (uri.indexOf('?') != -1) {
183                    return ObjectHelper.before(uri, "?");
184                } else {
185                    return uri;
186                }
187            } else {
188                // use the full endpoint uri
189                return getEndpointUri();
190            }
191        }
192    
193        public CamelContext getCamelContext() {
194            return camelContext;
195        }
196    
197        /**
198         * Returns the component that created this endpoint.
199         * 
200         * @return the component that created this endpoint, or <tt>null</tt> if
201         *         none set
202         */
203        public Component getComponent() {
204            return component;
205        }
206    
207        public void setCamelContext(CamelContext camelContext) {
208            this.camelContext = camelContext;
209        }
210    
211        public PollingConsumer createPollingConsumer() throws Exception {
212            // should not configure consumer
213            return new EventDrivenPollingConsumer(this);
214        }
215    
216        public Exchange createExchange(Exchange exchange) {
217            return exchange.copy();
218        }
219    
220        public Exchange createExchange() {
221            return createExchange(getExchangePattern());
222        }
223    
224        public Exchange createExchange(ExchangePattern pattern) {
225            return new DefaultExchange(this, pattern);
226        }
227    
228        /**
229         * Returns the default exchange pattern to use for createExchange().
230         * 
231         * @see #setExchangePattern(ExchangePattern exchangePattern)
232         */
233        public ExchangePattern getExchangePattern() {
234            return exchangePattern;
235        }
236    
237        /**
238         * Sets the default exchange pattern to use for {@link #createExchange()}.
239         * The default value is {@link ExchangePattern#InOnly}
240         */
241        public void setExchangePattern(ExchangePattern exchangePattern) {
242            this.exchangePattern = exchangePattern;
243        }
244    
245        /**
246         * Returns whether synchronous processing should be strictly used.
247         * 
248         * @see #setSynchronous(boolean synchronous)
249         */
250        public boolean isSynchronous() {
251            return synchronous;
252        }
253    
254        /**
255         * Sets whether synchronous processing should be strictly used, or Camel is
256         * allowed to use asynchronous processing (if supported).
257         * 
258         * @param synchronous <tt>true</tt> to enforce synchronous processing
259         */
260        public void setSynchronous(boolean synchronous) {
261            this.synchronous = synchronous;
262        }
263    
264        public void configureProperties(Map<String, Object> options) {
265            Map<String, Object> consumerProperties = IntrospectionSupport.extractProperties(options, "consumer.");
266            if (consumerProperties != null && !consumerProperties.isEmpty()) {
267                setConsumerProperties(consumerProperties);
268            }
269        }
270    
271        /**
272         * Sets the bean properties on the given bean.
273         * <p/>
274         * This is the same logical implementation as {@link DefaultComponent#setProperties(Object, java.util.Map)}
275         *
276         * @param bean  the bean
277         * @param parameters  properties to set
278         */
279        protected void setProperties(Object bean, Map<String, Object> parameters) throws Exception {
280            // set reference properties first as they use # syntax that fools the regular properties setter
281            EndpointHelper.setReferenceProperties(getCamelContext(), bean, parameters);
282            EndpointHelper.setProperties(getCamelContext(), bean, parameters);
283        }
284    
285        /**
286         * A factory method to lazily create the endpointUri if none is specified
287         */
288        protected String createEndpointUri() {
289            return null;
290        }
291    
292        /**
293         * A factory method to lazily create the endpoint configuration if none is specified
294         */
295        protected EndpointConfiguration createEndpointConfiguration(String uri) {
296            // using this factory method to be backwards compatible with the old code
297            if (getComponent() != null) {
298                // prefer to use component endpoint configuration
299                try {
300                    return getComponent().createConfiguration(uri);
301                } catch (Exception e) {
302                    throw ObjectHelper.wrapRuntimeCamelException(e);
303                }
304            } else if (getCamelContext() != null) {
305                // fallback and use a mapped endpoint configuration
306                return new MappedEndpointConfiguration(getCamelContext(), uri);
307            }
308            // not configuration possible
309            return null;
310        }
311    
312        /**
313         * Sets the endpointUri if it has not been specified yet via some kind of
314         * dependency injection mechanism. This allows dependency injection
315         * frameworks such as Spring or Guice to set the default endpoint URI in
316         * cases where it has not been explicitly configured using the name/context
317         * in which an Endpoint is created.
318         */
319        public void setEndpointUriIfNotSpecified(String value) {
320            if (endpointUri == null) {
321                setEndpointUri(value);
322            }
323        }
324    
325        /**
326         * Sets the URI that created this endpoint.
327         */
328        protected void setEndpointUri(String endpointUri) {
329            this.endpointUri = endpointUri;
330        }
331    
332        public boolean isLenientProperties() {
333            // default should be false for most components
334            return false;
335        }
336    
337        public Map<String, Object> getConsumerProperties() {
338            if (consumerProperties == null) {
339                // must create empty if none exists
340                consumerProperties = new HashMap<String, Object>();
341            }
342            return consumerProperties;
343        }
344    
345        public void setConsumerProperties(Map<String, Object> consumerProperties) {
346            // append consumer properties
347            if (consumerProperties != null && !consumerProperties.isEmpty()) {
348                if (this.consumerProperties == null) {
349                    this.consumerProperties = new HashMap<String, Object>(consumerProperties);
350                } else {
351                    this.consumerProperties.putAll(consumerProperties);
352                }
353            }
354        }
355    
356        protected void configureConsumer(Consumer consumer) throws Exception {
357            if (consumerProperties != null) {
358                // use a defensive copy of the consumer properties as the methods below will remove the used properties
359                // and in case we restart routes, we need access to the original consumer properties again
360                Map<String, Object> copy = new HashMap<String, Object>(consumerProperties);
361    
362                // set reference properties first as they use # syntax that fools the regular properties setter
363                EndpointHelper.setReferenceProperties(getCamelContext(), consumer, copy);
364                EndpointHelper.setProperties(getCamelContext(), consumer, copy);
365    
366                // special consumer.bridgeErrorHandler option
367                Object bridge = copy.remove("bridgeErrorHandler");
368                if (bridge != null && "true".equals(bridge)) {
369                    if (consumer instanceof DefaultConsumer) {
370                        DefaultConsumer defaultConsumer = (DefaultConsumer) consumer;
371                        defaultConsumer.setExceptionHandler(new BridgeExceptionHandlerToErrorHandler(defaultConsumer));
372                    } else {
373                        throw new IllegalArgumentException("Option consumer.bridgeErrorHandler is only supported by endpoints,"
374                                + " having their consumer extend DefaultConsumer. The consumer is a " + consumer.getClass().getName() + " class.");
375                    }
376                }
377    
378                if (!this.isLenientProperties() && copy.size() > 0) {
379                    throw new ResolveEndpointFailedException(this.getEndpointUri(), "There are " + copy.size()
380                        + " parameters that couldn't be set on the endpoint consumer."
381                        + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
382                        + " Unknown consumer parameters=[" + copy + "]");
383                }
384            }
385        }
386    
387        protected void configurePollingConsumer(PollingConsumer consumer) throws Exception {
388            configureConsumer(consumer);
389        }
390    
391        @Override
392        protected void doStart() throws Exception {
393            // noop
394        }
395    
396        @Override
397        protected void doStop() throws Exception {
398            // noop
399        }
400    }