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.