From a502d96a860456ec5e8c96761db70f7cabb74751 Mon Sep 17 00:00:00 2001
From: Paul Martin <paul@paulsputer.com>
Date: Sat, 30 Apr 2016 04:19:14 -0400
Subject: [PATCH] Merge pull request #1073 from gitblit/1062-DocEditorUpdates

---
 src/main/java/com/gitblit/FileSettings.java |  111 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 107 insertions(+), 4 deletions(-)

diff --git a/src/main/java/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java
index 12739d2..3caf966 100644
--- a/src/main/java/com/gitblit/FileSettings.java
+++ b/src/main/java/com/gitblit/FileSettings.java
@@ -18,10 +18,13 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
 import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
 
 /**
  * Dynamically loads and reloads a properties file by keeping track of the last
@@ -32,7 +35,7 @@
  */
 public class FileSettings extends IStoredSettings {
 
-	protected final File propertiesFile;
+	protected File propertiesFile;
 
 	private final Properties properties = new Properties();
 
@@ -40,9 +43,32 @@
 
 	private volatile boolean forceReload;
 
-	public FileSettings(String file) {
+	public FileSettings() {
 		super(FileSettings.class);
+	}
+
+	public FileSettings(String file) {
+		this();
+		load(file);
+	}
+
+	public void load(String file) {
 		this.propertiesFile = new File(file);
+	}
+
+	/**
+	 * Merges the provided settings into this instance.  This will also
+	 * set the target file for this instance IFF it is unset AND the merge
+	 * source is also a FileSettings.  This is a little sneaky.
+	 */
+	@Override
+	public void merge(IStoredSettings settings) {
+		super.merge(settings);
+
+		// sneaky: set the target file from the merge source
+		if (propertiesFile == null && settings instanceof FileSettings) {
+			this.propertiesFile = ((FileSettings) settings).propertiesFile;
+		}
 	}
 
 	/**
@@ -51,12 +77,16 @@
 	 */
 	@Override
 	protected synchronized Properties read() {
-		if (propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
+		if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
 			FileInputStream is = null;
 			try {
+				logger.debug("loading {}", propertiesFile);
 				Properties props = new Properties();
 				is = new FileInputStream(propertiesFile);
 				props.load(is);
+
+				// ticket-110
+				props = readIncludes(props);
 
 				// load properties after we have successfully read file
 				properties.clear();
@@ -81,6 +111,79 @@
 	}
 
 	/**
+	 * Recursively read "include" properties files.
+	 *
+	 * @param properties
+	 * @return
+	 * @throws IOException
+	 */
+	private Properties readIncludes(Properties properties) throws IOException {
+
+		Properties baseProperties = new Properties();
+
+		String include = (String) properties.remove("include");
+		if (!StringUtils.isEmpty(include)) {
+
+			// allow for multiples
+			List<String> names = StringUtils.getStringsFromValue(include, ",");
+			for (String name : names) {
+
+				if (StringUtils.isEmpty(name)) {
+					continue;
+				}
+
+				// try co-located
+				File file = new File(propertiesFile.getParentFile(), name.trim());
+				if (!file.exists()) {
+					// try absolute path
+					file = new File(name.trim());
+				}
+
+				if (!file.exists()) {
+					logger.warn("failed to locate {}", file);
+					continue;
+				}
+
+				// load properties
+				logger.debug("loading {}", file);
+				try (FileInputStream iis = new FileInputStream(file)) {
+					baseProperties.load(iis);
+				}
+
+				// read nested includes
+				baseProperties = readIncludes(baseProperties);
+
+			}
+
+		}
+
+		// includes are "default" properties, they must be set first and the
+		// props which specified the "includes" must override
+		Properties merged = new Properties();
+		merged.putAll(baseProperties);
+		merged.putAll(properties);
+
+		return merged;
+	}
+
+	@Override
+	public boolean saveSettings() {
+		String content = FileUtils.readContent(propertiesFile, "\n");
+		for (String key : removals) {
+			String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
+					+ "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+			content = content.replaceAll(regex, "");
+		}
+		removals.clear();
+
+		FileUtils.writeContent(propertiesFile, content);
+		// manually set the forceReload flag because not all JVMs support real
+		// millisecond resolution of lastModified. (issue-55)
+		forceReload = true;
+		return true;
+	}
+
+	/**
 	 * Updates the specified settings in the settings file.
 	 */
 	@Override
@@ -88,7 +191,7 @@
 		String content = FileUtils.readContent(propertiesFile, "\n");
 		for (Map.Entry<String, String> setting:settings.entrySet()) {
 			String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)"
-				    + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+					+ "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
 			String oldContent = content;
 			content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue());
 			if (content.equals(oldContent)) {

--
Gitblit v1.9.1