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