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.component.properties;
018
019import java.io.BufferedReader;
020import java.io.FileInputStream;
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.InputStreamReader;
025import java.io.Reader;
026import java.util.Map;
027import java.util.Properties;
028
029import org.apache.camel.CamelContext;
030import org.apache.camel.util.IOHelper;
031import org.apache.camel.util.ObjectHelper;
032
033/**
034 * Default {@link org.apache.camel.component.properties.PropertiesResolver} which can resolve properties
035 * from file and classpath.
036 * <p/>
037 * You can denote <tt>classpath:</tt> or <tt>file:</tt> as prefix in the uri to select whether the file
038 * is located in the classpath or on the file system.
039 *
040 * @version 
041 */
042public class DefaultPropertiesResolver implements PropertiesResolver {
043
044    private final PropertiesComponent propertiesComponent;
045
046    public DefaultPropertiesResolver(PropertiesComponent propertiesComponent) {
047        this.propertiesComponent = propertiesComponent;
048    }
049
050    public Properties resolveProperties(CamelContext context, boolean ignoreMissingLocation, String... uri) throws Exception {
051        Properties answer = new Properties();
052
053        for (String path : uri) {
054            if (path.startsWith("ref:")) {
055                Properties prop = loadPropertiesFromRegistry(context, ignoreMissingLocation, path);
056                prop = prepareLoadedProperties(prop);
057                answer.putAll(prop);
058            } else if (path.startsWith("file:")) {
059                Properties prop = loadPropertiesFromFilePath(context, ignoreMissingLocation, path);
060                prop = prepareLoadedProperties(prop);
061                answer.putAll(prop);
062            } else {
063                // default to classpath
064                Properties prop = loadPropertiesFromClasspath(context, ignoreMissingLocation, path);
065                prop = prepareLoadedProperties(prop);
066                answer.putAll(prop);
067            }
068        }
069
070        return answer;
071    }
072
073    protected Properties loadPropertiesFromFilePath(CamelContext context, boolean ignoreMissingLocation, String path) throws IOException {
074        Properties answer = new Properties();
075
076        if (path.startsWith("file:")) {
077            path = ObjectHelper.after(path, "file:");
078        }
079
080        InputStream is = null;
081        Reader reader = null;
082        try {
083            is = new FileInputStream(path);
084            if (propertiesComponent.getEncoding() != null) {
085                reader = new BufferedReader(new InputStreamReader(is, propertiesComponent.getEncoding()));
086                answer.load(reader);
087            } else {
088                answer.load(is);
089            }
090        } catch (FileNotFoundException e) {
091            if (!ignoreMissingLocation) {
092                throw e;
093            }
094        } finally {
095            IOHelper.close(reader, is);
096        }
097
098        return answer;
099    }
100
101    protected Properties loadPropertiesFromClasspath(CamelContext context, boolean ignoreMissingLocation, String path) throws IOException {
102        Properties answer = new Properties();
103
104        if (path.startsWith("classpath:")) {
105            path = ObjectHelper.after(path, "classpath:");
106        }
107
108        InputStream is = context.getClassResolver().loadResourceAsStream(path);
109        Reader reader = null;
110        if (is == null) {
111            if (!ignoreMissingLocation) {
112                throw new FileNotFoundException("Properties file " + path + " not found in classpath");
113            }
114        } else {
115            try {
116                if (propertiesComponent.getEncoding() != null) {
117                    reader = new BufferedReader(new InputStreamReader(is, propertiesComponent.getEncoding()));
118                    answer.load(reader);
119                } else {
120                    answer.load(is);
121                }
122            } finally {
123                IOHelper.close(reader, is);
124            }
125        }
126        return answer;
127    }
128
129    @SuppressWarnings({"rawtypes", "unchecked"})
130    protected Properties loadPropertiesFromRegistry(CamelContext context, boolean ignoreMissingLocation, String path) throws IOException {
131        if (path.startsWith("ref:")) {
132            path = ObjectHelper.after(path, "ref:");
133        }
134        Properties answer;
135        try {
136            answer = context.getRegistry().lookupByNameAndType(path, Properties.class);
137        } catch (Exception ex) {
138            // just look up the Map as a fault back
139            Map map = context.getRegistry().lookupByNameAndType(path, Map.class);
140            answer = new Properties();
141            answer.putAll(map);
142        }
143        if (answer == null && (!ignoreMissingLocation)) {
144            throw new FileNotFoundException("Properties " + path + " not found in registry");
145        }
146        return answer != null ? answer : new Properties();
147    }
148
149    /**
150     * Strategy to prepare loaded properties before being used by Camel.
151     * <p/>
152     * This implementation will ensure values are trimmed, as loading properties from
153     * a file with values having trailing spaces is not automatic trimmed by the Properties API
154     * from the JDK.
155     *
156     * @param properties  the properties
157     * @return the prepared properties
158     */
159    protected Properties prepareLoadedProperties(Properties properties) {
160        Properties answer = new Properties();
161        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
162            Object key = entry.getKey();
163            Object value = entry.getValue();
164            if (value instanceof String) {
165                String s = (String) value;
166
167                // trim any trailing spaces which can be a problem when loading from
168                // a properties file, note that java.util.Properties does already this
169                // for any potential leading spaces so there's nothing to do there
170                value = trimTrailingWhitespaces(s);
171            }
172            answer.put(key, value);
173        }
174        return answer;
175    }
176
177    private static String trimTrailingWhitespaces(String s) {
178        int endIndex = s.length();
179        for (int index = s.length() - 1; index >= 0; index--) {
180            if (s.charAt(index) == ' ') {
181                endIndex = index;
182            } else {
183                break;
184            }
185        }
186        String answer = s.substring(0, endIndex);
187        return answer;
188    }
189
190}