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=[{}]", 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 // if we have a value then convert it 349 return CamelContextHelper.mandatoryConvertTo(getCamelContext(), type, value); 350 } else { 351 value = defaultValue; 352 } 353 if (value == null) { 354 return null; 355 } 356 357 return CamelContextHelper.mandatoryConvertTo(getCamelContext(), type, value); 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 * @return the converted value parameter 368 */ 369 public <T> T getAndRemoveOrResolveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type) { 370 return getAndRemoveOrResolveReferenceParameter(parameters, key, type, null); 371 } 372 373 /** 374 * Gets the parameter and remove it from the parameter map. This method resolves 375 * reference parameters in the registry as well. 376 * 377 * @param parameters the parameters 378 * @param key the key 379 * @param type the requested type to convert the value from the parameter 380 * @param defaultValue use this default value if the parameter does not contain the key 381 * @return the converted value parameter 382 */ 383 public <T> T getAndRemoveOrResolveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) { 384 String value = getAndRemoveParameter(parameters, key, String.class); 385 if (value == null) { 386 return defaultValue; 387 } else if (EndpointHelper.isReferenceParameter(value)) { 388 return EndpointHelper.resolveReferenceParameter(getCamelContext(), value, type); 389 } else { 390 return getCamelContext().getTypeConverter().convertTo(type, value); 391 } 392 } 393 394 /** 395 * Resolves a reference parameter in the registry and removes it from the map. 396 * 397 * @param <T> type of object to lookup in the registry. 398 * @param parameters parameter map. 399 * @param key parameter map key. 400 * @param type type of object to lookup in the registry. 401 * @return the referenced object or <code>null</code> if the parameter map 402 * doesn't contain the key. 403 * @throws IllegalArgumentException if a non-null reference was not found in 404 * registry. 405 */ 406 public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type) { 407 return resolveAndRemoveReferenceParameter(parameters, key, type, null); 408 } 409 410 /** 411 * Resolves a reference parameter in the registry and removes it from the map. 412 * 413 * @param <T> type of object to lookup in the registry. 414 * @param parameters parameter map. 415 * @param key parameter map key. 416 * @param type type of object to lookup in the registry. 417 * @param defaultValue default value to use if the parameter map doesn't 418 * contain the key. 419 * @return the referenced object or the default value. 420 * @throws IllegalArgumentException if referenced object was not found in 421 * registry. 422 */ 423 public <T> T resolveAndRemoveReferenceParameter(Map<String, Object> parameters, String key, Class<T> type, T defaultValue) { 424 String value = getAndRemoveParameter(parameters, key, String.class); 425 if (value == null) { 426 return defaultValue; 427 } else { 428 return EndpointHelper.resolveReferenceParameter(getCamelContext(), value, type); 429 } 430 } 431 432 /** 433 * Resolves a reference list parameter in the registry and removes it from 434 * the map. 435 * 436 * @param parameters parameter map. 437 * @param key parameter map key. 438 * @param elementType result list element type. 439 * @return the list of referenced objects or an empty list if the parameter 440 * map doesn't contain the key. 441 * @throws IllegalArgumentException if any of the referenced objects was 442 * not found in registry. 443 * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class) 444 */ 445 public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType) { 446 return resolveAndRemoveReferenceListParameter(parameters, key, elementType, new ArrayList<>(0)); 447 } 448 449 /** 450 * Resolves a reference list parameter in the registry and removes it from 451 * the map. 452 * 453 * @param parameters parameter map. 454 * @param key parameter map key. 455 * @param elementType result list element type. 456 * @param defaultValue default value to use if the parameter map doesn't 457 * contain the key. 458 * @return the list of referenced objects or the default value. 459 * @throws IllegalArgumentException if any of the referenced objects was 460 * not found in registry. 461 * @see EndpointHelper#resolveReferenceListParameter(CamelContext, String, Class) 462 */ 463 public <T> List<T> resolveAndRemoveReferenceListParameter(Map<String, Object> parameters, String key, Class<T> elementType, List<T> defaultValue) { 464 String value = getAndRemoveParameter(parameters, key, String.class); 465 466 if (value == null) { 467 return defaultValue; 468 } else { 469 return EndpointHelper.resolveReferenceListParameter(getCamelContext(), value, elementType); 470 } 471 } 472 473 /** 474 * Returns the reminder of the text if it starts with the prefix. 475 * <p/> 476 * Is useable for string parameters that contains commands. 477 * 478 * @param prefix the prefix 479 * @param text the text 480 * @return the reminder, or null if no reminder 481 */ 482 protected String ifStartsWithReturnRemainder(String prefix, String text) { 483 if (text.startsWith(prefix)) { 484 String remainder = text.substring(prefix.length()); 485 if (remainder.length() > 0) { 486 return remainder; 487 } 488 } 489 return null; 490 } 491 492 protected void registerExtension(ComponentExtension extension) { 493 extensions.add(() -> extension); 494 } 495 496 protected void registerExtension(Supplier<ComponentExtension> supplier) { 497 extensions.add(Suppliers.memorize(supplier)); 498 } 499 500 @Override 501 public Collection<Class<? extends ComponentExtension>> getSupportedExtensions() { 502 return extensions.stream() 503 .map(Supplier::get) 504 .map(ComponentExtension::getClass) 505 .collect(Collectors.toList()); 506 } 507 508 @Override 509 public <T extends ComponentExtension> Optional<T> getExtension(Class<T> extensionType) { 510 return extensions.stream() 511 .map(Supplier::get) 512 .filter(extensionType::isInstance) 513 .findFirst() 514 .map(extensionType::cast) 515 .map(e -> ComponentExtensionHelper.trySetComponent(e, this)) 516 .map(e -> ComponentExtensionHelper.trySetCamelContext(e, getCamelContext())); 517 } 518}