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