James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
5fe7df 16 package com.gitblit;
JM 17
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileNotFoundException;
3ef46e 21 import java.io.IOException;
JM 22 import java.util.List;
97a20e 23 import java.util.Map;
5fe7df 24 import java.util.Properties;
97a20e 25
JM 26 import com.gitblit.utils.FileUtils;
3ef46e 27 import com.gitblit.utils.StringUtils;
5fe7df 28
JM 29 /**
892570 30  * Dynamically loads and reloads a properties file by keeping track of the last
JM 31  * modification date.
699e71 32  *
892570 33  * @author James Moger
699e71 34  *
5fe7df 35  */
f339f5 36 public class FileSettings extends IStoredSettings {
2a7306 37
e24670 38     protected File propertiesFile;
db653a 39
f339f5 40     private final Properties properties = new Properties();
5fe7df 41
892570 42     private volatile long lastModified;
699e71 43
5e58f0 44     private volatile boolean forceReload;
5fe7df 45
e24670 46     public FileSettings() {
f339f5 47         super(FileSettings.class);
e24670 48     }
JM 49
50     public FileSettings(String file) {
51         this();
52         load(file);
53     }
54
55     public void load(String file) {
28d6b2 56         this.propertiesFile = new File(file);
e24670 57     }
JM 58
59     /**
60      * Merges the provided settings into this instance.  This will also
61      * set the target file for this instance IFF it is unset AND the merge
62      * source is also a FileSettings.  This is a little sneaky.
63      */
64     @Override
65     public void merge(IStoredSettings settings) {
66         super.merge(settings);
67
68         // sneaky: set the target file from the merge source
69         if (propertiesFile == null && settings instanceof FileSettings) {
70             this.propertiesFile = ((FileSettings) settings).propertiesFile;
71         }
28d6b2 72     }
db653a 73
892570 74     /**
JM 75      * Returns a properties object which contains the most recent contents of
76      * the properties file.
77      */
87cc1e 78     @Override
f339f5 79     protected synchronized Properties read() {
e24670 80         if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
2a7306 81             FileInputStream is = null;
5fe7df 82             try {
17ae31 83                 logger.debug("loading {}", propertiesFile);
f339f5 84                 Properties props = new Properties();
28d6b2 85                 is = new FileInputStream(propertiesFile);
f339f5 86                 props.load(is);
85c2e6 87
3ef46e 88                 // ticket-110
JM 89                 props = readIncludes(props);
90
f339f5 91                 // load properties after we have successfully read file
JM 92                 properties.clear();
93                 properties.putAll(props);
892570 94                 lastModified = propertiesFile.lastModified();
5e58f0 95                 forceReload = false;
5fe7df 96             } catch (FileNotFoundException f) {
2a7306 97                 // IGNORE - won't happen because file.exists() check above
5fe7df 98             } catch (Throwable t) {
f339f5 99                 logger.error("Failed to read " + propertiesFile.getName(), t);
2a7306 100             } finally {
JM 101                 if (is != null) {
102                     try {
103                         is.close();
104                     } catch (Throwable t) {
105                         // IGNORE
106                     }
107                 }
5fe7df 108             }
JM 109         }
110         return properties;
111     }
2a7306 112
3ef46e 113     /**
JM 114      * Recursively read "include" properties files.
115      *
116      * @param properties
117      * @return
118      * @throws IOException
119      */
120     private Properties readIncludes(Properties properties) throws IOException {
121
122         Properties baseProperties = new Properties();
123
124         String include = (String) properties.remove("include");
125         if (!StringUtils.isEmpty(include)) {
126
127             // allow for multiples
17ae31 128             List<String> names = StringUtils.getStringsFromValue(include, ",");
3ef46e 129             for (String name : names) {
17ae31 130
JM 131                 if (StringUtils.isEmpty(name)) {
132                     continue;
133                 }
3ef46e 134
JM 135                 // try co-located
136                 File file = new File(propertiesFile.getParentFile(), name.trim());
137                 if (!file.exists()) {
138                     // try absolute path
139                     file = new File(name.trim());
140                 }
141
17ae31 142                 if (!file.exists()) {
JM 143                     logger.warn("failed to locate {}", file);
144                     continue;
3ef46e 145                 }
17ae31 146
JM 147                 // load properties
148                 logger.debug("loading {}", file);
149                 try (FileInputStream iis = new FileInputStream(file)) {
150                     baseProperties.load(iis);
151                 }
152
153                 // read nested includes
154                 baseProperties = readIncludes(baseProperties);
3ef46e 155
JM 156             }
157
158         }
159
160         // includes are "default" properties, they must be set first and the
161         // props which specified the "includes" must override
162         Properties merged = new Properties();
163         merged.putAll(baseProperties);
164         merged.putAll(properties);
165
166         return merged;
167     }
168
53a83f 169     @Override
JM 170     public boolean saveSettings() {
171         String content = FileUtils.readContent(propertiesFile, "\n");
172         for (String key : removals) {
173             String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
17ae31 174                     + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
53a83f 175             content = content.replaceAll(regex, "");
JM 176         }
177         removals.clear();
178
179         FileUtils.writeContent(propertiesFile, content);
180         // manually set the forceReload flag because not all JVMs support real
181         // millisecond resolution of lastModified. (issue-55)
182         forceReload = true;
183         return true;
184     }
185
892570 186     /**
97a20e 187      * Updates the specified settings in the settings file.
JM 188      */
699e71 189     @Override
97a20e 190     public synchronized boolean saveSettings(Map<String, String> settings) {
JM 191         String content = FileUtils.readContent(propertiesFile, "\n");
192         for (Map.Entry<String, String> setting:settings.entrySet()) {
193             String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)"
17ae31 194                     + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
f3ff37 195             String oldContent = content;
97a20e 196             content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue());
f3ff37 197             if (content.equals(oldContent)) {
JM 198                 // did not replace value because it does not exist in the file
199                 // append new setting to content (issue-85)
200                 content += "\n" + setting.getKey() + " = " + setting.getValue();
201             }
97a20e 202         }
JM 203         FileUtils.writeContent(propertiesFile, content);
5e58f0 204         // manually set the forceReload flag because not all JVMs support real
699e71 205         // millisecond resolution of lastModified. (issue-55)
5e58f0 206         forceReload = true;
97a20e 207         return true;
JM 208     }
699e71 209
97a20e 210     private String regExEscape(String input) {
8c8f1f 211         return input.replace(".", "\\.").replace("$", "\\$").replace("{", "\\{");
97a20e 212     }
JM 213
214     /**
892570 215      * @return the last modification date of the properties file
JM 216      */
217     protected long lastModified() {
218         return lastModified;
85c2e6 219     }
JM 220
5e58f0 221     /**
JM 222      * @return the state of the force reload flag
223      */
224     protected boolean forceReload() {
225         return forceReload;
226     }
227
87cc1e 228     @Override
JM 229     public String toString() {
28d6b2 230         return propertiesFile.getAbsolutePath();
87cc1e 231     }
5fe7df 232 }