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