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