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 */ 017 package org.apache.camel.component.properties; 018 019 import java.io.Serializable; 020 import java.util.Arrays; 021 import java.util.Map; 022 import java.util.Properties; 023 import java.util.regex.Matcher; 024 import java.util.regex.Pattern; 025 026 import org.apache.camel.Endpoint; 027 import org.apache.camel.impl.DefaultComponent; 028 import org.apache.camel.util.LRUSoftCache; 029 import org.apache.camel.util.ObjectHelper; 030 import org.slf4j.Logger; 031 import org.slf4j.LoggerFactory; 032 033 /** 034 * The <a href="http://camel.apache.org/properties">properties</a> component. 035 * 036 * @version 037 */ 038 public class PropertiesComponent extends DefaultComponent { 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 * Key for stores special override properties that containers such as OSGi can store 066 * in the OSGi service registry 067 */ 068 public static final String OVERRIDE_PROPERTIES = PropertiesComponent.class.getName() + ".OverrideProperties"; 069 070 // must be non greedy patterns 071 private static final Pattern ENV_PATTERN = Pattern.compile("\\$\\{env:(.*?)\\}", Pattern.DOTALL); 072 private static final Pattern SYS_PATTERN = Pattern.compile("\\$\\{(.*?)\\}", Pattern.DOTALL); 073 074 private static final transient Logger LOG = LoggerFactory.getLogger(PropertiesComponent.class); 075 private final Map<CacheKey, Properties> cacheMap = new LRUSoftCache<CacheKey, Properties>(1000); 076 private PropertiesResolver propertiesResolver = new DefaultPropertiesResolver(); 077 private PropertiesParser propertiesParser = new DefaultPropertiesParser(); 078 private String[] locations; 079 private boolean ignoreMissingLocation; 080 private boolean cache = true; 081 private String propertyPrefix; 082 private String propertySuffix; 083 private boolean fallbackToUnaugmentedProperty = true; 084 private String prefixToken = DEFAULT_PREFIX_TOKEN; 085 private String suffixToken = DEFAULT_SUFFIX_TOKEN; 086 private Properties overrideProperties; 087 088 public PropertiesComponent() { 089 } 090 091 public PropertiesComponent(String location) { 092 setLocation(location); 093 } 094 095 public PropertiesComponent(String... locations) { 096 setLocations(locations); 097 } 098 099 @Override 100 protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { 101 String[] paths = locations; 102 103 // override default locations 104 String locations = getAndRemoveParameter(parameters, "locations", String.class); 105 Boolean ignoreMissingLocationLoc = getAndRemoveParameter(parameters, "ignoreMissingLocation", Boolean.class); 106 if (locations != null) { 107 LOG.trace("Overriding default locations with location: {}", locations); 108 paths = locations.split(","); 109 } 110 if (ignoreMissingLocationLoc != null) { 111 ignoreMissingLocation = ignoreMissingLocationLoc; 112 } 113 114 String endpointUri = parseUri(remaining, paths); 115 LOG.debug("Endpoint uri parsed as: {}", endpointUri); 116 return getCamelContext().getEndpoint(endpointUri); 117 } 118 119 public String parseUri(String uri) throws Exception { 120 return parseUri(uri, locations); 121 } 122 123 public String parseUri(String uri, String... paths) throws Exception { 124 Properties prop = null; 125 if (paths != null) { 126 // location may contain JVM system property or OS environment variables 127 // so we need to parse those 128 String[] locations = parseLocations(paths); 129 130 // check cache first 131 CacheKey key = new CacheKey(locations); 132 prop = cache ? cacheMap.get(key) : null; 133 if (prop == null) { 134 prop = propertiesResolver.resolveProperties(getCamelContext(), ignoreMissingLocation, locations); 135 if (cache) { 136 cacheMap.put(key, prop); 137 } 138 } 139 } 140 141 // use override properties 142 if (prop != null && overrideProperties != null) { 143 // make a copy to avoid affecting the original properties 144 Properties override = new Properties(); 145 override.putAll(prop); 146 override.putAll(overrideProperties); 147 prop = override; 148 } 149 150 // enclose tokens if missing 151 if (!uri.contains(prefixToken) && !uri.startsWith(prefixToken)) { 152 uri = prefixToken + uri; 153 } 154 if (!uri.contains(suffixToken) && !uri.endsWith(suffixToken)) { 155 uri = uri + suffixToken; 156 } 157 158 LOG.trace("Parsing uri {} with properties: {}", uri, prop); 159 160 if (propertiesParser instanceof AugmentedPropertyNameAwarePropertiesParser) { 161 return ((AugmentedPropertyNameAwarePropertiesParser) propertiesParser).parseUri(uri, prop, prefixToken, suffixToken, 162 propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty); 163 } else { 164 return propertiesParser.parseUri(uri, prop, prefixToken, suffixToken); 165 } 166 } 167 168 public String[] getLocations() { 169 return locations; 170 } 171 172 public void setLocations(String[] locations) { 173 this.locations = locations; 174 } 175 176 public void setLocation(String location) { 177 setLocations(location.split(",")); 178 } 179 180 public PropertiesResolver getPropertiesResolver() { 181 return propertiesResolver; 182 } 183 184 public void setPropertiesResolver(PropertiesResolver propertiesResolver) { 185 this.propertiesResolver = propertiesResolver; 186 } 187 188 public PropertiesParser getPropertiesParser() { 189 return propertiesParser; 190 } 191 192 public void setPropertiesParser(PropertiesParser propertiesParser) { 193 this.propertiesParser = propertiesParser; 194 } 195 196 public boolean isCache() { 197 return cache; 198 } 199 200 public void setCache(boolean cache) { 201 this.cache = cache; 202 } 203 204 public String getPropertyPrefix() { 205 return propertyPrefix; 206 } 207 208 public void setPropertyPrefix(String propertyPrefix) { 209 this.propertyPrefix = propertyPrefix; 210 } 211 212 public String getPropertySuffix() { 213 return propertySuffix; 214 } 215 216 public void setPropertySuffix(String propertySuffix) { 217 this.propertySuffix = propertySuffix; 218 } 219 220 public boolean isFallbackToUnaugmentedProperty() { 221 return fallbackToUnaugmentedProperty; 222 } 223 224 public void setFallbackToUnaugmentedProperty(boolean fallbackToUnaugmentedProperty) { 225 this.fallbackToUnaugmentedProperty = fallbackToUnaugmentedProperty; 226 } 227 228 public boolean isIgnoreMissingLocation() { 229 return ignoreMissingLocation; 230 } 231 232 public void setIgnoreMissingLocation(boolean ignoreMissingLocation) { 233 this.ignoreMissingLocation = ignoreMissingLocation; 234 } 235 236 public String getPrefixToken() { 237 return prefixToken; 238 } 239 240 /** 241 * Sets the value of the prefix token used to identify properties to replace. Setting a value of 242 * {@code null} restores the default token (@link {@link #DEFAULT_PREFIX_TOKEN}). 243 */ 244 public void setPrefixToken(String prefixToken) { 245 if (prefixToken == null) { 246 this.prefixToken = DEFAULT_PREFIX_TOKEN; 247 } else { 248 this.prefixToken = prefixToken; 249 } 250 } 251 252 public String getSuffixToken() { 253 return suffixToken; 254 } 255 256 /** 257 * Sets the value of the suffix token used to identify properties to replace. Setting a value of 258 * {@code null} restores the default token (@link {@link #DEFAULT_SUFFIX_TOKEN}). 259 */ 260 public void setSuffixToken(String suffixToken) { 261 if (suffixToken == null) { 262 this.suffixToken = DEFAULT_SUFFIX_TOKEN; 263 } else { 264 this.suffixToken = suffixToken; 265 } 266 } 267 268 public Properties getOverrideProperties() { 269 return overrideProperties; 270 } 271 272 /** 273 * Sets a special list of override properties that take precedence 274 * and will use first, if a property exist. 275 * 276 * @param overrideProperties properties that is used first 277 */ 278 public void setOverrideProperties(Properties overrideProperties) { 279 this.overrideProperties = overrideProperties; 280 } 281 282 @Override 283 protected void doStop() throws Exception { 284 cacheMap.clear(); 285 super.doStop(); 286 } 287 288 private String[] parseLocations(String[] locations) { 289 String[] answer = new String[locations.length]; 290 291 for (int i = 0; i < locations.length; i++) { 292 String location = locations[i]; 293 LOG.trace("Parsing location: {} ", location); 294 295 Matcher matcher = ENV_PATTERN.matcher(location); 296 while (matcher.find()) { 297 String key = matcher.group(1); 298 String value = System.getenv(key); 299 if (ObjectHelper.isEmpty(value)) { 300 throw new IllegalArgumentException("Cannot find system environment with key: " + key); 301 } 302 // must quote the replacement to have it work as literal replacement 303 value = Matcher.quoteReplacement(value); 304 location = matcher.replaceFirst(value); 305 // must match again as location is changed 306 matcher = ENV_PATTERN.matcher(location); 307 } 308 309 matcher = SYS_PATTERN.matcher(location); 310 while (matcher.find()) { 311 String key = matcher.group(1); 312 String value = System.getProperty(key); 313 if (ObjectHelper.isEmpty(value)) { 314 throw new IllegalArgumentException("Cannot find JVM system property with key: " + key); 315 } 316 // must quote the replacement to have it work as literal replacement 317 value = Matcher.quoteReplacement(value); 318 location = matcher.replaceFirst(value); 319 // must match again as location is changed 320 matcher = SYS_PATTERN.matcher(location); 321 } 322 323 LOG.debug("Parsed location: {} ", location); 324 answer[i] = location; 325 } 326 327 return answer; 328 } 329 330 /** 331 * Key used in the locations cache 332 */ 333 private static final class CacheKey implements Serializable { 334 private static final long serialVersionUID = 1L; 335 private final String[] locations; 336 337 private CacheKey(String[] locations) { 338 this.locations = locations; 339 } 340 341 @Override 342 public boolean equals(Object o) { 343 if (this == o) { 344 return true; 345 } 346 if (o == null || getClass() != o.getClass()) { 347 return false; 348 } 349 350 CacheKey that = (CacheKey) o; 351 352 if (!Arrays.equals(locations, that.locations)) { 353 return false; 354 } 355 356 return true; 357 } 358 359 @Override 360 public int hashCode() { 361 return locations != null ? Arrays.hashCode(locations) : 0; 362 } 363 364 @Override 365 public String toString() { 366 return "LocationKey[" + Arrays.asList(locations).toString() + "]"; 367 } 368 } 369 370 }