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    }