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