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