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