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.component.properties; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Properties; 026 027import org.apache.camel.Endpoint; 028import org.apache.camel.impl.UriEndpointComponent; 029import org.apache.camel.util.FilePathResolver; 030import org.apache.camel.util.LRUSoftCache; 031import org.apache.camel.util.ObjectHelper; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * The <a href="http://camel.apache.org/properties">Properties Component</a> allows you to use property placeholders when defining Endpoint URIs 037 */ 038public class PropertiesComponent extends UriEndpointComponent { 039 040 /** 041 * The default prefix token. 042 */ 043 public static final String DEFAULT_PREFIX_TOKEN = "{{"; 044 045 /** 046 * The default suffix token. 047 */ 048 public static final String DEFAULT_SUFFIX_TOKEN = "}}"; 049 050 /** 051 * The default prefix token. 052 * @deprecated Use {@link #DEFAULT_PREFIX_TOKEN} instead. 053 */ 054 @Deprecated 055 public static final String PREFIX_TOKEN = DEFAULT_PREFIX_TOKEN; 056 057 /** 058 * The default suffix token. 059 * @deprecated Use {@link #DEFAULT_SUFFIX_TOKEN} instead. 060 */ 061 @Deprecated 062 public static final String SUFFIX_TOKEN = DEFAULT_SUFFIX_TOKEN; 063 064 /** 065 * Never check system properties. 066 */ 067 public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0; 068 069 /** 070 * Check system properties if not resolvable in the specified properties. 071 */ 072 public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1; 073 074 /** 075 * Check system properties first, before trying the specified properties. 076 * This allows system properties to override any other property source. 077 * <p/> 078 * This is the default. 079 */ 080 public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2; 081 082 /** 083 * Key for stores special override properties that containers such as OSGi can store 084 * in the OSGi service registry 085 */ 086 public static final String OVERRIDE_PROPERTIES = PropertiesComponent.class.getName() + ".OverrideProperties"; 087 088 private static final Logger LOG = LoggerFactory.getLogger(PropertiesComponent.class); 089 private final Map<CacheKey, Properties> cacheMap = new LRUSoftCache<CacheKey, Properties>(1000); 090 private final Map<String, PropertiesFunction> functions = new HashMap<String, PropertiesFunction>(); 091 private PropertiesResolver propertiesResolver = new DefaultPropertiesResolver(this); 092 private PropertiesParser propertiesParser = new DefaultPropertiesParser(this); 093 private boolean isDefaultCreated; 094 private String[] locations; 095 private boolean ignoreMissingLocation; 096 private String encoding; 097 private boolean cache = true; 098 private String propertyPrefix; 099 private String propertyPrefixResolved; 100 private String propertySuffix; 101 private String propertySuffixResolved; 102 private boolean fallbackToUnaugmentedProperty = true; 103 private String prefixToken = DEFAULT_PREFIX_TOKEN; 104 private String suffixToken = DEFAULT_SUFFIX_TOKEN; 105 private Properties initialProperties; 106 private Properties overrideProperties; 107 private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_OVERRIDE; 108 109 public PropertiesComponent() { 110 super(PropertiesEndpoint.class); 111 // include out of the box functions 112 addFunction(new EnvPropertiesFunction()); 113 addFunction(new SysPropertiesFunction()); 114 addFunction(new ServicePropertiesFunction()); 115 addFunction(new ServiceHostPropertiesFunction()); 116 addFunction(new ServicePortPropertiesFunction()); 117 } 118 119 public PropertiesComponent(boolean isDefaultCreated) { 120 this(); 121 this.isDefaultCreated = isDefaultCreated; 122 } 123 124 public PropertiesComponent(String location) { 125 this(); 126 setLocation(location); 127 } 128 129 public PropertiesComponent(String... locations) { 130 this(); 131 setLocations(locations); 132 } 133 134 @Override 135 protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { 136 String[] paths = locations; 137 138 // override default locations 139 String locations = getAndRemoveParameter(parameters, "locations", String.class); 140 Boolean ignoreMissingLocationLoc = getAndRemoveParameter(parameters, "ignoreMissingLocation", Boolean.class); 141 if (locations != null) { 142 LOG.trace("Overriding default locations with location: {}", locations); 143 paths = locations.split(","); 144 } 145 if (ignoreMissingLocationLoc != null) { 146 ignoreMissingLocation = ignoreMissingLocationLoc; 147 } 148 149 String endpointUri = parseUri(remaining, paths); 150 LOG.debug("Endpoint uri parsed as: {}", endpointUri); 151 152 Endpoint delegate = getCamelContext().getEndpoint(endpointUri); 153 PropertiesEndpoint answer = new PropertiesEndpoint(uri, delegate, this); 154 155 setProperties(answer, parameters); 156 return answer; 157 } 158 159 public String parseUri(String uri) throws Exception { 160 return parseUri(uri, locations); 161 } 162 163 public String parseUri(String uri, String... paths) throws Exception { 164 Properties prop = new Properties(); 165 166 // use initial properties 167 if (null != initialProperties) { 168 prop.putAll(initialProperties); 169 } 170 171 // use locations 172 if (paths != null) { 173 // location may contain JVM system property or OS environment variables 174 // so we need to parse those 175 String[] locations = parseLocations(paths); 176 177 // check cache first 178 CacheKey key = new CacheKey(locations); 179 Properties locationsProp = cache ? cacheMap.get(key) : null; 180 if (locationsProp == null) { 181 locationsProp = propertiesResolver.resolveProperties(getCamelContext(), ignoreMissingLocation, locations); 182 if (cache) { 183 cacheMap.put(key, locationsProp); 184 } 185 } 186 prop.putAll(locationsProp); 187 } 188 189 // use override properties 190 if (overrideProperties != null) { 191 // make a copy to avoid affecting the original properties 192 Properties override = new Properties(); 193 override.putAll(prop); 194 override.putAll(overrideProperties); 195 prop = override; 196 } 197 198 // enclose tokens if missing 199 if (!uri.contains(prefixToken) && !uri.startsWith(prefixToken)) { 200 uri = prefixToken + uri; 201 } 202 if (!uri.contains(suffixToken) && !uri.endsWith(suffixToken)) { 203 uri = uri + suffixToken; 204 } 205 206 LOG.trace("Parsing uri {} with properties: {}", uri, prop); 207 208 if (propertiesParser instanceof AugmentedPropertyNameAwarePropertiesParser) { 209 return ((AugmentedPropertyNameAwarePropertiesParser) propertiesParser).parseUri(uri, prop, prefixToken, suffixToken, 210 propertyPrefixResolved, propertySuffixResolved, fallbackToUnaugmentedProperty); 211 } else { 212 return propertiesParser.parseUri(uri, prop, prefixToken, suffixToken); 213 } 214 } 215 216 /** 217 * Is this component created as a default by {@link org.apache.camel.CamelContext} during starting up Camel. 218 */ 219 public boolean isDefaultCreated() { 220 return isDefaultCreated; 221 } 222 223 public String[] getLocations() { 224 return locations; 225 } 226 227 /** 228 * A list of locations to load properties. You can use comma to separate multiple locations. 229 * This option will override any default locations and only use the locations from this option. 230 */ 231 public void setLocations(String[] locations) { 232 // make sure to trim as people may use new lines when configuring using XML 233 // and do this in the setter as Spring/Blueprint resolves placeholders before Camel is being started 234 if (locations != null && locations.length > 0) { 235 for (int i = 0; i < locations.length; i++) { 236 String loc = locations[i]; 237 locations[i] = loc.trim(); 238 } 239 } 240 241 this.locations = locations; 242 } 243 244 /** 245 * A list of locations to load properties. You can use comma to separate multiple locations. 246 * This option will override any default locations and only use the locations from this option. 247 */ 248 public void setLocation(String location) { 249 if (location != null) { 250 setLocations(location.split(",")); 251 } 252 } 253 254 public String getEncoding() { 255 return encoding; 256 } 257 258 /** 259 * Encoding to use when loading properties file from the file system or classpath. 260 * <p/> 261 * If no encoding has been set, then the properties files is loaded using ISO-8859-1 encoding (latin-1) 262 * as documented by {@link java.util.Properties#load(java.io.InputStream)} 263 */ 264 public void setEncoding(String encoding) { 265 this.encoding = encoding; 266 } 267 268 public PropertiesResolver getPropertiesResolver() { 269 return propertiesResolver; 270 } 271 272 /** 273 * To use a custom PropertiesResolver 274 */ 275 public void setPropertiesResolver(PropertiesResolver propertiesResolver) { 276 this.propertiesResolver = propertiesResolver; 277 } 278 279 public PropertiesParser getPropertiesParser() { 280 return propertiesParser; 281 } 282 283 /** 284 * To use a custom PropertiesParser 285 */ 286 public void setPropertiesParser(PropertiesParser propertiesParser) { 287 this.propertiesParser = propertiesParser; 288 } 289 290 public boolean isCache() { 291 return cache; 292 } 293 294 /** 295 * Whether or not to cache loaded properties. The default value is true. 296 */ 297 public void setCache(boolean cache) { 298 this.cache = cache; 299 } 300 301 public String getPropertyPrefix() { 302 return propertyPrefix; 303 } 304 305 /** 306 * Optional prefix prepended to property names before resolution. 307 */ 308 public void setPropertyPrefix(String propertyPrefix) { 309 this.propertyPrefix = propertyPrefix; 310 this.propertyPrefixResolved = propertyPrefix; 311 if (ObjectHelper.isNotEmpty(this.propertyPrefix)) { 312 this.propertyPrefixResolved = FilePathResolver.resolvePath(this.propertyPrefix); 313 } 314 } 315 316 public String getPropertySuffix() { 317 return propertySuffix; 318 } 319 320 /** 321 * Optional suffix appended to property names before resolution. 322 */ 323 public void setPropertySuffix(String propertySuffix) { 324 this.propertySuffix = propertySuffix; 325 this.propertySuffixResolved = propertySuffix; 326 if (ObjectHelper.isNotEmpty(this.propertySuffix)) { 327 this.propertySuffixResolved = FilePathResolver.resolvePath(this.propertySuffix); 328 } 329 } 330 331 public boolean isFallbackToUnaugmentedProperty() { 332 return fallbackToUnaugmentedProperty; 333 } 334 335 /** 336 * If true, first attempt resolution of property name augmented with propertyPrefix and propertySuffix 337 * before falling back the plain property name specified. If false, only the augmented property name is searched. 338 */ 339 public void setFallbackToUnaugmentedProperty(boolean fallbackToUnaugmentedProperty) { 340 this.fallbackToUnaugmentedProperty = fallbackToUnaugmentedProperty; 341 } 342 343 public boolean isIgnoreMissingLocation() { 344 return ignoreMissingLocation; 345 } 346 347 /** 348 * Whether to silently ignore if a location cannot be located, such as a properties file not found. 349 */ 350 public void setIgnoreMissingLocation(boolean ignoreMissingLocation) { 351 this.ignoreMissingLocation = ignoreMissingLocation; 352 } 353 354 public String getPrefixToken() { 355 return prefixToken; 356 } 357 358 /** 359 * Sets the value of the prefix token used to identify properties to replace. Setting a value of 360 * {@code null} restores the default token (@link {@link #DEFAULT_PREFIX_TOKEN}). 361 */ 362 public void setPrefixToken(String prefixToken) { 363 if (prefixToken == null) { 364 this.prefixToken = DEFAULT_PREFIX_TOKEN; 365 } else { 366 this.prefixToken = prefixToken; 367 } 368 } 369 370 public String getSuffixToken() { 371 return suffixToken; 372 } 373 374 /** 375 * Sets the value of the suffix token used to identify properties to replace. Setting a value of 376 * {@code null} restores the default token (@link {@link #DEFAULT_SUFFIX_TOKEN}). 377 */ 378 public void setSuffixToken(String suffixToken) { 379 if (suffixToken == null) { 380 this.suffixToken = DEFAULT_SUFFIX_TOKEN; 381 } else { 382 this.suffixToken = suffixToken; 383 } 384 } 385 386 public Properties getInitialProperties() { 387 return initialProperties; 388 } 389 390 /** 391 * Sets initial properties which will be used before any locations are resolved. 392 * 393 * @param initialProperties properties that are added first 394 */ 395 public void setInitialProperties(Properties initialProperties) { 396 this.initialProperties = initialProperties; 397 } 398 399 public Properties getOverrideProperties() { 400 return overrideProperties; 401 } 402 403 /** 404 * Sets a special list of override properties that take precedence 405 * and will use first, if a property exist. 406 * 407 * @param overrideProperties properties that is used first 408 */ 409 public void setOverrideProperties(Properties overrideProperties) { 410 this.overrideProperties = overrideProperties; 411 } 412 413 /** 414 * Gets the functions registered in this properties component. 415 */ 416 public Map<String, PropertiesFunction> getFunctions() { 417 return functions; 418 } 419 420 /** 421 * Registers the {@link org.apache.camel.component.properties.PropertiesFunction} as a function to this component. 422 */ 423 public void addFunction(PropertiesFunction function) { 424 this.functions.put(function.getName(), function); 425 } 426 427 /** 428 * Is there a {@link org.apache.camel.component.properties.PropertiesFunction} with the given name? 429 */ 430 public boolean hasFunction(String name) { 431 return functions.containsKey(name); 432 } 433 434 public int getSystemPropertiesMode() { 435 return systemPropertiesMode; 436 } 437 438 /** 439 * Sets the system property mode. 440 * 441 * @see #SYSTEM_PROPERTIES_MODE_NEVER 442 * @see #SYSTEM_PROPERTIES_MODE_FALLBACK 443 * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE 444 */ 445 public void setSystemPropertiesMode(int systemPropertiesMode) { 446 this.systemPropertiesMode = systemPropertiesMode; 447 } 448 449 @Override 450 protected void doStart() throws Exception { 451 super.doStart(); 452 453 if (systemPropertiesMode != SYSTEM_PROPERTIES_MODE_NEVER 454 && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_FALLBACK 455 && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_OVERRIDE) { 456 throw new IllegalArgumentException("Option systemPropertiesMode has invalid value: " + systemPropertiesMode); 457 } 458 459 // inject the component to the parser 460 if (propertiesParser instanceof DefaultPropertiesParser) { 461 ((DefaultPropertiesParser) propertiesParser).setPropertiesComponent(this); 462 } 463 } 464 465 @Override 466 protected void doStop() throws Exception { 467 cacheMap.clear(); 468 super.doStop(); 469 } 470 471 private String[] parseLocations(String[] locations) { 472 List<String> answer = new ArrayList<String>(); 473 474 for (String location : locations) { 475 LOG.trace("Parsing location: {} ", location); 476 477 try { 478 location = FilePathResolver.resolvePath(location); 479 LOG.debug("Parsed location: {} ", location); 480 if (ObjectHelper.isNotEmpty(location)) { 481 answer.add(location); 482 } 483 } catch (IllegalArgumentException e) { 484 if (!ignoreMissingLocation) { 485 throw e; 486 } else { 487 LOG.debug("Ignored missing location: {}", location); 488 } 489 } 490 } 491 492 // must return a not-null answer 493 return answer.toArray(new String[answer.size()]); 494 } 495 496 /** 497 * Key used in the locations cache 498 */ 499 private static final class CacheKey implements Serializable { 500 private static final long serialVersionUID = 1L; 501 private final String[] locations; 502 503 private CacheKey(String[] locations) { 504 this.locations = locations; 505 } 506 507 @Override 508 public boolean equals(Object o) { 509 if (this == o) { 510 return true; 511 } 512 if (o == null || getClass() != o.getClass()) { 513 return false; 514 } 515 516 CacheKey that = (CacheKey) o; 517 518 if (!Arrays.equals(locations, that.locations)) { 519 return false; 520 } 521 522 return true; 523 } 524 525 @Override 526 public int hashCode() { 527 return locations != null ? Arrays.hashCode(locations) : 0; 528 } 529 530 @Override 531 public String toString() { 532 return "LocationKey[" + Arrays.asList(locations).toString() + "]"; 533 } 534 } 535 536}