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