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