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 */
017package org.apache.camel.spring.spi;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.Properties;
022
023import org.apache.camel.CamelContext;
024import org.apache.camel.component.properties.AugmentedPropertyNameAwarePropertiesParser;
025import org.apache.camel.component.properties.PropertiesLocation;
026import org.apache.camel.component.properties.PropertiesParser;
027import org.apache.camel.component.properties.PropertiesResolver;
028import org.springframework.beans.BeansException;
029import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
030import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
031import org.springframework.core.Constants;
032import org.springframework.util.PropertyPlaceholderHelper;
033
034/**
035 * A {@link PropertyPlaceholderConfigurer} that bridges Camel's <a href="http://camel.apache.org/using-propertyplaceholder.html">
036 * property placeholder</a> with the Spring property placeholder mechanism.
037 */
038public class BridgePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements PropertiesResolver, AugmentedPropertyNameAwarePropertiesParser {
039
040    // NOTE: this class must be in the spi package as if its in the root package, then Spring fails to parse the XML
041    // files due some weird spring issue. But that is okay as having this class in the spi package is fine anyway.
042
043    private final Properties properties = new Properties();
044    private PropertiesResolver resolver;
045    private PropertiesParser parser;
046    private String id;
047    private PropertyPlaceholderHelper helper;
048
049    // to support both Spring 3.0 / 3.1+ we need to keep track of these as they have private modified in Spring 3.0
050    private String configuredPlaceholderPrefix;
051    private String configuredPlaceholderSuffix;
052    private String configuredValueSeparator;
053    private Boolean configuredIgnoreUnresolvablePlaceholders;
054    private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
055    private Boolean ignoreResourceNotFound;
056
057    public int getSystemPropertiesMode() {
058        return systemPropertiesMode;
059    }
060
061    @Override
062    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
063        super.processProperties(beanFactoryToProcess, props);
064        // store all the spring properties so we can refer to them later
065        properties.putAll(props);
066        // create helper
067        helper = new PropertyPlaceholderHelper(
068                configuredPlaceholderPrefix != null ? configuredPlaceholderPrefix : DEFAULT_PLACEHOLDER_PREFIX,
069                configuredPlaceholderSuffix != null ? configuredPlaceholderSuffix : DEFAULT_PLACEHOLDER_SUFFIX,
070                configuredValueSeparator != null ? configuredValueSeparator : DEFAULT_VALUE_SEPARATOR,
071                configuredIgnoreUnresolvablePlaceholders != null ? configuredIgnoreUnresolvablePlaceholders : false);
072    }
073
074    @Override
075    public void setBeanName(String beanName) {
076        this.id = beanName;
077        super.setBeanName(beanName);
078    }
079
080    @Override
081    public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException {
082        super.setSystemPropertiesModeName(constantName);
083        Constants constants = new Constants(PropertyPlaceholderConfigurer.class);
084        this.systemPropertiesMode = constants.asNumber(constantName).intValue();
085    }
086
087    @Override
088    public void setSystemPropertiesMode(int systemPropertiesMode) {
089        super.setSystemPropertiesMode(systemPropertiesMode);
090        this.systemPropertiesMode = systemPropertiesMode;
091    }
092
093    @Override
094    public void setPlaceholderPrefix(String placeholderPrefix) {
095        super.setPlaceholderPrefix(placeholderPrefix);
096        this.configuredPlaceholderPrefix = placeholderPrefix;
097    }
098
099    @Override
100    public void setPlaceholderSuffix(String placeholderSuffix) {
101        super.setPlaceholderSuffix(placeholderSuffix);
102        this.configuredPlaceholderSuffix = placeholderSuffix;
103    }
104
105    @Override
106    public void setValueSeparator(String valueSeparator) {
107        super.setValueSeparator(valueSeparator);
108        this.configuredValueSeparator = valueSeparator;
109    }
110
111    @Override
112    public void setIgnoreUnresolvablePlaceholders(boolean ignoreUnresolvablePlaceholders) {
113        super.setIgnoreUnresolvablePlaceholders(ignoreUnresolvablePlaceholders);
114        this.configuredIgnoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
115    }
116    
117    @Override
118    public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
119        super.setIgnoreResourceNotFound(ignoreResourceNotFound);
120        this.ignoreResourceNotFound = ignoreResourceNotFound;
121    }
122    
123    @Override
124    protected String resolvePlaceholder(String placeholder, Properties props) {
125        String value = props.getProperty(placeholder);
126        if (parser != null) {
127            // Just apply the parser to the place holder value to avoid configuring the other placeholder configure twice for the inside and outside camel context
128            return parser.parseProperty(placeholder, value, props);
129        } else {
130            return value;
131        }
132    }
133
134    @Override
135    public Properties resolveProperties(CamelContext context, boolean ignoreMissingLocation, List<PropertiesLocation> locations) throws Exception {
136        // return the spring properties, if it
137        Properties answer = new Properties();
138        for (PropertiesLocation location : locations) {
139            if ("ref".equals(location.getResolver()) && id.equals(location.getPath())) {
140                answer.putAll(properties);
141            } else if (resolver != null) {
142                boolean flag = ignoreMissingLocation;
143                // Override the setting by using ignoreResourceNotFound
144                if (ignoreResourceNotFound != null) {
145                    flag = ignoreResourceNotFound;
146                }
147                Properties p = resolver.resolveProperties(context, flag, Collections.singletonList(location));
148                if (p != null) {
149                    answer.putAll(p);
150                }
151            }
152        }
153        // must not return null
154        return answer;
155    }
156
157    @Override
158    public String parseUri(String text, Properties properties, String prefixToken, String suffixToken,
159                           String propertyPrefix, String propertySuffix, boolean fallbackToUnaugmentedProperty, boolean defaultFallbackEnabled) throws IllegalArgumentException {
160
161        // first let Camel parse the text as it may contain Camel placeholders
162        String answer;
163        if (parser instanceof AugmentedPropertyNameAwarePropertiesParser) {
164            answer = ((AugmentedPropertyNameAwarePropertiesParser) parser).parseUri(text, properties, prefixToken, suffixToken,
165                    propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty, defaultFallbackEnabled);
166        } else {
167            answer = parser.parseUri(text, properties, prefixToken, suffixToken);
168        }
169
170        // then let Spring parse it to resolve any Spring placeholders
171        if (answer != null) {
172            answer = springResolvePlaceholders(answer, properties);
173        } else {
174            answer = springResolvePlaceholders(text, properties);
175        }
176        return answer;
177    }
178
179    @Override
180    public String parseUri(String text, Properties properties, String prefixToken, String suffixToken) throws IllegalArgumentException {
181        String answer = parser.parseUri(text, properties, prefixToken, suffixToken);
182        if (answer != null) {
183            answer = springResolvePlaceholders(answer, properties);
184        } else {
185            answer = springResolvePlaceholders(text, properties);
186        }
187        return answer;
188    }
189
190    @Override
191    public String parseProperty(String key, String value, Properties properties) {
192        String answer = parser.parseProperty(key, value, properties);
193        if (answer != null) {
194            answer = springResolvePlaceholders(answer, properties);
195        } else {
196            answer = springResolvePlaceholders(value, properties);
197        }
198        return answer;
199    }
200
201    /**
202     * Resolves the placeholders using Spring's property placeholder functionality.
203     *
204     * @param text   the text which may contain spring placeholders
205     * @param properties the properties
206     * @return the parsed text with replaced placeholders, or the original text as is
207     */
208    protected String springResolvePlaceholders(String text, Properties properties) {
209        return helper.replacePlaceholders(text, new BridgePropertyPlaceholderResolver(properties));
210    }
211
212    public void setResolver(PropertiesResolver resolver) {
213        this.resolver = resolver;
214    }
215
216    public void setParser(PropertiesParser parser) {
217        if (this.parser != null) {
218            // use a bridge if there is already a parser configured
219            this.parser = new BridgePropertiesParser(this.parser, parser);
220        } else {
221            this.parser = parser;
222        }
223    }
224
225    private class BridgePropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver {
226
227        private final Properties properties;
228
229        BridgePropertyPlaceholderResolver(Properties properties) {
230            this.properties = properties;
231        }
232
233        public String resolvePlaceholder(String placeholderName) {
234            String propVal = null;
235            if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
236                propVal = resolveSystemProperty(placeholderName);
237            }
238            if (propVal == null) {
239                propVal = (String) properties.get(placeholderName);
240            }
241            if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
242                propVal = resolveSystemProperty(placeholderName);
243            }
244            return propVal;
245        }
246    }
247
248    private final class BridgePropertiesParser implements PropertiesParser, AugmentedPropertyNameAwarePropertiesParser {
249
250        private final PropertiesParser delegate;
251        private final PropertiesParser parser;
252
253        private BridgePropertiesParser(PropertiesParser delegate, PropertiesParser parser) {
254            this.delegate = delegate;
255            this.parser = parser;
256        }
257
258        @Override
259        public String parseUri(String text, Properties properties, String prefixToken, String suffixToken, String propertyPrefix, String propertySuffix,
260                               boolean fallbackToUnaugmentedProperty, boolean defaultFallbackEnabled) throws IllegalArgumentException {
261            String answer = null;
262            if (delegate != null) {
263                if (delegate instanceof AugmentedPropertyNameAwarePropertiesParser) {
264                    answer = ((AugmentedPropertyNameAwarePropertiesParser)this.delegate).parseUri(text, properties,
265                        prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty, defaultFallbackEnabled);
266                } else {
267                    answer = delegate.parseUri(text, properties, prefixToken, suffixToken);
268                }
269            }
270            if (answer != null) {
271                text = answer;
272            }
273            if (parser instanceof AugmentedPropertyNameAwarePropertiesParser) {
274                answer = ((AugmentedPropertyNameAwarePropertiesParser)this.parser).parseUri(text, properties,
275                    prefixToken, suffixToken, propertyPrefix, propertySuffix, fallbackToUnaugmentedProperty, defaultFallbackEnabled);
276            } else {
277                answer = parser.parseUri(text, properties, prefixToken, suffixToken);
278            }
279            return answer;
280        }
281
282        @Override
283        public String parseUri(String text, Properties properties, String prefixToken, String suffixToken) throws IllegalArgumentException {
284            String answer = null;
285            if (delegate != null) {
286                answer = delegate.parseUri(text, properties, prefixToken, suffixToken);
287            }
288            if (answer != null) {
289                text = answer;
290            }
291            return parser.parseUri(text, properties, prefixToken, suffixToken);
292        }
293
294        @Override
295        public String parseProperty(String key, String value, Properties properties) {
296            String answer = null;
297            if (delegate != null) {
298                answer = delegate.parseProperty(key, value, properties);
299            }
300            if (answer != null) {
301                value = answer;
302            }
303            return parser.parseProperty(key, value, properties);
304        }
305    }
306
307}