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