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