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.Iterator;
021 import java.util.Map;
022 import java.util.concurrent.ExecutorService;
023 import java.util.concurrent.ScheduledExecutorService;
024
025 import org.apache.camel.CamelContext;
026 import org.apache.camel.Component;
027 import org.apache.camel.Endpoint;
028 import org.apache.camel.ResolveEndpointFailedException;
029 import org.apache.camel.util.CamelContextHelper;
030 import org.apache.camel.util.IntrospectionSupport;
031 import org.apache.camel.util.ObjectHelper;
032 import org.apache.camel.util.URISupport;
033 import org.apache.camel.util.UnsafeUriCharactersEncoder;
034 import org.apache.camel.util.concurrent.ExecutorServiceHelper;
035 import org.apache.commons.logging.Log;
036 import org.apache.commons.logging.LogFactory;
037
038
039 /**
040 * Default component to use for base for components implementations.
041 *
042 * @version $Revision: 777808 $
043 */
044 public abstract class DefaultComponent extends ServiceSupport implements Component {
045 private static final transient Log LOG = LogFactory.getLog(DefaultComponent.class);
046
047 private static final int DEFAULT_THREADPOOL_SIZE = 5;
048 private CamelContext camelContext;
049 private ExecutorService executorService;
050
051 public DefaultComponent() {
052 }
053
054 public DefaultComponent(CamelContext context) {
055 this.camelContext = context;
056 }
057
058 public Endpoint createEndpoint(String uri) throws Exception {
059 ObjectHelper.notNull(getCamelContext(), "camelContext");
060 //encode URI string to the unsafe URI characters
061 URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
062 String path = u.getSchemeSpecificPart();
063
064 // lets trim off any query arguments
065 if (path.startsWith("//")) {
066 path = path.substring(2);
067 }
068 int idx = path.indexOf('?');
069 if (idx > 0) {
070 path = path.substring(0, idx);
071 }
072 Map parameters = URISupport.parseParameters(u);
073
074 validateURI(uri, path, parameters);
075
076 if (LOG.isDebugEnabled()) {
077 LOG.debug("Creating endpoint uri=[" + uri + "], path=[" + path + "], parameters=[" + parameters + "]");
078 }
079 Endpoint endpoint = createEndpoint(uri, path, parameters);
080 if (endpoint == null) {
081 return null;
082 }
083
084 if (parameters != null) {
085 endpoint.configureProperties(parameters);
086 if (useIntrospectionOnEndpoint()) {
087 setProperties(endpoint, parameters);
088 }
089
090 // if endpoint is strict (not lenient) and we have unknown parameters configured then
091 // fail if there are parameters that could not be set, then they are probably miss spelt or not supported at all
092 if (!endpoint.isLenientProperties()) {
093 validateParameters(uri, parameters, null);
094 }
095 }
096
097 return endpoint;
098 }
099
100 /**
101 * Strategy for validation of parameters, that was not able to be resolved to any endpoint options.
102 *
103 * @param uri the uri - the uri the end user provided untouched
104 * @param parameters the parameters, an empty map if no parameters given
105 * @param optionPrefix optional prefix to filter the parameters for validation. Use <tt>null</tt> for validate all.
106 * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
107 */
108 protected void validateParameters(String uri, Map parameters, String optionPrefix) {
109 Map param = parameters;
110 if (optionPrefix != null) {
111 param = IntrospectionSupport.extractProperties(parameters, optionPrefix);
112 }
113
114 if (param.size() > 0) {
115 throw new ResolveEndpointFailedException(uri, "There are " + param.size()
116 + " parameters that couldn't be set on the endpoint."
117 + " Check the uri if the parameters are spelt correctly and that they are properties of the endpoint."
118 + " Unknown parameters=[" + param + "]");
119 }
120 }
121
122 /**
123 * Strategy for validation of the uri when creating the endpoint.
124 *
125 * @param uri the uri - the uri the end user provided untouched
126 * @param path the path - part after the scheme
127 * @param parameters the parameters, an empty map if no parameters given
128 * @throws ResolveEndpointFailedException should be thrown if the URI validation failed
129 */
130 protected void validateURI(String uri, String path, Map parameters) {
131 // check for uri containing & but no ? marker
132 if (uri.contains("&") && !uri.contains("?")) {
133 throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: no ? marker however the uri "
134 + "has & parameter separators. Check the uri if its missing a ? marker.");
135 }
136
137 // check for uri containing double && markers
138 if (uri.contains("&&")) {
139 throw new ResolveEndpointFailedException(uri, "Invalid uri syntax: Double && marker found. "
140 + "Check the uri and remove the duplicate & marker.");
141 }
142 }
143
144 public CamelContext getCamelContext() {
145 return camelContext;
146 }
147
148 public void setCamelContext(CamelContext context) {
149 this.camelContext = context;
150 }
151
152 public synchronized ExecutorService getExecutorService() {
153 if (executorService == null) {
154 executorService = createScheduledExecutorService();
155 }
156 return executorService;
157 }
158
159 public synchronized void setExecutorService(ExecutorService executorService) {
160 this.executorService = executorService;
161 }
162
163 public synchronized ScheduledExecutorService getScheduledExecutorService() {
164 ExecutorService executor = getExecutorService();
165 if (executor instanceof ScheduledExecutorService) {
166 return (ScheduledExecutorService) executor;
167 } else {
168 return createScheduledExecutorService();
169 }
170 }
171
172 /**
173 * A factory method to create a default thread pool and executor
174 */
175 protected ScheduledExecutorService createScheduledExecutorService() {
176 String name = getClass().getSimpleName();
177 return ExecutorServiceHelper.newScheduledThreadPool(DEFAULT_THREADPOOL_SIZE, name, true);
178 }
179
180 protected void doStart() throws Exception {
181 ObjectHelper.notNull(getCamelContext(), "camelContext");
182 }
183
184 protected void doStop() throws Exception {
185 if (executorService != null) {
186 executorService.shutdown();
187 }
188 }
189
190 /**
191 * A factory method allowing derived components to create a new endpoint
192 * from the given URI, remaining path and optional parameters
193 *
194 * @param uri the full URI of the endpoint
195 * @param remaining the remaining part of the URI without the query
196 * parameters or component prefix
197 * @param parameters the optional parameters passed in
198 * @return a newly created endpoint or null if the endpoint cannot be
199 * created based on the inputs
200 */
201 protected abstract Endpoint createEndpoint(String uri, String remaining, Map parameters)
202 throws Exception;
203
204 /**
205 * Sets the bean properties on the given bean
206 *
207 * @param bean the bean
208 * @param parameters properties to set
209 */
210 protected void setProperties(Object bean, Map parameters) throws Exception {
211 // set reference properties first as they use # syntax that fools the regular properties setter
212 setReferenceProperties(bean, parameters);
213 IntrospectionSupport.setProperties(getCamelContext().getTypeConverter(), bean, parameters);
214 }
215
216 /**
217 * Sets the reference properties on the given bean
218 * <p/>
219 * This is convention over configuration, setting all reference parameters (using {@link #isReferenceParameter(String)}
220 * by looking it up in registry and setting it on the bean if possible.
221 */
222 protected void setReferenceProperties(Object bean, Map parameters) throws Exception {
223 Iterator it = parameters.keySet().iterator();
224 while (it.hasNext()) {
225 Object key = it.next();
226 String value = (String) parameters.get(key);
227 if (isReferenceParameter(value)) {
228 Object ref = lookup(value.substring(1));
229 String name = key.toString();
230 if (ref != null) {
231 boolean hit = IntrospectionSupport.setProperty(getCamelContext().getTypeConverter(), bean, name, ref);
232 if (hit) {
233 if (LOG.isDebugEnabled()) {
234 LOG.debug("Configued property: " + name + " on bean: " + bean + " with value: " + ref);
235 }
236 // must remove as its a valid option and we could configure it
237 it.remove();
238 }
239 }
240 }
241 }
242 }
243
244 /**
245 * Is the given parameter a reference parameter (starting with a # char)
246 */
247 protected boolean isReferenceParameter(String parameter) {
248 return parameter != null && parameter.startsWith("#");
249 }
250
251 /**
252 * Derived classes may wish to overload this to prevent the default introspection of URI parameters
253 * on the created Endpoint instance
254 */
255 protected boolean useIntrospectionOnEndpoint() {
256 return true;
257 }
258
259
260 // Some helper methods
261 //-------------------------------------------------------------------------
262
263 /**
264 * Converts the given value to the requested type
265 */
266 public <T> T convertTo(Class<T> type, Object value) {
267 return CamelContextHelper.convertTo(getCamelContext(), type, value);
268 }
269
270 /**
271 * Converts the given value to the specified type throwing an {@link IllegalArgumentException}
272 * if the value could not be converted to a non null value
273 */
274 public <T> T mandatoryConvertTo(Class<T> type, Object value) {
275 return CamelContextHelper.mandatoryConvertTo(getCamelContext(), type, value);
276 }
277
278 /**
279 * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given
280 * {@link CamelContext}
281 */
282 public <T> T newInstance(Class<T> beanType) {
283 return getCamelContext().getInjector().newInstance(beanType);
284 }
285
286 /**
287 * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
288 * {@link CamelContext}
289 */
290 public Object lookup(String name) {
291 return getCamelContext().getRegistry().lookup(name);
292 }
293
294 /**
295 * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
296 * {@link CamelContext}
297 */
298 public <T> T lookup(String name, Class<T> beanType) {
299 return getCamelContext().getRegistry().lookup(name, beanType);
300 }
301
302 /**
303 * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the
304 * {@link CamelContext} or throws exception if not found.
305 */
306 public Object mandatoryLookup(String name) {
307 return CamelContextHelper.mandatoryLookup(getCamelContext(), name);
308 }
309
310 /**
311 * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the
312 * {@link CamelContext} or throws exception if not found.
313 */
314 public <T> T mandatoryLookup(String name, Class<T> beanType) {
315 return CamelContextHelper.mandatoryLookup(getCamelContext(), name, beanType);
316 }
317
318 /**
319 * Gets the parameter and remove it from the parameter map.
320 *
321 * @param parameters the parameters
322 * @param key the key
323 * @param type the requested type to convert the value from the parameter
324 * @return the converted value parameter, <tt>null</tt> if parameter does not exists.
325 */
326 public <T> T getAndRemoveParameter(Map parameters, String key, Class<T> type) {
327 return getAndRemoveParameter(parameters, key, type, null);
328 }
329
330 /**
331 * Gets the parameter and remove it from the parameter map.
332 *
333 * @param parameters the parameters
334 * @param key the key
335 * @param type the requested type to convert the value from the parameter
336 * @param defaultValue use this default value if the parameter does not contain the key
337 * @return the converted value parameter
338 */
339 public <T> T getAndRemoveParameter(Map parameters, String key, Class<T> type, T defaultValue) {
340 Object value = parameters.remove(key);
341 if (value == null) {
342 value = defaultValue;
343 }
344 if (value == null) {
345 return null;
346 }
347 return convertTo(type, value);
348 }
349
350 /**
351 * Returns the reminder of the text if it starts with the prefix.
352 * <p/>
353 * Is useable for string parameters that contains commands.
354 *
355 * @param prefix the prefix
356 * @param text the text
357 * @return the reminder, or null if no reminder
358 */
359 protected String ifStartsWithReturnRemainder(String prefix, String text) {
360 if (text.startsWith(prefix)) {
361 String remainder = text.substring(prefix.length());
362 if (remainder.length() > 0) {
363 return remainder;
364 }
365 }
366 return null;
367 }
368
369 }