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