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 <jamesbog...@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
jamesbog...@apache.org.

Reply via email to