You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tamaya.apache.org by ot...@apache.org on 2014/12/22 23:05:54 UTC
incubator-tamaya git commit: adds listener to specific folder
Repository: incubator-tamaya
Updated Branches:
refs/heads/master 26e7f2e95 -> 22d2d1ad8
adds listener to specific folder
Project: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/commit/22d2d1ad
Tree: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/tree/22d2d1ad
Diff: http://git-wip-us.apache.org/repos/asf/incubator-tamaya/diff/22d2d1ad
Branch: refs/heads/master
Commit: 22d2d1ad87cdc38aba43115f946a79b684bd8cf1
Parents: 26e7f2e
Author: otaviojava <ot...@people.apache.org>
Authored: Mon Dec 22 20:00:18 2014 -0200
Committer: otaviojava <ot...@people.apache.org>
Committed: Mon Dec 22 20:03:40 2014 -0200
----------------------------------------------------------------------
.../internal/config/FileChangeListener.java | 137 +++++++++++++++++++
.../internal/config/FileChangeObserver.java | 13 ++
.../core/internal/config/FileConfiguration.java | 74 ++++++++++
.../tamaya/core/internal/config/FileReader.java | 80 +++++++++++
.../config/FilesPropertiesConfigProvider.java | 97 +++++++++++++
.../FilesPropertiesConfigProviderTest.java | 69 ++++++++++
.../test/resources/META-INF/config/example.ini | 2 +-
.../META-INF/configuration/example.ini | 18 +++
.../META-INF/configuration/example.properties | 20 +++
.../META-INF/configuration/example.xml | 23 ++++
10 files changed, 532 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeListener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeListener.java b/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeListener.java
new file mode 100644
index 0000000..94e6694
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeListener.java
@@ -0,0 +1,137 @@
+package org.apache.tamaya.core.internal.config;
+
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.log4j.Logger;
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.core.spi.ConfigurationProviderSpi;
+
+/**
+ * Class that has the responsibility to watch the folder and then update the {@link ConfigurationProviderSpi}
+ * to update the Configuration from the properties or xml files, another ones will be ignored.
+ * @see FilesPropertiesConfigProvider
+ * This listener will wait to events and wait to one second to watch again.
+ * <p>If new file was created or modified will update from this file.</p>
+ * <p>If a file was removed then the listener will load using all files left.</p>
+ * @author otaviojava
+ */
+class FileChangeListener implements Runnable {
+
+ private static final Logger LOGGER = Logger.getLogger(FileChangeListener.class);
+
+ private WatchService watchService;
+
+ private FileChangeObserver observer;
+
+ private Map<String, String> configurationMap;
+
+ private Path directory;
+
+ private FileReader fileReader = new FileReader();
+
+ public FileChangeListener(FileChangeObserver observer, Map<String, String> mapConfiguration, Path directory) {
+ this.observer = observer;
+ this.configurationMap = mapConfiguration;
+ this.directory = directory;
+ this.watchService = getWatchService();
+
+ if (Objects.nonNull(watchService) && Objects.nonNull(directory)) {
+ try {
+ directory.register(watchService, ENTRY_DELETE, ENTRY_MODIFY,
+ ENTRY_CREATE);
+ } catch (IOException e) {
+ throw new FileChangeListenerException("An error happened when does try to registry to watch the folder", e);
+ }
+ }
+ }
+
+
+ @Override
+ public void run() {
+ if (Objects.isNull(watchService) || Objects.isNull(directory)) {
+ return;
+ }
+ while (true) {
+ watchFolder();
+ }
+ }
+
+
+ private void watchFolder() {
+ try {
+ WatchKey watckKey = watchService.take();
+ boolean needUpdate = false;
+ for (WatchEvent<?> event : watckKey.pollEvents()) {
+ Path keyDirectory = (Path) watckKey.watchable();
+ if(listenerPath(event, keyDirectory)) {
+ needUpdate = true;
+ }
+ }
+
+ if (needUpdate) {
+ observer.update(configurationMap);
+ }
+ watckKey.reset();
+ Thread.sleep(1_000L);
+ } catch (Exception e) {
+ throw new FileChangeListenerException("An error happened when does try to watch the folder", e);
+ }
+ }
+
+ private boolean listenerPath(WatchEvent<?> event, Path keyDirectory) {
+ boolean wasModified = false;
+ Path path = keyDirectory.resolve((Path)event.context());
+ if(fileReader.isObservavleFile(path)) {
+
+ if (event.kind() == ENTRY_CREATE || event.kind() == ENTRY_MODIFY) {
+ wasModified = true;
+ configurationMap.putAll(fileReader.runFile(path.toAbsolutePath()));
+ LOGGER.info("An event was detected in file: " + path.getFileName());
+ }
+
+ if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
+ wasModified = true;
+ configurationMap = fileReader.runFiles(directory);
+ LOGGER.info("A remotion event was detected in file: " + path.getFileName());
+ }
+
+ } else {
+ LOGGER.info("Ignoring the file: " + path.getFileName() + " because is not a properties or xml file");
+ }
+ return wasModified;
+ }
+
+ private WatchService getWatchService() {
+ try {
+ FileSystem fileSystem = Paths.get(".").getFileSystem();
+ return fileSystem.newWatchService();
+ } catch (IOException e) {
+ LOGGER.warn("This file System does not supports WatchService", e);
+ return null;
+ }
+
+ }
+
+ class FileChangeListenerException extends ConfigException {
+
+ private static final long serialVersionUID = -8965486770881001513L;
+
+ public FileChangeListenerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeObserver.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeObserver.java b/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeObserver.java
new file mode 100644
index 0000000..e4fc95d
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/internal/config/FileChangeObserver.java
@@ -0,0 +1,13 @@
+package org.apache.tamaya.core.internal.config;
+
+import java.util.Map;
+
+/**
+ * Observer to be used in {@link FileChangeListener} to update all configurations and provider.
+ * @author otaviojava
+ */
+interface FileChangeObserver {
+
+ void update(Map<String, String> configurationMap);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/main/java/org/apache/tamaya/core/internal/config/FileConfiguration.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/internal/config/FileConfiguration.java b/core/src/main/java/org/apache/tamaya/core/internal/config/FileConfiguration.java
new file mode 100644
index 0000000..0b5c2c7
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/internal/config/FileConfiguration.java
@@ -0,0 +1,74 @@
+package org.apache.tamaya.core.internal.config;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.MetaInfo;
+
+/**
+ * Implementation of Configuration which the information is from xml or properties files.
+ * Once the File modified, it will update automatically by provider.
+ * @see FilesPropertiesConfigProvider
+ * @see FileChangeObserver
+ * @author otaviojava
+ */
+class FileConfiguration implements Configuration, FileChangeObserver {
+
+ private Map<String, String> configurationMap;
+
+ public FileConfiguration(Map<String, String> configurationMap) {
+ this.configurationMap = configurationMap;
+ }
+
+ @Override
+ public Optional<String> get(String key) {
+ return Optional.ofNullable(configurationMap.get(key));
+ }
+
+ @Override
+ public MetaInfo getMetaInfo() {
+ return MetaInfo.of("files.config");
+ }
+
+ @Override
+ public boolean containsKey(String key) {
+ return configurationMap.containsKey(key);
+ }
+
+ @Override
+ public Map<String, String> toMap() {
+ return configurationMap;
+ }
+
+ @Override
+ public void update(Map<String, String> configurationMap) {
+ synchronized (this) {
+ this.configurationMap = configurationMap;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "org.apache.tamaya.core.internal.config.FileConfiguration: " + configurationMap.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(configurationMap);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(this == obj) {
+ return true;
+ }
+ if(Configuration.class.isInstance(obj)) {
+ Configuration other = Configuration.class.cast(obj);
+ return Objects.equals(configurationMap, other.toMap());
+ }
+
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/main/java/org/apache/tamaya/core/internal/config/FileReader.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/internal/config/FileReader.java b/core/src/main/java/org/apache/tamaya/core/internal/config/FileReader.java
new file mode 100644
index 0000000..287ccc4
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/internal/config/FileReader.java
@@ -0,0 +1,80 @@
+package org.apache.tamaya.core.internal.config;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.InvalidPropertiesFormatException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Class to read a file and creates a {@link Map} of {@link String}.
+ * The implementation of {@link Map} will {@link HashMap}
+ * @author otaviojava
+ */
+class FileReader {
+
+ private static final String EXTENSIONS_ACCEPTED = "*.{xml,properties}";
+
+ public Map<String, String> runFiles(Path directory) {
+ Properties properties = createProperties(directory);
+ return properties
+ .stringPropertyNames()
+ .stream()
+ .collect(
+ Collectors.toMap(Function.identity(),
+ properties::getProperty));
+ }
+
+ public Map<String, String> runFile(Path path) {
+ Properties properties = new Properties();
+ try {
+ loadFile(properties, path);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return properties
+ .stringPropertyNames()
+ .stream()
+ .collect(
+ Collectors.toMap(Function.identity(),
+ properties::getProperty));
+ }
+
+ private Properties createProperties(Path directory) {
+ Properties properties = new Properties();
+
+ try {
+ for (Path path : Files.newDirectoryStream(directory, EXTENSIONS_ACCEPTED)) {
+ loadFile(properties, path);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return properties;
+ }
+
+ private void loadFile(Properties properties, Path path) throws IOException,
+ InvalidPropertiesFormatException {
+ if (isXmlExtension(path)) {
+ properties.loadFromXML(Files.newInputStream(path));
+ } else {
+ properties.load(Files.newInputStream(path));
+ }
+}
+
+ private boolean isXmlExtension(Path path) {
+ return path.toString().toLowerCase().endsWith(".xml");
+ }
+
+ private boolean isPropertiesExtension(Path path) {
+ return path.toString().toLowerCase().endsWith(".properties");
+ }
+
+ public boolean isObservavleFile(Path path) {
+ return isPropertiesExtension(path) || isXmlExtension(path);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/main/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProvider.java b/core/src/main/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProvider.java
new file mode 100644
index 0000000..5cd1a93
--- /dev/null
+++ b/core/src/main/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProvider.java
@@ -0,0 +1,97 @@
+package org.apache.tamaya.core.internal.config;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.tamaya.ConfigException;
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.core.spi.ConfigurationProviderSpi;
+
+/**
+ * This implementation run in a folder and once found xml and properties files
+ * will create the Configuration, when one file is created, deleted or modified the configuration will update
+ * automatically.
+ * The default folder is META-INF/configuration, but you can change using the absolute path in
+ * "-Dtamaya.configbase" parameter.
+ * @author otaviojava
+ */
+public class FilesPropertiesConfigProvider implements ConfigurationProviderSpi, FileChangeObserver {
+
+ private static final String DEFAULT_CONFIG_NAME = "files.configuration";
+
+ private Map<String, String> configurationMap = Collections.emptyMap();
+
+ private ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ private List<FileChangeObserver> fileChangeObservers = new ArrayList<>();
+
+ public FilesPropertiesConfigProvider() {
+ Path directory = getDirectory();
+ if (Objects.nonNull(directory)) {
+ configurationMap = new FileReader().runFiles(directory);
+ Runnable runnable = new FileChangeListener(this, configurationMap, directory);
+ executor.execute(runnable);
+ } else {
+ executor.shutdown();
+ }
+ }
+
+ @Override
+ public String getConfigName() {
+ return DEFAULT_CONFIG_NAME;
+ }
+
+ private Path getDirectory() {
+ String absolutePath = System.getProperty("tamaya.configbase");
+
+ if(Objects.nonNull(absolutePath)) {
+ Path path = Paths.get(absolutePath);
+ if(Files.isDirectory(path)) {
+ return path;
+ }
+ }
+
+ URL resource = FilesPropertiesConfigProvider.class.getResource("/META-INF/configuration/");
+ if (Objects.nonNull(resource)) {
+ try {
+ return Paths.get(resource.toURI());
+ } catch (URISyntaxException e) {
+ throw new ConfigException("An error to find the directory to watch", e);
+ }
+ }
+ return null;
+ }
+
+
+ @Override
+ public Configuration getConfiguration() {
+ return new FileConfiguration(Collections.unmodifiableMap(configurationMap));
+ }
+
+ @Override
+ public void reload() {
+ Path directory = getDirectory();
+ if (Objects.nonNull(directory)) {
+ configurationMap = new FileReader().runFiles(directory);
+ }
+ }
+
+ @Override
+ public void update(Map<String, String> configurationMap) {
+ synchronized (this) {
+ this.configurationMap = configurationMap;
+ Map<String, String> unmodifiableMap = Collections.unmodifiableMap(configurationMap);
+ fileChangeObservers.forEach(fi -> fi.update(unmodifiableMap));
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/test/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProviderTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProviderTest.java b/core/src/test/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProviderTest.java
new file mode 100644
index 0000000..5aeeec5
--- /dev/null
+++ b/core/src/test/java/org/apache/tamaya/core/internal/config/FilesPropertiesConfigProviderTest.java
@@ -0,0 +1,69 @@
+package org.apache.tamaya.core.internal.config;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.tamaya.Configuration;
+import org.apache.tamaya.core.spi.ConfigurationProviderSpi;
+import org.junit.Before;
+import org.junit.Test;
+
+public class FilesPropertiesConfigProviderTest {
+
+
+ private ConfigurationProviderSpi configurationProvider;
+
+ @Before
+ public void init() throws InterruptedException {
+ configurationProvider = new FilesPropertiesConfigProvider();
+ }
+
+ @Test
+ public void getTest() throws InterruptedException{
+ Configuration configuration = configurationProvider.getConfiguration();
+ assertEquals(configuration.get("team").get(), "Bahia");
+ assertFalse(configuration.get("ignore").isPresent());
+ }
+
+ @Test
+ public void shouldUpdateAsync() throws Exception {
+ createPropertiesFile("newFile.properties", "language=java");
+ Configuration configuration = configurationProvider.getConfiguration();
+
+ Thread.sleep(100L);
+ assertEquals(configuration.get("language").get(), "java");
+
+ }
+
+ @Test
+ public void shoulIgnoreAsync() throws Exception {
+ createPropertiesFile("newFile.ini", "name=otavio");
+ Configuration configuration = configurationProvider.getConfiguration();
+
+ Thread.sleep(100L);
+ assertFalse(configuration.get("otavio").isPresent());
+
+ }
+
+ private void createPropertiesFile(String fileName, String context) throws URISyntaxException,
+ FileNotFoundException, IOException {
+ URL resource = FilesPropertiesConfigProviderTest.class.getResource("/META-INF/configuration/");
+ Path directory = Paths.get(resource.toURI());
+ File file = new File(directory.toFile(), fileName);
+ try (OutputStream stream = new FileOutputStream(file)) {
+ stream.write(context.getBytes());
+ file.deleteOnExit();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/test/resources/META-INF/config/example.ini
----------------------------------------------------------------------
diff --git a/core/src/test/resources/META-INF/config/example.ini b/core/src/test/resources/META-INF/config/example.ini
index dcbb1bc..95a2b7c 100644
--- a/core/src/test/resources/META-INF/config/example.ini
+++ b/core/src/test/resources/META-INF/config/example.ini
@@ -28,4 +28,4 @@ prename=Peter
name=Fischbach
address=Hand Platz
zip=4556
-location=Unterland
\ No newline at end of file
+location=Unterland
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/test/resources/META-INF/configuration/example.ini
----------------------------------------------------------------------
diff --git a/core/src/test/resources/META-INF/configuration/example.ini b/core/src/test/resources/META-INF/configuration/example.ini
new file mode 100644
index 0000000..e2cc979
--- /dev/null
+++ b/core/src/test/resources/META-INF/configuration/example.ini
@@ -0,0 +1,18 @@
+#
+# 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 current 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.
+ignore=value
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/test/resources/META-INF/configuration/example.properties
----------------------------------------------------------------------
diff --git a/core/src/test/resources/META-INF/configuration/example.properties b/core/src/test/resources/META-INF/configuration/example.properties
new file mode 100644
index 0000000..ed6148f
--- /dev/null
+++ b/core/src/test/resources/META-INF/configuration/example.properties
@@ -0,0 +1,20 @@
+#
+# 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 current 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.
+country=Brazil
+nacionality=Brazilian
+love=poliana
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya/blob/22d2d1ad/core/src/test/resources/META-INF/configuration/example.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/META-INF/configuration/example.xml b/core/src/test/resources/META-INF/configuration/example.xml
new file mode 100644
index 0000000..6a3694a
--- /dev/null
+++ b/core/src/test/resources/META-INF/configuration/example.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+#
+# Copyright 2014 Anatole Tresch and other (see authors).
+#
+# you may not use this file except in compliance with the License.
+# You may obtain a copy current 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.
+#
+-->
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
+<properties version="1.0">
+ <entry key="twitter.otavio">otaviojava</entry>
+ <entry key="team">Bahia</entry>
+ <entry key="sport">Soccer</entry>
+</properties>
\ No newline at end of file