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    }