juneau-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jamesbog...@apache.org
Subject [juneau] branch master updated: Config API enhancements
Date Thu, 15 Feb 2018 01:10:50 GMT
This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new e830ffd  Config API enhancements
e830ffd is described below

commit e830ffddefee7651e87d3c73e9c9c59232f82e41
Author: JamesBognar <jamesbognar@apache.org>
AuthorDate: Wed Feb 14 20:10:48 2018 -0500

    Config API enhancements
---
 .../org/apache/juneau/config/ConfigSourceFile.java | 133 ----------
 .../apache/juneau/config/ConfigSourceMemory.java   | 131 ----------
 .../apache/juneau/config/ConfigSourceSettings.java | 103 --------
 .../org/apache/juneau/config/source/FileStore.java | 281 +++++++++++++++++++++
 .../juneau/config/source/FileStoreBuilder.java     | 191 ++++++++++++++
 .../apache/juneau/config/source/MemoryStore.java   |  82 ++++++
 .../juneau/config/source/MemoryStoreBuilder.java   |  42 +++
 .../{ConfigSource.java => source/Store.java}       | 103 +++++---
 .../apache/juneau/config/source/StoreBuilder.java  |  40 +++
 .../apache/juneau/config/source/StoreListener.java |  27 ++
 10 files changed, 736 insertions(+), 397 deletions(-)

diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceFile.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceFile.java
deleted file mode 100644
index 4a03964..0000000
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceFile.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
-// * with the License.  You may obtain a copy of the License at                         
                                    *
-// *                                                                                    
                                    *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
-// *                                                                                    
                                    *
-// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
-// * specific language governing permissions and limitations under the License.         
                                    *
-// ***************************************************************************************************************************
-package org.apache.juneau.config;
-
-import java.io.*;
-import java.nio.channels.*;
-import java.nio.charset.*;
-import java.util.*;
-import java.util.concurrent.*;
-
-import org.apache.juneau.internal.*;
-
-/**
- * Implementation of a configuration source that's a file on the local file system.
- */
-public class ConfigSourceFile extends ConfigSource {
-
-	private ConcurrentHashMap<String,CacheEntry> cache = new ConcurrentHashMap<>();
-
-	/**
-	 * Constructor.
-	 * 
-	 * @param settings
-	 * 	The settings for this config source.
-	 */
-	public ConfigSourceFile(ConfigSourceSettings settings) {
-		super(settings);
-	}
-
-	@Override /* ConfigSource */
-	public synchronized String read(String name) throws Exception {
-		CacheEntry fe = cache.get(name);
-
-		if (fe == null || fe.hasBeenModified()) {
-			File f = findFile(name);
-			try (FileInputStream fis = new FileInputStream(f)) {
-				try (FileLock lock = fis.getChannel().lock()) {
-					try (Reader r = new InputStreamReader(fis, Charset.defaultCharset())) {
-						String contents = IOUtils.read(r);
-						long lastModified = f.lastModified();
-						fe = new CacheEntry(f, lastModified, contents);
-						cache.put(name, fe);
-					}
-				}
-			}
-		}
-
-		return fe.contents;
-	}
-
-	@Override /* ConfigSource */
-	public synchronized boolean write(String name, String contents) throws Exception {
-		if (hasBeenModified(name))
-			return false;
-
-		CacheEntry fe = cache.get(name);
-		File f = fe != null ? fe.file : findFile(name);
-
-		try (FileOutputStream fos = new FileOutputStream(f)) {
-			try (FileLock lock = fos.getChannel().lock()) {
-				if (hasBeenModified(name))
-					return false;
-				try (Writer w = new OutputStreamWriter(fos, Charset.defaultCharset())) {
-					IOUtils.pipe(contents, w);
-				}
-				fe = new CacheEntry(f, f.lastModified(), contents);
-				cache.put(name, fe);
-				return true;
-			}
-		}
-	}
-
-	@Override /* ConfigSource */
-	public boolean hasBeenModified(String name) throws Exception {
-		CacheEntry fe = cache.get(name);
-		return (fe != null && fe.hasBeenModified());
-	}
-
-	private static class CacheEntry {
-		final File file;
-		final long lastModified;
-		final String contents;
-
-		CacheEntry(File file, long lastModified, String contents) {
-			this.file = file;
-			this.lastModified = lastModified;
-			this.contents = contents;
-		}
-
-		boolean hasBeenModified() {
-			return file.lastModified() != lastModified;
-		}
-	}
-
-	private File findFile(String name) throws IOException {
-
-		List<String> searchPaths = getSettings().getSearchPaths();
-
-		if (searchPaths.isEmpty())
-			throw new FileNotFoundException("No search paths specified in ConfigFileBuilder.");
-
-		// Handle paths relative to search paths.
-		for (String sp : searchPaths) {
-			File pf = new File(sp);
-			File f = new File(pf, name);
-			if (f.exists())
-				return f;
-		}
-
-		if (getSettings().isCreateIfNotExists()) {
-			for (String sf : searchPaths) {
-				File pf = new File(sf);
-				if (pf.exists() && pf.isDirectory() && pf.canWrite()) {
-					File f = new File(pf, name);
-					if (f.createNewFile())
-						return f;
-				}
-			}
-		}
-
-		throw new FileNotFoundException("Could not find config file '"+name+"'");
-	}
-}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceMemory.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceMemory.java
deleted file mode 100644
index b6d1b0e..0000000
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceMemory.java
+++ /dev/null
@@ -1,131 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
-// * with the License.  You may obtain a copy of the License at                         
                                    *
-// *                                                                                    
                                    *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
-// *                                                                                    
                                    *
-// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
-// * specific language governing permissions and limitations under the License.         
                                    *
-// ***************************************************************************************************************************
-package org.apache.juneau.config;
-
-import java.io.*;
-import java.util.*;
-import java.util.concurrent.*;
-
-/**
- * Implementation of a configuration source entirely in memory.
- */
-public class ConfigSourceMemory extends ConfigSource {
-
-	private static final ConcurrentHashMap<String,MemoryFile> MEMORY = new ConcurrentHashMap<>();
-
-	private ConcurrentHashMap<String,CacheEntry> cache = new ConcurrentHashMap<>();
-
-	/**
-	 * Constructor.
-	 * 
-	 * @param settings
-	 * 	The settings for this config source.
-	 */
-	public ConfigSourceMemory(ConfigSourceSettings settings) {
-		super(settings);
-	}
-
-	@Override /* ConfigSource */
-	public synchronized String read(String name) throws Exception {
-		CacheEntry ce = cache.get(name);
-
-		if (ce == null || ce.hasBeenModified()) {
-			MemoryFile f = findFile(name);
-			synchronized(f) {
-				ce = new CacheEntry(f, f.lastModified, f.contents);
-				cache.put(name, ce);
-			}
-		}
-
-		return ce.contents;
-	}
-
-	@Override /* ConfigSource */
-	public synchronized boolean write(String name, String contents) throws Exception {
-		if (hasBeenModified(name))
-			return false;
-
-		CacheEntry ce = cache.get(name);
-		MemoryFile f = ce != null ? ce.file : findFile(name);
-
-		synchronized(f) {
-			if (hasBeenModified(name))
-				return false;
-			f.contents = contents;
-			f.lastModified = System.currentTimeMillis();
-			ce = new CacheEntry(f, f.lastModified, f.contents);
-			cache.put(name, ce);
-		}
-
-		return true;
-	}
-
-	@Override /* ConfigSource */
-	public boolean hasBeenModified(String name) throws Exception {
-		CacheEntry ce = cache.get(name);
-		return (ce != null && ce.hasBeenModified());
-	}
-
-	private MemoryFile findFile(String name) throws IOException {
-
-		List<String> searchPaths = getSettings().getSearchPaths();
-
-		if (searchPaths.isEmpty())
-			throw new FileNotFoundException("No search paths specified in ConfigFileBuilder.");
-
-		// Handle paths relative to search paths.
-		for (String sp : searchPaths) {
-			String pf = sp + '/' + name;
-			MemoryFile mf = MEMORY.get(pf);
-			if (mf != null)
-				return mf;
-		}
-
-		if (getSettings().isCreateIfNotExists()) {
-			for (String sf : searchPaths) {
-				String path = sf + '/' + name;
-				MemoryFile mf = new MemoryFile("");
-				MEMORY.putIfAbsent(path, mf);
-				return MEMORY.get(path);
-			}
-		}
-
-		throw new FileNotFoundException("Could not find config file '"+name+"'");
-	}
-
-	private static class MemoryFile {
-		String contents;
-		long lastModified;
-
-		MemoryFile(String contents) {
-			this.contents = contents;
-			this.lastModified = System.currentTimeMillis();
-		}
-	}
-
-	private static class CacheEntry {
-		final MemoryFile file;
-		final long lastModified;
-		final String contents;
-
-		CacheEntry(MemoryFile file, long lastModified, String contents) {
-			this.file = file;
-			this.lastModified = lastModified;
-			this.contents = contents;
-		}
-
-		boolean hasBeenModified() {
-			return file.lastModified != lastModified;
-		}
-	}
-}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceSettings.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceSettings.java
deleted file mode 100644
index bfc66a3..0000000
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSourceSettings.java
+++ /dev/null
@@ -1,103 +0,0 @@
-// ***************************************************************************************************************************
-// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
-// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
-// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
-// * with the License.  You may obtain a copy of the License at                         
                                    *
-// *                                                                                    
                                    *
-// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
-// *                                                                                    
                                    *
-// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
-// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
-// * specific language governing permissions and limitations under the License.         
                                    *
-// ***************************************************************************************************************************
-package org.apache.juneau.config;
-
-import java.nio.charset.*;
-import java.util.*;
-
-/**
- * Configuration settings for the {@link ConfigSource} class.
- */
-public class ConfigSourceSettings {
-
-	private final List<String> searchPaths;
-	private final Charset charset;
-	private final boolean readonly, createIfNotExists;
-
-	static final class Builder {
-		List<String> searchPaths = Arrays.asList(new String[]{"."});
-		Charset charset = Charset.defaultCharset();
-		boolean readonly = false, createIfNotExists = true;
-
-		Builder searchPaths(String[] searchPaths) {
-			this.searchPaths = Arrays.asList(searchPaths);
-			return this;
-		}
-
-		Builder charset(Charset charset) {
-			this.charset = charset;
-			return this;
-		}
-
-		Builder readonly(boolean readonly) {
-			this.readonly = readonly;
-			return this;
-		}
-
-		Builder createIfNotExists(boolean createIfNotExists) {
-			this.createIfNotExists = createIfNotExists;
-			return this;
-		}
-
-		ConfigSourceSettings build() {
-			return new ConfigSourceSettings(this);
-		}
-	}
-
-	ConfigSourceSettings(Builder b) {
-		this.searchPaths = b.searchPaths;
-		this.charset = b.charset;
-		this.readonly = b.readonly;
-		this.createIfNotExists = b.createIfNotExists;
-	}
-
-	/**
-	 * Returns the paths to search to find config files.
-	 * 
-	 * @return The paths to search to find config files.
-	 */
-	public List<String> getSearchPaths() {
-		return searchPaths;
-	}
-
-	/**
-	 * Returns the charset of the config file.
-	 * 
-	 * @return The charset of the config file.
-	 */
-	public Charset getCharset() {
-		return charset;
-	}
-
-	/**
-	 * Specifies whether the config file should be opened in read-only mode.
-	 * 
-	 * @return <jk>true</jk> if the config file should be opened in read-only mode.
-	 */
-	public boolean isReadonly() {
-		return readonly;
-	}
-
-	/**
-	 * Specifies whether config files should be created if they're not found in the search paths.
-	 * 
-	 * <p>
-	 * Note that the first writable path will be used for the location of the file.
-	 * 
-	 * @return <jk>true</jk> if the config file should be created if not found.
-	 */
-	public boolean isCreateIfNotExists() {
-		return createIfNotExists;
-	}
-}
-
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStore.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStore.java
new file mode 100644
index 0000000..e5a9dc5
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStore.java
@@ -0,0 +1,281 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+import static java.nio.file.StandardWatchEventKinds.*;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+import java.nio.charset.*;
+import java.nio.file.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Filesystem-based storage location for configuration files.
+ * 
+ * <p>
+ * Points to a file system directory containing configuration files.
+ */
+public class FileStore extends Store {
+
+	//-------------------------------------------------------------------------------------------------------------------
+	// Configurable properties
+	//-------------------------------------------------------------------------------------------------------------------
+
+	private static final String PREFIX = "FileStore.";
+
+	/**
+	 * Configuration property:  Local file system directory.
+	 * 
+	 * <h5 class='section'>Property:</h5>
+	 * <ul>
+	 * 	<li><b>Name:</b>  <js>"FileStore.directory.s"</js>
+	 * 	<li><b>Data type:</b>  <code>String</code>
+	 * 	<li><b>Default:</b>  <js>"."</js>
+	 * 	<li><b>Methods:</b> 
+	 * 		<ul>
+	 * 			<li class='jm'>{@link FileStoreBuilder#directory(String)}
+	 * 			<li class='jm'>{@link FileStoreBuilder#directory(File)}
+	 * 		</ul>
+	 * </ul>
+	 * 
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Identifies the path of the directory containing the configuration files.
+	 */
+	public static final String FILESTORE_directory = PREFIX + "directory.s";
+
+	/**
+	 * Configuration property:  Charset.
+	 * 
+	 * <h5 class='section'>Property:</h5>
+	 * <ul>
+	 * 	<li><b>Name:</b>  <js>"FileStore.charset.s"</js>
+	 * 	<li><b>Data type:</b>  {@link Charset}
+	 * 	<li><b>Default:</b>  {@link Charset#defaultCharset()}
+	 * 	<li><b>Methods:</b> 
+	 * 		<ul>
+	 * 			<li class='jm'>{@link FileStoreBuilder#charset(String)}
+	 * 			<li class='jm'>{@link FileStoreBuilder#charset(Charset)}
+	 * 		</ul>
+	 * </ul>
+	 * 
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Identifies the charset of external files.
+	 */
+	public static final String FILESTORE_charset = PREFIX + "charset.s";
+	
+	/**
+	 * Configuration property:  Use watcher.
+	 * 
+	 * <h5 class='section'>Property:</h5>
+	 * <ul>
+	 * 	<li><b>Name:</b>  <js>"FileStore.useWatcher.b"</js>
+	 * 	<li><b>Data type:</b>  <code>Boolean</code>
+	 * 	<li><b>Default:</b>  <jk>false</jk>
+	 * 	<li><b>Methods:</b> 
+	 * 		<ul>
+	 * 			<li class='jm'>{@link FileStoreBuilder#useWatcher()}
+	 * 			<li class='jm'>{@link FileStoreBuilder#useWatcher(boolean)}
+	 * 		</ul>
+	 * </ul>
+	 * 
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Use a file system watcher for file system changes.
+	 * 
+	 * <h5 class='section'>Notes:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>Calling {@link #close()} on this object closes the watcher.
+	 * </ul>
+	 */
+	public static final String FILESTORE_useWatcher = PREFIX + "useWatcher.s";
+	
+	/**
+	 * Configuration property:  Config file extension.
+	 * 
+	 * <h5 class='section'>Property:</h5>
+	 * <ul>
+	 * 	<li><b>Name:</b>  <js>"FileStore.ext.s"</js>
+	 * 	<li><b>Data type:</b>  <code>String</code>
+	 * 	<li><b>Default:</b>  <js>"cfg"</js>
+	 * 	<li><b>Methods:</b> 
+	 * 		<ul>
+	 * 			<li class='jm'>{@link FileStoreBuilder#ext(String)}
+	 * 		</ul>
+	 * </ul>
+	 * 
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * File extension identifier for config files.
+	 */
+	public static final String FILESTORE_ext = PREFIX + "ext.s";
+
+	/**
+	 * Create a new builder for this object.
+	 * 
+	 * @return A new builder for this object.
+	 */
+	public static FileStoreBuilder create() {
+		return new FileStoreBuilder();
+	}
+	
+	@Override /* Context */
+	public FileStoreBuilder builder() {
+		return new FileStoreBuilder(getPropertyStore());
+	}
+
+	private final File dir;
+	private final String ext;
+	private final Charset charset;
+	private final WatcherThread watcher;
+	private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
+	
+	/**
+	 * Constructor.
+	 * 
+	 * @param ps The settings for this content store.
+	 */
+	protected FileStore(PropertyStore ps) {
+		super(ps);
+		try {
+			dir = new File(getStringProperty(FILESTORE_directory, ".")).getCanonicalFile();
+			ext = getStringProperty(FILESTORE_ext, "cfg");
+			charset = getProperty(FILESTORE_charset, Charset.class, Charset.defaultCharset());
+			watcher = getBooleanProperty(FILESTORE_useWatcher, false) ? new WatcherThread(dir) : null;
+			if (watcher != null)
+				watcher.start();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+	
+	@Override /* Closeable */
+	public synchronized void close() {
+		if (watcher != null)
+			watcher.interrupt();
+	}
+	
+	//---------------------------------------------------------------------------------------------
+	// WatcherThread
+	//---------------------------------------------------------------------------------------------
+
+	final class WatcherThread extends Thread {
+		private final WatchService watchService;
+
+		WatcherThread(File dir) throws IOException {
+			watchService = FileSystems.getDefault().newWatchService();
+			dir.toPath().register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
+		}
+		
+		@SuppressWarnings("unchecked")
+		@Override /* Thread */
+		public void run() {
+		    try {
+				while (true) {
+				    WatchKey key = watchService.take();
+				    
+				    for (WatchEvent<?> event: key.pollEvents()) {
+				        WatchEvent.Kind<?> kind = event.kind();
+
+				        if (kind != OVERFLOW) 
+				        		FileStore.this.onFileEvent(((WatchEvent<Path>)event));
+				    }  
+				    
+				    if (! key.reset())
+				    		break;
+				}
+			} catch (Exception e) {
+				throw new RuntimeException(e);
+			}
+		};
+		
+		@Override /* Thread */
+		public void interrupt() {
+			try {
+				watchService.close();
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			} finally {
+				super.interrupt();
+			}
+		}
+	}
+	
+	synchronized void onFileEvent(WatchEvent<Path> e) throws IOException {
+		File f = e.context().toFile();
+		String fn = f.getName();
+		String bn = FileUtils.getBaseName(fn);
+		String ext = FileUtils.getExtension(fn);
+		if (ext.equals(ext)) {
+			String newContents = IOUtils.read(f);
+			String oldContents = cache.get(bn);
+			if (! StringUtils.isEquals(oldContents, newContents)) {
+				onChange(bn, newContents);
+				cache.put(bn, newContents);
+			}
+		}
+	}
+		
+	@Override
+	public synchronized String read(String name) throws Exception {
+		String s = cache.get(name);
+		if (s != null)
+			return s;
+		
+		File f = new File(dir, name + '.' + ext);
+		if (f.exists()) {
+			try (FileInputStream fis = new FileInputStream(f)) {
+				try (FileLock lock = fis.getChannel().lock()) {
+					try (Reader r = new InputStreamReader(fis, charset)) {
+						String contents = IOUtils.read(r);
+						cache.put(name, contents);
+					}
+				}
+			}
+		}
+		
+		return cache.get(name);
+	}
+
+	@Override
+	public synchronized boolean write(String name, String oldContents, String newContents) throws
Exception {
+		File f = new File(dir, name + '.' + ext);
+		try (FileChannel fc = FileChannel.open(f.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE))
{
+			try (FileLock lock = fc.lock()) {
+				ByteBuffer buf = ByteBuffer.allocate(1024);
+				StringBuilder sb = new StringBuilder();
+				while (fc.read(buf) != -1) {
+					sb.append(charset.decode(buf));
+					sb.append(charset.decode((ByteBuffer)(buf.flip())));
+					buf.clear();
+				}
+				String s = sb.toString();
+				if (! StringUtils.isEquals(oldContents, sb.toString())) {
+					cache.put(name, s);
+					return false;
+				}
+				fc.position(0);
+				fc.write(charset.encode(newContents));
+				cache.put(name, newContents);
+				return true;
+			}
+			
+		}
+	}
+}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStoreBuilder.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStoreBuilder.java
new file mode 100644
index 0000000..1030f94
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/FileStoreBuilder.java
@@ -0,0 +1,191 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+import static org.apache.juneau.config.source.FileStore.*;
+
+import java.io.*;
+import java.nio.charset.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Builder for {@link FileStore} objects.
+ */
+public class FileStoreBuilder extends StoreBuilder {
+
+	/**
+	 * Constructor, default settings.
+	 */
+	public FileStoreBuilder() {
+		super();
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param ps The initial configuration settings for this builder.
+	 */
+	public FileStoreBuilder(PropertyStore ps) {
+		super(ps);
+	}
+
+	
+	//--------------------------------------------------------------------------------
+	// Properties
+	//--------------------------------------------------------------------------------
+
+	/**
+	 * Configuration property:  Local file system directory.
+	 * 
+	 * <p>
+	 * Identifies the path of the directory containing the configuration files.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_directory}
+	 * </ul>
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <js>"."</js>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder directory(String value) {
+		super.set(FILESTORE_directory, value);
+		return this;
+	}
+
+	/**
+	 * Configuration property:  Local file system directory.
+	 * 
+	 * <p>
+	 * Identifies the path of the directory containing the configuration files.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_directory}
+	 * </ul>
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <js>"."</js>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder directory(File value) {
+		super.set(FILESTORE_directory, value);
+		return this;
+	}
+
+	/**
+	 * Configuration property:  Charset.
+	 * 
+	 * <p>
+	 * Identifies the charset of external files.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_charset}
+	 * </ul>
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <js>"."</js>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder charset(String value) {
+		super.set(FILESTORE_charset, value);
+		return this;
+	}
+	
+	/**
+	 * Configuration property:  Charset.
+	 * 
+	 * <p>
+	 * Identifies the charset of external files.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_charset}
+	 * </ul>
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <js>"."</js>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder charset(Charset value) {
+		super.set(FILESTORE_charset, value);
+		return this;
+	}
+	
+	/**
+	 * Configuration property:  Use watcher.
+	 * 
+	 * <p>
+	 * Use a file system watcher for file system changes.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_useWatcher}
+	 * </ul>
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <jk>false</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder useWatcher(boolean value) {
+		super.set(FILESTORE_useWatcher, value);
+		return this;
+	}
+
+	/**
+	 * Configuration property:  Use watcher.
+	 * 
+	 * <p>
+	 * Shortcut for calling <code>useWatcher(<jk>true</jk>)</code>.
+	 * 
+	 * <h5 class='section'>See Also:</h5>
+	 * <ul>
+	 * 	<li class='jf'>{@link FileStore#FILESTORE_useWatcher}
+	 * </ul>
+	 * 
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder useWatcher() {
+		super.set(FILESTORE_useWatcher, true);
+		return this;
+	}
+	
+	/**
+	 * Configuration property:  Config file extension.
+	 * 
+	 * <p>
+	 * File extension identifier for config files.
+	 * 
+	 * @param value 
+	 * 	The new value for this property.
+	 * 	<br>The default is <js>"cfg"</js>.
+	 * @return This object (for method chaining).
+	 */
+	public FileStoreBuilder ext(String value) {
+		super.set(FILESTORE_ext, value);
+		return this;
+	}
+
+	@Override /* ContextBuilder */
+	public FileStore build() {
+		return new FileStore(getPropertyStore());
+	}
+}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStore.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStore.java
new file mode 100644
index 0000000..98a64a8
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStore.java
@@ -0,0 +1,82 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.*;
+
+/**
+ * Filesystem-based storage location for configuration files.
+ * 
+ * <p>
+ * Points to a file system directory containing configuration files.
+ */
+public class MemoryStore extends Store {
+
+	/**
+	 * Create a new builder for this object.
+	 * 
+	 * @return A new builder for this object.
+	 */
+	public static MemoryStoreBuilder create() {
+		return new MemoryStoreBuilder();
+	}
+	
+	@Override /* Context */
+	public MemoryStoreBuilder builder() {
+		return new MemoryStoreBuilder(getPropertyStore());
+	}
+
+	private final ConcurrentHashMap<String,String> cache = new ConcurrentHashMap<>();
+	
+	/**
+	 * Constructor.
+	 * 
+	 * @param ps The settings for this content store.
+	 */
+	protected MemoryStore(PropertyStore ps) {
+		super(ps);
+	}
+	
+	@Override /* Store */
+	public synchronized String read(String name) throws Exception {
+		return cache.get(name);
+	}
+
+	@Override /* Store */
+	public synchronized boolean write(String name, String oldContents, String newContents) throws
Exception {
+		String s = cache.get(name);
+		
+		if (! isEquals(s, oldContents)) 
+			return false;
+		
+		if (! isEquals(s, newContents)) {
+			cache.put(name, newContents);
+			onChange(name, newContents);
+		}
+		
+		return true;
+	}
+
+	/**
+	 * No-op.
+	 */
+	@Override /* Closeable */
+	public void close() throws IOException {
+		// No-op
+	}
+}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStoreBuilder.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStoreBuilder.java
new file mode 100644
index 0000000..346e298
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/MemoryStoreBuilder.java
@@ -0,0 +1,42 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+import org.apache.juneau.*;
+
+/**
+ * Builder for {@link MemoryStore} objects.
+ */
+public class MemoryStoreBuilder extends StoreBuilder {
+
+	/**
+	 * Constructor, default settings.
+	 */
+	public MemoryStoreBuilder() {
+		super();
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param ps The initial configuration settings for this builder.
+	 */
+	public MemoryStoreBuilder(PropertyStore ps) {
+		super(ps);
+	}
+
+	@Override /* ContextBuilder */
+	public MemoryStore build() {
+		return new MemoryStore(getPropertyStore());
+	}
+}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSource.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/Store.java
similarity index 51%
rename from juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSource.java
rename to juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/Store.java
index 02c8038..f340ae5 100644
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/ConfigSource.java
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/Store.java
@@ -10,33 +10,34 @@
 // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
 // * specific language governing permissions and limitations under the License.         
                                    *
 // ***************************************************************************************************************************
-package org.apache.juneau.config;
+package org.apache.juneau.config.source;
 
-/**
- * Represents a storage location of a configuration file.
- */
-public abstract class ConfigSource {
+import java.io.*;
+import java.util.*;
 
-	/** The path of the config file. */
-	private final ConfigSourceSettings settings;
+import org.apache.juneau.*;
 
+/**
+ * Represents a storage location for configuration files.
+ * 
+ * <p>
+ * Content stores require two methods to be implemented:
+ * <ul>
+ * 	<li class='jm'>{@link #read(String)} - Retrieve a config file.
+ * 	<li class='jm'>{@link #write(String,String,String)} - Store a config file.
+ * </ul>
+ */
+public abstract class Store extends Context implements Closeable {
+	
+	private final List<StoreListener> listeners = new LinkedList<>();
+	
 	/**
 	 * Constructor.
 	 * 
-	 * @param settings
-	 * 	The settings for this config source.
-	 */
-	protected ConfigSource(ConfigSourceSettings settings) {
-		this.settings = settings;
-	}
-
-	/**
-	 * Returns the name of the config file.
-	 * 
-	 * @return The name of the config file.
+	 * @param ps The settings for this content store.
 	 */
-	protected final ConfigSourceSettings getSettings() {
-		return settings;
+	protected Store(PropertyStore ps) {
+		super(ps);
 	}
 
 	/**
@@ -46,29 +47,71 @@ public abstract class ConfigSource {
 	 * @return The contents of the configuration file.
 	 * @throws Exception
 	 */
-	protected abstract String read(String name) throws Exception;
+	public abstract String read(String name) throws Exception;
 
 	/**
 	 * Saves the contents of the configuration file if the underlying storage hasn't been modified.
 	 * 
 	 * @param name The config file name.
-	 * @param contents The new contents of the configuration file.
+	 * @param oldContents The old contents.
+	 * @param newContents The new contents.
 	 * @return <jk>true</jk> if we successfully saved the new configuration file
contents, or <jk>false</jk> if the
 	 * 	underlying storage changed since the last time the {@link #read(String)} method was
called.
 	 * @throws Exception
 	 */
-	protected abstract boolean write(String name, String contents) throws Exception;
+	public abstract boolean write(String name, String oldContents, String newContents) throws
Exception;
+
+	/**
+	 * Registers a new listener on this store.
+	 * 
+	 * @param l The new listener.
+	 * @return This object (for method chaining).
+	 */
+	public Store register(StoreListener l) {
+		this.listeners.add(l);
+		return this;
+	}
+	
+	/**
+	 * Unregisters a listener from this store.
+	 * 
+	 * @param l The listener to unregister.
+	 * @return This object (for method chaining).
+	 */
+	public Store unregister(StoreListener l) {
+		this.listeners.remove(l);
+		return this;
+	}
 
 	/**
-	 * Returns whether the underlying configuration contents have changed.
+	 * Called when the physical contents of a config file have changed.
 	 * 
 	 * <p>
-	 * For example, if the configuration source is a file, this method would return <jk>true</jk>
if the
-	 * file on the filesystem has been modified since the {@link #read(String)} method was called.
+	 * Triggers calls to {@link StoreListener#onChange(String, String)} on all registered listeners.
 	 * 
-	 * @param name The config file name.
-	 * @return <jk>true</jk> if the persisted contents of the config file have changed.
-	 * @throws Exception
+	 * @param name The config name (e.g. the filename without the extension).
+	 * @param contents The new contents.
+	 * @return This object (for method chaining).
+	 */
+	protected Store onChange(String name, String contents) {
+		for (StoreListener l : listeners)
+			l.onChange(name, contents);
+		return this;
+	}
+	
+	/**
+	 * Unused.
 	 */
-	protected abstract boolean hasBeenModified(String name) throws Exception;
+	@Override /* Context */
+	public final Session createSession(SessionArgs args) {
+		throw new NoSuchMethodError();
+	}
+
+	/**
+	 * Unused.
+	 */
+	@Override /* Context */
+	public final SessionArgs createDefaultSessionArgs() {
+		throw new NoSuchMethodError();
+	}
 }
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreBuilder.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreBuilder.java
new file mode 100644
index 0000000..c3a7b6e
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreBuilder.java
@@ -0,0 +1,40 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+import org.apache.juneau.*;
+
+/**
+ * Base builder class for {@link Store} objects.
+ */
+public abstract class StoreBuilder extends ContextBuilder {
+
+	/**
+	 * Constructor, default settings.
+	 */
+	public StoreBuilder() {
+		super();
+	}
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param ps The initial configuration settings for this builder.
+	 */
+	public StoreBuilder(PropertyStore ps) {
+		super(ps);
+	}
+
+	@Override
+	public abstract Store build();
+}
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreListener.java
b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreListener.java
new file mode 100644
index 0000000..0526e56
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/source/StoreListener.java
@@ -0,0 +1,27 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.
 The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance            *
+// * with the License.  You may obtain a copy of the License at                         
                                    * 
+// *                                                                                    
                                    *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                        
                                    *
+// *                                                                                    
                                    *
+// * Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the        *
+// * specific language governing permissions and limitations under the License.         
                                    *
+// ***************************************************************************************************************************
+package org.apache.juneau.config.source;
+
+/**
+ * Listens for changes to stored config files.
+ */
+public interface StoreListener {
+	
+	/**
+	 * Called when the physical contents of a config file have changed.
+	 * 
+	 * @param name The config name (e.g. the filename without the extension).
+	 * @param contents The new config contents;
+	 */
+	void onChange(String name, String contents);
+}

-- 
To stop receiving notification emails like this one, please contact
jamesbognar@apache.org.

Mime
View raw message