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