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