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