View Javadoc
1   /*
2    * #%L
3    * settings4j
4    * ===============================================================
5    * Copyright (C) 2008 - 2015 Brabenetz Harald, Austria
6    * ===============================================================
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * 
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package org.settings4j.connector;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.IOException;
25  import java.net.URL;
26  import java.util.Map.Entry;
27  import java.util.Properties;
28  import java.util.Set;
29  
30  import org.apache.commons.io.FilenameUtils;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.commons.lang3.Validate;
33  import org.settings4j.contentresolver.ClasspathContentResolver;
34  import org.settings4j.contentresolver.FSContentResolver;
35  
36  /**
37   * The {@link Properties}-File implementation of an {@link org.settings4j.Connector}.
38   * <h3>1. Example - Use Property-File from Classpath or Filepath</h3>
39   * <p>
40   * Example usage read property File from Classpath or Filepath: <br>
41   * In this case a prefix "file:" or "classpath:" is required.
42   * </p>
43   *
44   * <pre>
45   * &lt;settings4j:configuration xmlns:settings4j='http://settings4j.org/'&gt;
46   *
47   *     &lt;connector name="PropertyFileConnector" class="org.settings4j.connector.PropertyFileConnector"&gt;
48   *         &lt;param  name="propertyFromPath" value="classpath:org/settings4j/config/propertyFile.properties" /&gt;
49   *     &lt;/connector&gt;
50   *
51   * &lt;/settings4j:configuration&gt;
52   * </pre>
53   *
54   * <h3>2. Example - get the path to the Property File from any other Connector</h3>
55   * <p>
56   * In this Example you can configure the path to your Property-File via System-Properties or per Server-JNDI-Context.<br>
57   * E.g.: Start your application with: "-DmyAppConfig=file:/somePath/myConfig.properties"
58   * </p>
59   *
60   * <pre>
61   * &lt;settings4j:configuration xmlns:settings4j='http://settings4j.org/'&gt;
62   *
63   *     &lt;connector name="PropertyFileConnector" class="org.settings4j.connector.PropertyFileConnector"&gt;
64   *         &lt;param  name="propertyFromPath" value="${connectors.string['myAppConfig']}" /&gt;
65   *         &lt;connector-ref ref="SystemPropertyConnector" /&gt;
66   *         &lt;connector-ref ref="JNDIConnector" /&gt;
67   *     &lt;/connector&gt;
68   *
69   *     &lt;connector name="SystemPropertyConnector" class="org.settings4j.connector.SystemPropertyConnector"/&gt;
70   *
71   *     &lt;connector name="JNDIConnector" class="org.settings4j.connector.JNDIConnector"/&gt;
72   *
73   * &lt;/settings4j:configuration&gt;
74   * </pre>
75   *
76   * <h3>3. Resolve relative Paths in your Property File</h3>
77   * <p>
78   * In many application it is required to configure some paths.<br>
79   * Some Examples are:
80   * </p>
81   * <ul>
82   * <li>path to your message-Properties
83   * <li>path to other configuration-Files (e.g.: log4j.xml)
84   * <li>path to some folders (file-upload, cache-directory, log-directory)
85   * <li>other paths
86   * </ul>
87   * <p>
88   * The chance are good, that you want place this folders somewhere relative to the property file.<br>
89   * You must only set the parameter "resolveRelativePaths" on the PropertyFileConnector to "true", and all values from the Property-File which starts with
90   * "file:." will be resolved relative to the Property-File.
91   * </p>
92   *
93   * <pre>
94   * &lt;settings4j:configuration xmlns:settings4j='http://settings4j.org/'&gt;
95   *
96   *     &lt;connector name="SystemPropertyConnector" class="org.settings4j.connector.SystemPropertyConnector"/&gt;
97   *
98   *     &lt;connector name="PropertyFileConnector" class="org.settings4j.connector.PropertyFileConnector"&gt;
99   *         &lt;param  name="propertyFromPath" value="${connectors.string['myAppConfig']}" /&gt;
100  *         &lt;param  name="resolveRelativePaths" value="true /&gt;
101  *         &lt;connector-ref ref="SystemPropertyConnector" /&gt;
102  *     &lt;/connector&gt;
103  *
104  * &lt;/settings4j:configuration&gt;
105  * </pre>
106  * <p>
107  * With this config, you can start your app with <code>"-DmyAppConfig=file:/somePath/myConfig.properties"</code>.<br>
108  * In "myConfig.properties" you have somewhere configured the property:<br>
109  * <code>xyz=file:./test.xml</code><br>
110  * The Settings4j.getString("xyz") will return the {@link URL}-Qualified path: "file:/somePath/test.xml"
111  * </p>
112  *
113  * @author Harald.Brabenetz
114  */
115 public class PropertyFileConnector extends AbstractPropertyConnector {
116 
117     private Properties property = new Properties();
118 
119     private boolean resolveRelativePaths;
120     private URL propertyFileFolderUrl;
121 
122     @Override
123     public String getString(final String key) {
124         return this.property.getProperty(key, null);
125     }
126 
127     public void setProperty(final Properties property) {
128         this.property = property;
129     }
130 
131     // SuppressWarnings PMD.DefaultPackage: used for UnitTests validation.
132     @SuppressWarnings("PMD.DefaultPackage")
133     URL getPropertyFileFolderUrl() {
134         return this.propertyFileFolderUrl;
135     }
136 
137     /**
138      * @param resolveRelativePaths
139      *        set to true if Property Values wit a relative path (starting with "file:.") should be replace with a full qualified URL Path relative to the
140      *        Property-File
141      */
142     public void setResolveRelativePaths(final boolean resolveRelativePaths) {
143         this.resolveRelativePaths = resolveRelativePaths;
144         resolveRelativePaths();
145     }
146 
147     private void setPropertyFromContentInternal(final byte[] content, final String propertyPath) {
148         Validate.notNull(content, "No Property-File found for path: '%s'", propertyPath);
149         final Properties tmpProperty = new Properties();
150         try {
151             tmpProperty.load(new ByteArrayInputStream(content));
152             this.property = tmpProperty;
153         } catch (final IOException e) {
154             throw new IllegalArgumentException(e);
155         }
156     }
157 
158     /**
159      * @param propertyPath
160      *        The filepath to a Property-File. Supported prefixes: "file:" and "classpath:".
161      */
162     public void setPropertyFromPath(final String propertyPath) {
163         if (StringUtils.isEmpty(propertyPath)) {
164             throw new IllegalArgumentException("The Property Path cannot be empty");
165         }
166         if (propertyPath.startsWith(FSContentResolver.FILE_URL_PREFIX)) {
167             final byte[] content = new FSContentResolver().getContent(propertyPath);
168             setPropertyFromContentInternal(content, propertyPath);
169             this.propertyFileFolderUrl = getParentFolderUrlFromFile(propertyPath);
170         } else if (propertyPath.startsWith(ClasspathContentResolver.CLASSPATH_URL_PREFIX)) {
171             final byte[] content = new ClasspathContentResolver().getContent(propertyPath);
172             setPropertyFromContentInternal(content, propertyPath);
173             this.propertyFileFolderUrl = getParentFolderUrlFromClasspath(propertyPath);
174         } else {
175             throw new IllegalArgumentException("The Property Path must start with 'file:' or 'classpath:'. " //
176                 + "But the File Property Path was: '" + propertyPath + "'.");
177         }
178 
179         resolveRelativePaths();
180     }
181 
182     private static URL getParentFolderUrlFromClasspath(final String propertyPath) {
183         final URL resource = ClasspathContentResolver.getResource(propertyPath);
184         Validate.notNull(resource, "The URL for '%s' was null", propertyPath);
185         final String fullPathNoEndSeparator = FilenameUtils.getFullPathNoEndSeparator(resource.toExternalForm());
186         return createURL(fullPathNoEndSeparator + "/");
187     }
188 
189     private static URL getParentFolderUrlFromFile(final String propertyPath) {
190         final File propertyFile = new File(StringUtils.removeStart(propertyPath, FSContentResolver.FILE_URL_PREFIX));
191         try {
192             return propertyFile.getParentFile().toURI().toURL();
193         } catch (final IOException e) {
194             throw new IllegalArgumentException(e);
195         }
196     }
197 
198     private void resolveRelativePaths() {
199 
200         if (!this.resolveRelativePaths) {
201             return;
202         }
203 
204         if (this.propertyFileFolderUrl == null) {
205             return;
206         }
207         final Set<Entry<Object, Object>> entrySet = this.property.entrySet();
208         for (final Entry<Object, Object> entry : entrySet) {
209             final String propValue = entry.getValue().toString();
210             if (propValue.startsWith(FSContentResolver.FILE_URL_PREFIX + ".")) {
211 
212                 final String valuePath = StringUtils.removeStart(propValue, FSContentResolver.FILE_URL_PREFIX);
213                 final String newPath = createURL(this.propertyFileFolderUrl, valuePath).toString();
214                 this.property.setProperty(entry.getKey().toString(), newPath);
215 
216             }
217         }
218     }
219 
220     private static URL createURL(final String fullPathNoEndSeparator) {
221         try {
222             return new URL(fullPathNoEndSeparator);
223         } catch (final Exception e) {
224             throw new IllegalArgumentException(e);
225         }
226     }
227 
228     private static URL createURL(final URL context, final String spec) {
229         try {
230             return new URL(context, spec);
231         } catch (final Exception e) {
232             throw new IllegalArgumentException(e);
233         }
234     }
235 }