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 setLocations(location.split(",")); 250 } 251 252 public String getEncoding() { 253 return encoding; 254 } 255 256 /** 257 * Encoding to use when loading properties file from the file system or classpath. 258 * <p/> 259 * If no encoding has been set, then the properties files is loaded using ISO-8859-1 encoding (latin-1) 260 * as documented by {@link java.util.Properties#load(java.io.InputStream)} 261 */ 262 public void setEncoding(String encoding) { 263 this.encoding = encoding; 264 } 265 266 public PropertiesResolver getPropertiesResolver() { 267 return propertiesResolver; 268 } 269 270 /** 271 * To use a custom PropertiesResolver 272 */ 273 public void setPropertiesResolver(PropertiesResolver propertiesResolver) { 274 this.propertiesResolver = propertiesResolver; 275 } 276 277 public PropertiesParser getPropertiesParser() { 278 return propertiesParser; 279 } 280 281 /** 282 * To use a custom PropertiesParser 283 */ 284 public void setPropertiesParser(PropertiesParser propertiesParser) { 285 this.propertiesParser = propertiesParser; 286 } 287 288 public boolean isCache() { 289 return cache; 290 } 291 292 /** 293 * Whether or not to cache loaded properties. The default value is true. 294 */ 295 public void setCache(boolean cache) { 296 this.cache = cache; 297 } 298 299 public String getPropertyPrefix() { 300 return propertyPrefix; 301 } 302 303 /** 304 * Optional prefix prepended to property names before resolution. 305 */ 306 public void setPropertyPrefix(String propertyPrefix) { 307 this.propertyPrefix = propertyPrefix; 308 this.propertyPrefixResolved = propertyPrefix; 309 if (ObjectHelper.isNotEmpty(this.propertyPrefix)) { 310 this.propertyPrefixResolved = FilePathResolver.resolvePath(this.propertyPrefix); 311 } 312 } 313 314 public String getPropertySuffix() { 315 return propertySuffix; 316 } 317 318 /** 319 * Optional suffix appended to property names before resolution. 320 */ 321 public void setPropertySuffix(String propertySuffix) { 322 this.propertySuffix = propertySuffix; 323 this.propertySuffixResolved = propertySuffix; 324 if (ObjectHelper.isNotEmpty(this.propertySuffix)) { 325 this.propertySuffixResolved = FilePathResolver.resolvePath(this.propertySuffix); 326 } 327 } 328 329 public boolean isFallbackToUnaugmentedProperty() { 330 return fallbackToUnaugmentedProperty; 331 } 332 333 /** 334 * If true, first attempt resolution of property name augmented with propertyPrefix and propertySuffix 335 * before falling back the plain property name specified. If false, only the augmented property name is searched. 336 */ 337 public void setFallbackToUnaugmentedProperty(boolean fallbackToUnaugmentedProperty) { 338 this.fallbackToUnaugmentedProperty = fallbackToUnaugmentedProperty; 339 } 340 341 public boolean isIgnoreMissingLocation() { 342 return ignoreMissingLocation; 343 } 344 345 /** 346 * Whether to silently ignore if a location cannot be located, such as a properties file not found. 347 */ 348 public void setIgnoreMissingLocation(boolean ignoreMissingLocation) { 349 this.ignoreMissingLocation = ignoreMissingLocation; 350 } 351 352 public String getPrefixToken() { 353 return prefixToken; 354 } 355 356 /** 357 * Sets the value of the prefix token used to identify properties to replace. Setting a value of 358 * {@code null} restores the default token (@link {@link #DEFAULT_PREFIX_TOKEN}). 359 */ 360 public void setPrefixToken(String prefixToken) { 361 if (prefixToken == null) { 362 this.prefixToken = DEFAULT_PREFIX_TOKEN; 363 } else { 364 this.prefixToken = prefixToken; 365 } 366 } 367 368 public String getSuffixToken() { 369 return suffixToken; 370 } 371 372 /** 373 * Sets the value of the suffix token used to identify properties to replace. Setting a value of 374 * {@code null} restores the default token (@link {@link #DEFAULT_SUFFIX_TOKEN}). 375 */ 376 public void setSuffixToken(String suffixToken) { 377 if (suffixToken == null) { 378 this.suffixToken = DEFAULT_SUFFIX_TOKEN; 379 } else { 380 this.suffixToken = suffixToken; 381 } 382 } 383 384 public Properties getInitialProperties() { 385 return initialProperties; 386 } 387 388 /** 389 * Sets initial properties which will be used before any locations are resolved. 390 * 391 * @param initialProperties properties that are added first 392 */ 393 public void setInitialProperties(Properties initialProperties) { 394 this.initialProperties = initialProperties; 395 } 396 397 public Properties getOverrideProperties() { 398 return overrideProperties; 399 } 400 401 /** 402 * Sets a special list of override properties that take precedence 403 * and will use first, if a property exist. 404 * 405 * @param overrideProperties properties that is used first 406 */ 407 public void setOverrideProperties(Properties overrideProperties) { 408 this.overrideProperties = overrideProperties; 409 } 410 411 /** 412 * Gets the functions registered in this properties component. 413 */ 414 public Map<String, PropertiesFunction> getFunctions() { 415 return functions; 416 } 417 418 /** 419 * Registers the {@link org.apache.camel.component.properties.PropertiesFunction} as a function to this component. 420 */ 421 public void addFunction(PropertiesFunction function) { 422 this.functions.put(function.getName(), function); 423 } 424 425 /** 426 * Is there a {@link org.apache.camel.component.properties.PropertiesFunction} with the given name? 427 */ 428 public boolean hasFunction(String name) { 429 return functions.containsKey(name); 430 } 431 432 public int getSystemPropertiesMode() { 433 return systemPropertiesMode; 434 } 435 436 /** 437 * Sets the system property mode. 438 * 439 * @see #SYSTEM_PROPERTIES_MODE_NEVER 440 * @see #SYSTEM_PROPERTIES_MODE_FALLBACK 441 * @see #SYSTEM_PROPERTIES_MODE_OVERRIDE 442 */ 443 public void setSystemPropertiesMode(int systemPropertiesMode) { 444 this.systemPropertiesMode = systemPropertiesMode; 445 } 446 447 @Override 448 protected void doStart() throws Exception { 449 super.doStart(); 450 451 if (systemPropertiesMode != SYSTEM_PROPERTIES_MODE_NEVER 452 && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_FALLBACK 453 && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_OVERRIDE) { 454 throw new IllegalArgumentException("Option systemPropertiesMode has invalid value: " + systemPropertiesMode); 455 } 456 457 // inject the component to the parser 458 if (propertiesParser instanceof DefaultPropertiesParser) { 459 ((DefaultPropertiesParser) propertiesParser).setPropertiesComponent(this); 460 } 461 } 462 463 @Override 464 protected void doStop() throws Exception { 465 cacheMap.clear(); 466 super.doStop(); 467 } 468 469 private String[] parseLocations(String[] locations) { 470 List<String> answer = new ArrayList<String>(); 471 472 for (String location : locations) { 473 LOG.trace("Parsing location: {} ", location); 474 475 try { 476 location = FilePathResolver.resolvePath(location); 477 LOG.debug("Parsed location: {} ", location); 478 if (ObjectHelper.isNotEmpty(location)) { 479 answer.add(location); 480 } 481 } catch (IllegalArgumentException e) { 482 if (!ignoreMissingLocation) { 483 throw e; 484 } else { 485 LOG.debug("Ignored missing location: {}", location); 486 } 487 } 488 } 489 490 // must return a not-null answer 491 return answer.toArray(new String[answer.size()]); 492 } 493 494 /** 495 * Key used in the locations cache 496 */ 497 private static final class CacheKey implements Serializable { 498 private static final long serialVersionUID = 1L; 499 private final String[] locations; 500 501 private CacheKey(String[] locations) { 502 this.locations = locations; 503 } 504 505 @Override 506 public boolean equals(Object o) { 507 if (this == o) { 508 return true; 509 } 510 if (o == null || getClass() != o.getClass()) { 511 return false; 512 } 513 514 CacheKey that = (CacheKey) o; 515 516 if (!Arrays.equals(locations, that.locations)) { 517 return false; 518 } 519 520 return true; 521 } 522 523 @Override 524 public int hashCode() { 525 return locations != null ? Arrays.hashCode(locations) : 0; 526 } 527 528 @Override 529 public String toString() { 530 return "LocationKey[" + Arrays.asList(locations).toString() + "]"; 531 } 532 } 533 534}