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.ScheduledExecutorService; 023 import java.util.concurrent.ScheduledThreadPoolExecutor; 024 import java.util.concurrent.ThreadFactory; 025 026 import org.apache.camel.CamelContext; 027 import org.apache.camel.Component; 028 import org.apache.camel.Endpoint; 029 import org.apache.camel.ResolveEndpointFailedException; 030 import org.apache.camel.util.CamelContextHelper; 031 import org.apache.camel.util.IntrospectionSupport; 032 import org.apache.camel.util.ObjectHelper; 033 import org.apache.camel.util.URISupport; 034 import org.apache.camel.util.UnsafeUriCharactersEncoder; 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: 751221 $ 043 */ 044 public abstract class DefaultComponent extends ServiceSupport implements Component { 045 private static final transient Log LOG = LogFactory.getLog(DefaultComponent.class); 046 047 private int defaultThreadPoolSize = 5; 048 private CamelContext camelContext; 049 private ScheduledExecutorService 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) throws ResolveEndpointFailedException { 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 ScheduledExecutorService getExecutorService() { 153 if (executorService == null) { 154 executorService = createExecutorService(); 155 } 156 return executorService; 157 } 158 159 public void setExecutorService(ScheduledExecutorService executorService) { 160 this.executorService = executorService; 161 } 162 163 /** 164 * A factory method to create a default thread pool and executor 165 */ 166 protected ScheduledExecutorService createExecutorService() { 167 return new ScheduledThreadPoolExecutor(defaultThreadPoolSize, new ThreadFactory() { 168 int counter; 169 170 public synchronized Thread newThread(Runnable runnable) { 171 Thread thread = new Thread(runnable); 172 thread.setName("Thread: " + (++counter) + " " + DefaultComponent.this.toString()); 173 return thread; 174 } 175 }); 176 } 177 178 protected void doStart() throws Exception { 179 ObjectHelper.notNull(getCamelContext(), "camelContext"); 180 } 181 182 protected void doStop() throws Exception { 183 if (executorService != null) { 184 executorService.shutdown(); 185 } 186 } 187 188 /** 189 * A factory method allowing derived components to create a new endpoint 190 * from the given URI, remaining path and optional parameters 191 * 192 * @param uri the full URI of the endpoint 193 * @param remaining the remaining part of the URI without the query 194 * parameters or component prefix 195 * @param parameters the optional parameters passed in 196 * @return a newly created endpoint or null if the endpoint cannot be 197 * created based on the inputs 198 */ 199 protected abstract Endpoint createEndpoint(String uri, String remaining, Map parameters) 200 throws Exception; 201 202 /** 203 * Sets the bean properties on the given bean 204 * 205 * @param bean the bean 206 * @param parameters properties to set 207 */ 208 protected void setProperties(Object bean, Map parameters) throws Exception { 209 // set reference properties first as they use # syntax that fools the regular properties setter 210 setReferenceProperties(bean, parameters); 211 IntrospectionSupport.setProperties(getCamelContext().getTypeConverter(), bean, parameters); 212 } 213 214 /** 215 * Sets the reference properties on the given bean 216 * <p/> 217 * This is convention over configuration, setting all reference parameters (using {@link #isReferenceParameter(String)} 218 * by looking it up in registry and setting it on the bean if possible. 219 */ 220 protected void setReferenceProperties(Object bean, Map parameters) throws Exception { 221 Iterator it = parameters.keySet().iterator(); 222 while (it.hasNext()) { 223 Object key = it.next(); 224 String value = (String) parameters.get(key); 225 if (isReferenceParameter(value)) { 226 Object ref = lookup(value.substring(1)); 227 String name = key.toString(); 228 if (ref != null) { 229 boolean hit = IntrospectionSupport.setProperty(getCamelContext().getTypeConverter(), bean, name, ref); 230 if (hit) { 231 if (LOG.isDebugEnabled()) { 232 LOG.debug("Configued property: " + name + " on bean: " + bean + " with value: " + ref); 233 } 234 // must remove as its a valid option and we could configure it 235 it.remove(); 236 } 237 } 238 } 239 } 240 } 241 242 /** 243 * Is the given parameter a reference parameter (starting with a # char) 244 */ 245 protected boolean isReferenceParameter(String parameter) { 246 return parameter != null && parameter.startsWith("#"); 247 } 248 249 /** 250 * Derived classes may wish to overload this to prevent the default introspection of URI parameters 251 * on the created Endpoint instance 252 */ 253 protected boolean useIntrospectionOnEndpoint() { 254 return true; 255 } 256 257 258 // Some helper methods 259 //------------------------------------------------------------------------- 260 261 /** 262 * Converts the given value to the requested type 263 */ 264 public <T> T convertTo(Class<T> type, Object value) { 265 return CamelContextHelper.convertTo(getCamelContext(), type, value); 266 } 267 268 /** 269 * Converts the given value to the specified type throwing an {@link IllegalArgumentException} 270 * if the value could not be converted to a non null value 271 */ 272 public <T> T mandatoryConvertTo(Class<T> type, Object value) { 273 return CamelContextHelper.mandatoryConvertTo(getCamelContext(), type, value); 274 } 275 276 /** 277 * Creates a new instance of the given type using the {@link org.apache.camel.spi.Injector} on the given 278 * {@link CamelContext} 279 */ 280 public <T> T newInstance(Class<T> beanType) { 281 return getCamelContext().getInjector().newInstance(beanType); 282 } 283 284 /** 285 * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the 286 * {@link CamelContext} 287 */ 288 public Object lookup(String name) { 289 return getCamelContext().getRegistry().lookup(name); 290 } 291 292 /** 293 * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the 294 * {@link CamelContext} 295 */ 296 public <T> T lookup(String name, Class<T> beanType) { 297 return getCamelContext().getRegistry().lookup(name, beanType); 298 } 299 300 /** 301 * Look up the given named bean in the {@link org.apache.camel.spi.Registry} on the 302 * {@link CamelContext} or throws exception if not found. 303 */ 304 public Object mandatoryLookup(String name) { 305 return CamelContextHelper.mandatoryLookup(getCamelContext(), name); 306 } 307 308 /** 309 * Look up the given named bean of the given type in the {@link org.apache.camel.spi.Registry} on the 310 * {@link CamelContext} or throws exception if not found. 311 */ 312 public <T> T mandatoryLookup(String name, Class<T> beanType) { 313 return CamelContextHelper.mandatoryLookup(getCamelContext(), name, beanType); 314 } 315 316 /** 317 * Gets the parameter and remove it from the parameter map. 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, <tt>null</tt> if parameter does not exists. 323 */ 324 public <T> T getAndRemoveParameter(Map parameters, String key, Class<T> type) { 325 return getAndRemoveParameter(parameters, key, type, null); 326 } 327 328 /** 329 * Gets the parameter and remove it from the parameter map. 330 * 331 * @param parameters the parameters 332 * @param key the key 333 * @param type the requested type to convert the value from the parameter 334 * @param defaultValue use this default value if the parameter does not contain the key 335 * @return the converted value parameter 336 */ 337 public <T> T getAndRemoveParameter(Map parameters, String key, Class<T> type, T defaultValue) { 338 Object value = parameters.remove(key); 339 if (value == null) { 340 value = defaultValue; 341 } 342 if (value == null) { 343 return null; 344 } 345 return convertTo(type, value); 346 } 347 348 /** 349 * Returns the reminder of the text if it starts with the prefix. 350 * <p/> 351 * Is useable for string parameters that contains commands. 352 * 353 * @param prefix the prefix 354 * @param text the text 355 * @return the reminder, or null if no reminder 356 */ 357 protected String ifStartsWithReturnRemainder(String prefix, String text) { 358 if (text.startsWith(prefix)) { 359 String remainder = text.substring(prefix.length()); 360 if (remainder.length() > 0) { 361 return remainder; 362 } 363 } 364 return null; 365 } 366 367 }