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