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    
024    import org.apache.camel.CamelContext;
025    import org.apache.camel.Component;
026    import org.apache.camel.Endpoint;
027    import org.apache.camel.ResolveEndpointFailedException;
028    import org.apache.camel.util.CamelContextHelper;
029    import org.apache.camel.util.EndpointHelper;
030    import org.apache.camel.util.IntrospectionSupport;
031    import org.apache.camel.util.ObjectHelper;
032    import org.apache.camel.util.URISupport;
033    import org.apache.camel.util.UnsafeUriCharactersEncoder;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    
037    /**
038     * Default component to use for base for components implementations.
039     *
040     * @version $Revision: 21293 $
041     */
042    public abstract class DefaultComponent extends ServiceSupport implements Component {
043        private static final transient Log LOG = LogFactory.getLog(DefaultComponent.class);
044    
045        private CamelContext camelContext;
046    
047        public DefaultComponent() {
048        }
049    
050        public DefaultComponent(CamelContext context) {
051            this.camelContext = context;
052        }
053    
054        public Endpoint createEndpoint(String uri) throws Exception {
055            ObjectHelper.notNull(getCamelContext(), "camelContext");
056            //encode URI string to the unsafe URI characters
057            URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
058            String path = u.getSchemeSpecificPart();
059    
060            // lets trim off any query arguments
061            if (path.startsWith("//")) {
062                path = path.substring(2);
063            }
064            int idx = path.indexOf('?');
065            if (idx > 0) {
066                path = path.substring(0, idx);
067            }
068            Map<String, Object> parameters = URISupport.parseParameters(u);
069    
070            validateURI(uri, path, parameters);
071    
072            if (LOG.isDebugEnabled()) {
073                LOG.debug("Creating endpoint uri=[" + uri + "], path=[" + path + "], parameters=[" + parameters + "]");
074            }
075            Endpoint endpoint = createEndpoint(uri, path, parameters);
076            if (endpoint == null) {
077                return null;
078            }
079    
080            if (parameters != null && !parameters.isEmpty()) {
081                endpoint.configureProperties(parameters);
082                if (useIntrospectionOnEndpoint()) {
083                    setProperties(endpoint, parameters);
084                }
085    
086                // if endpoint is strict (not lenient) and we have unknown parameters configured then
087                // fail if there are parameters that could not be set, then they are probably misspell or not supported at all
088                if (!endpoint.isLenientProperties()) {
089                    validateParameters(uri, parameters, null);
090                }
091            }
092    
093            afterConfiguration(uri, path, endpoint, parameters);
094            return endpoint;
095        }
096    
097        /**
098         * Strategy to do post configuration logic.
099         * <p/>
100         * Can be used to construct an URI based on the remaining parameters. For example the parameters that configures
101         * the endpoint have been removed from the parameters which leaves only the additional parameters left.
102         *
103         * @param endpoint the created endpoint
104         * @param parameters the remaining parameters after the endpoint has been created and parsed the parameters
105         * @throws Exception can be thrown to indicate error creating the endpoint
106         */
107        protected void afterConfiguration(String uri, String remaining, Endpoint endpoint, Map<String, Object> parameters) throws Exception {
108            // noop
109        }
110    
111        /**
112         * Strategy for validation of parameters, that was not able to be resolved to any endpoint options.
113         *
114         * @param uri          the uri - the uri the end user provided untouched
115         * @param parameters   the parameters, an empty map if no parameters given
116         * @param optionPrefix optional prefix to filter the parameters for validation. Use <tt>null</tt> for validate all.
117         * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
118         */
119        protected void validateParameters(String uri, Map<String, Object> parameters, String optionPrefix) {
120            Map<String, Object> param = parameters;
121            if (optionPrefix != null) {
122                param = IntrospectionSupport.extractProperties(parameters, optionPrefix);
123            }
124    
125            if (param.size() > 0) {
126                throw new ResolveEndpointFailedException(uri, "There are " + param.size()
127                    + " parameters that couldn't be set on the endpoint."
128                    + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
129                    + " Unknown parameters=[" + param + "]");
130            }
131        }
132    
133        /**
134         * Strategy for validation of the uri when creating the endpoint.
135         *
136         * @param uri        the uri - the uri the end user provided untouched
137         * @param path       the path - part after the scheme
138         * @param parameters the parameters, an empty map if no parameters given
139         * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
140         */
141        protected void validateURI(String uri, String path, Map<String, Object> parameters) {
142            // check for uri containing & but no ? marker
143            if (uri.contains("&") && !uri.contains("?")) {
144                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: no ? marker however the uri "
145                    + "has & parameter separators. Check the uri if its missing a ? marker.");
146            }
147    
148            // check for uri containing double && markers
149            if (uri.contains("&&")) {
150                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: Double && marker found. "
151                    + "Check the uri and remove the duplicate & marker.");
152            }
153    
154            // if we have a trailing & then that is invalid as well
155            if (uri.endsWith("&")) {
156                throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: Trailing & marker found. "
157                    + "Check the uri and remove the trailing & marker.");
158            }
159        }
160    
161        public CamelContext getCamelContext() {
162            return camelContext;
163        }
164    
165        public void setCamelContext(CamelContext context) {
166            this.camelContext = context;
167        }
168    
169        protected void doStart() throws Exception {
170            ObjectHelper.notNull(getCamelContext(), "camelContext");
171        }
172    
173        protected void doStop() throws Exception {
174            // noop
175        }
176    
177        /**
178         * A factory method allowing derived components to create a new endpoint
179         * from the given URI, remaining path and optional parameters
180         *
181         * @param uri the full URI of the endpoint
182         * @param remaining the remaining part of the URI without the query
183         *                parameters or component prefix
184         * @param parameters the optional parameters passed in
185         * @return a newly created endpoint or null if the endpoint cannot be
186         *         created based on the inputs
187         */
188        protected abstract Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters)
189            throws Exception;
190    
191        /**
192         * Sets the bean properties on the given bean
193         *
194         * @param bean  the bean
195         * @param parameters  properties to set
196         */
197        protected void setProperties(Object bean, Map<String, Object> parameters) throws Exception {        
198            // set reference properties first as they use # syntax that fools the regular properties setter
199            EndpointHelper.setReferenceProperties(getCamelContext(), bean, parameters);
200            EndpointHelper.setProperties(getCamelContext(), bean, parameters);
201        }
202    
203        /**
204         * Derived classes may wish to overload this to prevent the default introspection of URI parameters
205         * on the created Endpoint instance
206         */
207        protected boolean useIntrospectionOnEndpoint() {
208            return true;
209        }
210    
211        /**
212         * Gets the parameter and remove it from the parameter map. This method doesn't resolve
213         * reference parameters in the registry.
214         * 
215         * @param parameters the parameters
216         * @param key        the key
217         * @param type       the requested type to convert the value from the parameter
218         * @return  the converted value parameter, <tt>null</tt> if parameter does not exists.
219         * @see #resolveAndRemoveReferenceParameter(Map, String, Class)
220         */
221        public <T> T getAndRemoveParameter(Map<String, Object> parameters, String key, Class<T> type) {
222            return getAndRemoveParameter(parameters, key, type, null);
223        }
224    
225        /**
226         * Gets the parameter and remove it from the parameter map. This method doesn't resolve
227         * reference parameters in the registry.
228         *
229         * @param parameters    the parameters
230         * @param key           the key
231         * @param type          the requested type to convert the value from the parameter
232         * @param defaultValue  use this default value if the parameter does not contain the key
233         * @return  the converted value parameter
234         * @see #resolveAndRemoveReferenceParameter(Map, String, Class, Object)
235         */
236        public <T> T getAndRemoveParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) {
237            Object value = parameters.remove(key);
238            if (value == null) {
239                value = defaultValue;
240            }
241            if (value == null) {
242                return null;
243            }
244    
245            return CamelContextHelper.convertTo(getCamelContext(), type, value);
246        }
247    
248        /**
249         * Resolves a reference parameter in the registry and removes it from the map. 
250         * 
251         * @param <T>           type of object to lookup in the registry.
252         * @param parameters    parameter map.
253         * @param key           parameter map key.
254         * @param type          type of object to lookup in the registry.
255         * @return the referenced object or <code>null</code> if the parameter map 
256         *         doesn't contain the key.
257         * @throws IllegalArgumentException if a non-null reference was not found in 
258         *         registry.
259         */
260        public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type) {
261            return resolveAndRemoveReferenceParameter(parameters, key, type, null); 
262        }
263    
264        /**
265         * Resolves a reference parameter in the registry and removes it from the map. 
266         * 
267         * @param <T>           type of object to lookup in the registry.
268         * @param parameters    parameter map.
269         * @param key           parameter map key.
270         * @param type          type of object to lookup in the registry.
271         * @param defaultValue  default value to use if the parameter map doesn't 
272         *                      contain the key.
273         * @return the referenced object or the default value.
274         * @throws IllegalArgumentException if referenced object was not found in 
275         *         registry.
276         */
277        public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) {
278            String value = getAndRemoveParameter(parameters, key, String.class);
279            if (value == null) {
280                return defaultValue;
281            } else {
282                return EndpointHelper.resolveReferenceParameter(getCamelContext(), value.toString(), type);
283            }
284        }
285        
286        /**
287         * Resolves a reference list parameter in the registry and removes it from
288         * the map.
289         * 
290         * @param parameters
291         *            parameter map.
292         * @param key
293         *            parameter map key.
294         * @param elementType
295         *            result list element type.
296         * @return the list of referenced objects or an empty list if the parameter
297         *         map doesn't contain the key.
298         * @throws IllegalArgumentException if any of the referenced objects was 
299         *         not found in registry.
300         * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class)
301         */
302        public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType) {
303            return resolveAndRemoveReferenceListParameter(parameters, key, elementType, new ArrayList<T>(0));
304        }
305    
306        /**
307         * Resolves a reference list parameter in the registry and removes it from
308         * the map.
309         * 
310         * @param parameters
311         *            parameter map.
312         * @param key
313         *            parameter map key.
314         * @param elementType
315         *            result list element type.
316         * @param defaultValue
317         *            default value to use if the parameter map doesn't
318         *            contain the key.
319         * @return the list of referenced objects or the default value.
320         * @throws IllegalArgumentException if any of the referenced objects was 
321         *         not found in registry.
322         * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class)
323         */
324        public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType, List<T>  defaultValue) {
325            String value = getAndRemoveParameter(parameters, key, String.class);
326            
327            if (value == null) {
328                return defaultValue;
329            } else {
330                return EndpointHelper.resolveReferenceListParameter(getCamelContext(), value.toString(), elementType);
331            }
332        }
333        
334        /**
335         * Returns the reminder of the text if it starts with the prefix.
336         * <p/>
337         * Is useable for string parameters that contains commands.
338         * 
339         * @param prefix  the prefix
340         * @param text  the text
341         * @return the reminder, or null if no reminder
342         */
343        protected String ifStartsWithReturnRemainder(String prefix, String text) {
344            if (text.startsWith(prefix)) {
345                String remainder = text.substring(prefix.length());
346                if (remainder.length() > 0) {
347                    return remainder;
348                }
349            }
350            return null;
351        }
352    
353    }