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