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