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.List;
027import java.util.Map;
028import java.util.Properties;
029
030import org.apache.camel.CamelContext;
031import org.apache.camel.util.IOHelper;
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, List<PropertiesLocation> locations) throws Exception {
051        Properties answer = new Properties();
052        Properties prop;
053
054        for (PropertiesLocation location : locations) {
055            switch(location.getResolver()) {
056            case "ref":
057                prop = loadPropertiesFromRegistry(context, ignoreMissingLocation, location);
058                prop = prepareLoadedProperties(prop);
059                answer.putAll(prop);
060                break;
061            case "file":
062                prop = loadPropertiesFromFilePath(context, ignoreMissingLocation, location);
063                prop = prepareLoadedProperties(prop);
064                answer.putAll(prop);
065                break;
066            case "classpath":
067            default:
068                // default to classpath
069                prop = loadPropertiesFromClasspath(context, ignoreMissingLocation, location);
070                prop = prepareLoadedProperties(prop);
071                answer.putAll(prop);
072                break;
073            }
074        }
075
076        return answer;
077    }
078
079    protected Properties loadPropertiesFromFilePath(CamelContext context, boolean ignoreMissingLocation, PropertiesLocation location) throws IOException {
080        Properties answer = new Properties();
081        String path = location.getPath();
082
083        InputStream is = null;
084        Reader reader = null;
085        try {
086            is = new FileInputStream(path);
087            if (propertiesComponent.getEncoding() != null) {
088                reader = new BufferedReader(new InputStreamReader(is, propertiesComponent.getEncoding()));
089                answer.load(reader);
090            } else {
091                answer.load(is);
092            }
093        } catch (FileNotFoundException e) {
094            if (!ignoreMissingLocation && !location.isOptional()) {
095                throw e;
096            }
097        } finally {
098            IOHelper.close(reader, is);
099        }
100
101        return answer;
102    }
103
104    protected Properties loadPropertiesFromClasspath(CamelContext context, boolean ignoreMissingLocation, PropertiesLocation location) throws IOException {
105        Properties answer = new Properties();
106        String path = location.getPath();
107
108        InputStream is = context.getClassResolver().loadResourceAsStream(path);
109        Reader reader = null;
110        if (is == null) {
111            if (!ignoreMissingLocation && !location.isOptional()) {
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, PropertiesLocation location) throws IOException {
131        String path = location.getPath();
132        Properties answer;
133        try {
134            answer = context.getRegistry().lookupByNameAndType(path, Properties.class);
135        } catch (Exception ex) {
136            // just look up the Map as a fault back
137            Map map = context.getRegistry().lookupByNameAndType(path, Map.class);
138            answer = new Properties();
139            answer.putAll(map);
140        }
141        if (answer == null && (!ignoreMissingLocation && !location.isOptional())) {
142            throw new FileNotFoundException("Properties " + path + " not found in registry");
143        }
144        return answer != null ? answer : new Properties();
145    }
146
147    /**
148     * Strategy to prepare loaded properties before being used by Camel.
149     * <p/>
150     * This implementation will ensure values are trimmed, as loading properties from
151     * a file with values having trailing spaces is not automatic trimmed by the Properties API
152     * from the JDK.
153     *
154     * @param properties  the properties
155     * @return the prepared properties
156     */
157    protected Properties prepareLoadedProperties(Properties properties) {
158        Properties answer = new Properties();
159        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
160            Object key = entry.getKey();
161            Object value = entry.getValue();
162            if (value instanceof String) {
163                String s = (String) value;
164
165                // trim any trailing spaces which can be a problem when loading from
166                // a properties file, note that java.util.Properties does already this
167                // for any potential leading spaces so there's nothing to do there
168                value = trimTrailingWhitespaces(s);
169            }
170            answer.put(key, value);
171        }
172        return answer;
173    }
174
175    private static String trimTrailingWhitespaces(String s) {
176        int endIndex = s.length();
177        for (int index = s.length() - 1; index >= 0; index--) {
178            if (s.charAt(index) == ' ') {
179                endIndex = index;
180            } else {
181                break;
182            }
183        }
184        String answer = s.substring(0, endIndex);
185        return answer;
186    }
187
188}