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.net.URI;
020    import java.util.ArrayList;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.concurrent.ExecutorService;
024    import java.util.concurrent.ScheduledExecutorService;
025    
026    import org.apache.camel.CamelContext;
027    import org.apache.camel.Component;
028    import org.apache.camel.Endpoint;
029    import org.apache.camel.ResolveEndpointFailedException;
030    import org.apache.camel.util.CamelContextHelper;
031    import org.apache.camel.util.EndpointHelper;
032    import org.apache.camel.util.IntrospectionSupport;
033    import org.apache.camel.util.ObjectHelper;
034    import org.apache.camel.util.URISupport;
035    import org.apache.camel.util.UnsafeUriCharactersEncoder;
036    import org.apache.camel.util.concurrent.ExecutorServiceHelper;
037    import org.apache.commons.logging.Log;
038    import org.apache.commons.logging.LogFactory;
039    
040    /**
041     * Default component to use for base for components implementations.
042     *
043     * @version $Revision: 17264 $
044     */
045    public abstract class DefaultComponent extends ServiceSupport implements Component {
046        private static final transient Log LOG = LogFactory.getLog(DefaultComponent.class);
047    
048        private static final int DEFAULT_THREADPOOL_SIZE = 10;
049        private CamelContext camelContext;
050        private ExecutorService executorService;
051    
052        public DefaultComponent() {
053        }
054    
055        public DefaultComponent(CamelContext context) {
056            this.camelContext = context;
057        }
058    
059        public Endpoint createEndpoint(String uri) throws Exception {
060            ObjectHelper.notNull(getCamelContext(), "camelContext");
061            //encode URI string to the unsafe URI characters
062            URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
063            String path = u.getSchemeSpecificPart();
064    
065            // lets trim off any query arguments
066            if (path.startsWith("//")) {
067                path = path.substring(2);
068            }
069            int idx = path.indexOf('?');
070            if (idx > 0) {
071                path = path.substring(0, idx);
072            }
073            Map<String, Object> parameters = URISupport.parseParameters(u);
074    
075            validateURI(uri, path, parameters);
076    
077            if (LOG.isDebugEnabled()) {
078                LOG.debug("Creating endpoint uri=[" + uri + "], path=[" + path + "], parameters=[" + parameters + "]");
079            }
080            Endpoint endpoint = createEndpoint(uri, path, parameters);
081            if (endpoint == null) {
082                return null;
083            }
084    
085            if (parameters != null) {
086                endpoint.configureProperties(parameters);
087                if (useIntrospectionOnEndpoint()) {
088                    setProperties(endpoint, parameters);
089                }
090    
091                // if endpoint is strict (not lenient) and we have unknown parameters configured then
092                // fail if there are parameters that could not be set, then they are probably misspell or not supported at all
093                if (!endpoint.isLenientProperties()) {
094                    validateParameters(uri, parameters, null);
095                }
096            }
097    
098            afterConfiguration(uri, path, endpoint, parameters);
099            return endpoint;
100        }
101    
102        /**
103         * Strategy to do post configuration logic.
104         * <p/>
105         * Can be used to construct an URI based on the remaining parameters. For example the parameters that configures
106         * the endpoint have been removed from the parameters which leaves only the additional parameters left.
107         *
108         * @param endpoint the created endpoint
109         * @param parameters the remaining parameters after the endpoint has been created and parsed the parameters
110         * @throws Exception can be thrown to indicate error creating the endpoint
111         */
112        protected void afterConfiguration(String uri, String remaining, Endpoint endpoint, Map<String, Object> parameters) throws Exception {
113            // noop
114        }
115    
116        /**
117         * Strategy for validation of parameters, that was not able to be resolved to any endpoint options.
118         *
119         * @param uri          the uri - the uri the end user provided untouched
120         * @param parameters   the parameters, an empty map if no parameters given
121         * @param optionPrefix optional prefix to filter the parameters for validation. Use <tt>null</tt> for validate all.
122         * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
123         */
124        protected void validateParameters(String uri, Map<String, Object> parameters, String optionPrefix) {
125            Map<String, Object> param = parameters;
126            if (optionPrefix != null) {
127                param = IntrospectionSupport.extractProperties(parameters, optionPrefix);
128            }
129    
130            if (param.size() > 0) {
131                throw new ResolveEndpointFailedException(uri, "There are " + param.size()
132                    + " parameters that couldn't be set on the endpoint."
133                    + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
134                    + " Unknown parameters=[" + param + "]");
135            }
136        }
137    
138        /**
139         * Strategy for validation of the uri when creating the endpoint.
140         *
141         * @param uri        the uri - the uri the end user provided untouched
142         * @param path       the path - part after the scheme
143         * @param parameters the parameters, an empty map if no parameters given
144         * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
145         */
146        protected void validateURI(String uri, String path, Map<String, Object> parameters) {
147            // check for uri containing & but no ? marker
148            if (uri.contains("&") && !uri.contains("?")) {
149                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: no ? marker however the uri "
150                    + "has & parameter separators. Check the uri if its missing a ? marker.");
151            }
152    
153            // check for uri containing double && markers
154            if (uri.contains("&&")) {
155                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: Double && marker found. "
156                    + "Check the uri and remove the duplicate & marker.");
157            }
158    
159            // if we have a trailing & then that is invalid as well
160            if (uri.endsWith("&")) {
161                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: Trailing & marker found. "
162                    + "Check the uri and remove the trailing & marker.");
163            }
164        }
165    
166        public CamelContext getCamelContext() {
167            return camelContext;
168        }
169    
170        public void setCamelContext(CamelContext context) {
171            this.camelContext = context;
172        }
173    
174        public synchronized ExecutorService getExecutorService() {
175            if (executorService == null) {
176                executorService = createScheduledExecutorService();
177            }
178            return executorService;
179        }
180    
181        public synchronized void setExecutorService(ExecutorService executorService) {
182            this.executorService = executorService;
183        }
184    
185        public synchronized ScheduledExecutorService getScheduledExecutorService() {
186            ExecutorService executor = getExecutorService();
187            if (executor instanceof ScheduledExecutorService) {
188                return (ScheduledExecutorService) executor;
189            } else {
190                return createScheduledExecutorService();
191            }
192        }
193    
194        /**
195         * A factory method to create a default thread pool and executor
196         */
197        protected ScheduledExecutorService createScheduledExecutorService() {
198            String name = getClass().getSimpleName();
199            return ExecutorServiceHelper.newScheduledThreadPool(DEFAULT_THREADPOOL_SIZE, name, true);
200        }
201    
202        protected void doStart() throws Exception {
203            ObjectHelper.notNull(getCamelContext(), "camelContext");
204        }
205    
206        protected void doStop() throws Exception {
207            if (executorService != null) {
208                executorService.shutdown();
209                // must null it so we can restart
210                executorService = null;
211            }
212        }
213    
214        /**
215         * A factory method allowing derived components to create a new endpoint
216         * from the given URI, remaining path and optional parameters
217         *
218         * @param uri the full URI of the endpoint
219         * @param remaining the remaining part of the URI without the query
220         *                parameters or component prefix
221         * @param parameters the optional parameters passed in
222         * @return a newly created endpoint or null if the endpoint cannot be
223         *         created based on the inputs
224         */
225        protected abstract Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters)
226            throws Exception;
227    
228        /**
229         * Sets the bean properties on the given bean
230         *
231         * @param bean  the bean
232         * @param parameters  properties to set
233         */
234        protected void setProperties(Object bean, Map<String, Object> parameters) throws Exception {        
235            // set reference properties first as they use # syntax that fools the regular properties setter
236            EndpointHelper.setReferenceProperties(getCamelContext(), bean, parameters);
237            EndpointHelper.setProperties(getCamelContext(), bean, parameters);
238        }
239    
240        /**
241         * Derived classes may wish to overload this to prevent the default introspection of URI parameters
242         * on the created Endpoint instance
243         */
244        protected boolean useIntrospectionOnEndpoint() {
245            return true;
246        }
247    
248    
249        // Some helper methods
250        //-------------------------------------------------------------------------
251    
252        /**
253         * Converts the given value to the requested type
254         * @deprecated will be removed in Camel 2.3
255         */
256        @Deprecated
257        public <T> T convertTo(Class<T> type, Object value) {
258            return CamelContextHelper.convertTo(getCamelContext(), type, value);
259        }
260    
261        /**
262         * Converts the given value to the specified type throwing an {@link IllegalArgumentException}
263         * if the value could not be converted to a non null value
264         * @deprecated will be removed in Camel 2.3
265         */
266        @Deprecated
267        public  <T> T mandatoryConvertTo(Class<T> type, Object value) {
268            return CamelContextHelper.mandatoryConvertTo(getCamelContext(), type, value);
269        }
270    
271        /**
272         * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given
273         * {@link CamelContext}
274         * @deprecated will be removed in Camel 2.3
275         */
276        @Deprecated
277        public  <T> T newInstance(Class<T> beanType) {
278            return getCamelContext().getInjector().newInstance(beanType);
279        }
280    
281        /**
282         * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
283         * {@link CamelContext}
284         * @deprecated will be removed in Camel 2.3
285         */
286        @Deprecated
287        public Object lookup(String name) {
288            return getCamelContext().getRegistry().lookup(name);
289        }
290    
291        /**
292         * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
293         * {@link CamelContext}
294         * @deprecated will be removed in Camel 2.3
295         */
296        @Deprecated
297        public <T> T lookup(String name, Class<T> beanType) {
298            return getCamelContext().getRegistry().lookup(name, beanType);
299        }
300    
301        /**
302         * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
303         * {@link CamelContext} or throws exception if not found.
304         * @deprecated will be removed in Camel 2.3
305         */
306        @Deprecated
307        public Object mandatoryLookup(String name) {
308            return CamelContextHelper.mandatoryLookup(getCamelContext(), name);
309        }
310    
311        /**
312         * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
313         * {@link CamelContext} or throws exception if not found.
314         * @deprecated will be removed in Camel 2.3
315         */
316        @Deprecated
317        public <T> T mandatoryLookup(String name, Class<T> beanType) {
318            return CamelContextHelper.mandatoryLookup(getCamelContext(), name, beanType);
319        }
320    
321        /**
322         * Gets the parameter and remove it from the parameter map. This method doesn't resolve
323         * reference parameters in the registry.
324         * 
325         * @param parameters the parameters
326         * @param key        the key
327         * @param type       the requested type to convert the value from the parameter
328         * @return  the converted value parameter, <tt>null</tt> if parameter does not exists.
329         * @see #resolveAndRemoveReferenceParameter(Map, String, Class)
330         */
331        public <T> T getAndRemoveParameter(Map<String, Object> parameters, String key, Class<T> type) {
332            return getAndRemoveParameter(parameters, key, type, null);
333        }
334    
335        /**
336         * Gets the parameter and remove it from the parameter map. This method doesn't resolve
337         * reference parameters in the registry.
338         *
339         * @param parameters    the parameters
340         * @param key           the key
341         * @param type          the requested type to convert the value from the parameter
342         * @param defaultValue  use this default value if the parameter does not contain the key
343         * @return  the converted value parameter
344         * @see #resolveAndRemoveReferenceParameter(Map, String, Class, Object)
345         */
346        public <T> T getAndRemoveParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) {
347            Object value = parameters.remove(key);
348            if (value == null) {
349                value = defaultValue;
350            }
351            if (value == null) {
352                return null;
353            }
354    
355            return CamelContextHelper.convertTo(getCamelContext(), type, value);
356        }
357    
358        /**
359         * Resolves a reference parameter in the registry and removes it from the map. 
360         * 
361         * @param <T>           type of object to lookup in the registry.
362         * @param parameters    parameter map.
363         * @param key           parameter map key.
364         * @param type          type of object to lookup in the registry.
365         * @return the referenced object or <code>null</code> if the parameter map 
366         *         doesn't contain the key.
367         * @throws IllegalArgumentException if a non-null reference was not found in 
368         *         registry.
369         */
370        public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type) {
371            return resolveAndRemoveReferenceParameter(parameters, key, type, null); 
372        }
373    
374        /**
375         * Resolves a reference parameter in the registry and removes it from the map. 
376         * 
377         * @param <T>           type of object to lookup in the registry.
378         * @param parameters    parameter map.
379         * @param key           parameter map key.
380         * @param type          type of object to lookup in the registry.
381         * @param defaultValue  default value to use if the parameter map doesn't 
382         *                      contain the key.
383         * @return the referenced object or the default value.
384         * @throws IllegalArgumentException if referenced object was not found in 
385         *         registry.
386         */
387        public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) {
388            String value = getAndRemoveParameter(parameters, key, String.class);
389            if (value == null) {
390                return defaultValue;
391            } else {
392                return EndpointHelper.resolveReferenceParameter(getCamelContext(), value.toString(), type);
393            }
394        }
395        
396        /**
397         * Resolves a reference list parameter in the registry and removes it from
398         * the map.
399         * 
400         * @param parameters
401         *            parameter map.
402         * @param key
403         *            parameter map key.
404         * @param elementType
405         *            result list element type.
406         * @return the list of referenced objects or an empty list if the parameter
407         *         map doesn't contain the key.
408         * @throws IllegalArgumentException if any of the referenced objects was 
409         *         not found in registry.
410         * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class)
411         */
412        public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType) {
413            return resolveAndRemoveReferenceListParameter(parameters, key, elementType, new ArrayList<T>(0));
414        }
415    
416        /**
417         * Resolves a reference list parameter in the registry and removes it from
418         * the map.
419         * 
420         * @param parameters
421         *            parameter map.
422         * @param key
423         *            parameter map key.
424         * @param elementType
425         *            result list element type.
426         * @param defaultValue
427         *            default value to use if the parameter map doesn't
428         *            contain the key.
429         * @return the list of referenced objects or the default value.
430         * @throws IllegalArgumentException if any of the referenced objects was 
431         *         not found in registry.
432         * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class)
433         */
434        public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType, List<T>  defaultValue) {
435            String value = getAndRemoveParameter(parameters, key, String.class);
436            
437            if (value == null) {
438                return defaultValue;
439            } else {
440                return EndpointHelper.resolveReferenceListParameter(getCamelContext(), value.toString(), elementType);
441            }
442        }
443        
444        /**
445         * Returns the reminder of the text if it starts with the prefix.
446         * <p/>
447         * Is useable for string parameters that contains commands.
448         * 
449         * @param prefix  the prefix
450         * @param text  the text
451         * @return the reminder, or null if no reminder
452         */
453        protected String ifStartsWithReturnRemainder(String prefix, String text) {
454            if (text.startsWith(prefix)) {
455                String remainder = text.substring(prefix.length());
456                if (remainder.length() > 0) {
457                    return remainder;
458                }
459            }
460            return null;
461        }
462    
463    }