http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/delta/package-info.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/delta/package-info.java b/events/src/main/java/org/apache/tamaya/events/delta/package-info.java new file mode 100644 index 0000000..2006717 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/delta/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +/** + * This package contains artifacts to describe the changes (delta) of a + * Configuration or a PropertySource. + */ +package org.apache.tamaya.events.delta; \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeListener.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeListener.java b/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeListener.java new file mode 100644 index 0000000..283719e --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeListener.java @@ -0,0 +1,144 @@ +/* + * 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.tamaya.events.folderobserver; + +import org.apache.tamaya.ConfigException; + +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.logging.Level; +import java.util.logging.Logger; + + +/** + * Class that has the responsibility to watch the folder and then publish the changes to a + * {@link org.apache.tamaya.events.PropertySourceChange}. + * @see ObservingPropertySourceProvider + * This listener will wait to events and wait to one second to watch again. + * <p>If new file was created or modified will commit 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.getName()); + + private final WatchService watchService; + + private final FileChangeObserver observer; + + private final Path directory; + + private volatile boolean running = true; + + public FileChangeListener(Path directory, FileChangeObserver observer) { + this.observer = observer; + this.directory = directory; + this.watchService = getWatchService(); + + if (watchService!=null && directory!=null) { + try { + directory.register(watchService, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_CREATE); + } catch (IOException e) { + throw new FileChangeListenerException("An error happened when does try to registry to watch the folder", e); + } + } + } + + /** + * Stops the listener service from observing the target directory. + */ + public void stopListener(){ + running = false; + } + + @Override + public void run() { + if (watchService!=null || directory!=null) { + return; + } + while (running) { + watchFolder(); + } + } + + /** + * Start watching the current folder. + */ + private void watchFolder() { + try { + WatchKey watckKey = watchService.take(); + for (WatchEvent<?> event : watckKey.pollEvents()) { + Path filePath = (Path) watckKey.watchable(); + if(event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE)|| + event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY) || + event.kind().equals(StandardWatchEventKinds.ENTRY_DELETE)){ + LOGGER.info("File change detected in: " + filePath.getFileName()); + observer.directoryChanged(filePath); + } + } + watckKey.reset(); + Thread.sleep(1_000L); + } catch (Exception e) { + throw new FileChangeListenerException("An error happened when does try to watch the folder", e); + } + } + + /** + * Get the watch service. + * @return the watch service, or null, if the watch service is not supported. + */ + private WatchService getWatchService() { + try { + FileSystem fileSystem = Paths.get(".").getFileSystem(); + return fileSystem.newWatchService(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "The file System does not supports WatchService", e); + return null; + } + + } + + /** + * Exception if file listening fails. + */ + static class FileChangeListenerException extends ConfigException { + /** Serialversion UID. */ + private static final long serialVersionUID = -8965486770881001513L; + + /** + * Constructor. + * @param message a message + * @param cause an (optional) root cause. + */ + 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-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeObserver.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeObserver.java b/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeObserver.java new file mode 100644 index 0000000..63d25cd --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/folderobserver/FileChangeObserver.java @@ -0,0 +1,33 @@ +/* + * 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.tamaya.events.folderobserver; + +import java.nio.file.Path; + +/** + * Observer to be used in {@link FileChangeListener} to commit all configurations and provider. + */ +interface FileChangeObserver { + /** + * Called when a file has been modified. + * @param path the file path, not null. + */ + void directoryChanged(Path path); + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/folderobserver/ObservingPropertySourceProvider.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/folderobserver/ObservingPropertySourceProvider.java b/events/src/main/java/org/apache/tamaya/events/folderobserver/ObservingPropertySourceProvider.java new file mode 100644 index 0000000..feddd70 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/folderobserver/ObservingPropertySourceProvider.java @@ -0,0 +1,209 @@ +/* + * 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.tamaya.events.folderobserver; + +import java.io.IOException; +import java.io.InputStream; +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.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.events.ConfigEventManager; +import org.apache.tamaya.events.ConfigurationContextChange; +import org.apache.tamaya.events.ConfigurationContextChangeBuilder; +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertySourceProvider; +import org.apache.tamaya.spisupport.BasePropertySource; + +/** + * This implementation runs in a folder taking up all files compatible with the given + * ConfigurationFormats. When a file is added, deleted or modified the PropertySourceProvider + * will adapt the changes automatically and trigger according + * {@link org.apache.tamaya.events.PropertySourceChange} events. + * The default folder is META-INF/config, but you can change it via an absolute path in the + * "-Dtamaya.configdir" parameter. + */ +public class ObservingPropertySourceProvider implements PropertySourceProvider, FileChangeObserver { + /** + * The logger. + */ + private static final Logger LOG = Logger.getLogger(ObservingPropertySourceProvider.class.getName()); + /** + * The current active property sources of this provider. + */ + private final List<PropertySource> propertySources = Collections.synchronizedList(new LinkedList<PropertySource>()); + /** + * The thread pool used. + */ + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + /** + * Constructorm using an explicit directory, ignoring all kind of configuration, if set. + * + * @param directory the target directory. If null, the default configuration and system property are used. + */ + public ObservingPropertySourceProvider(Path directory) { + if (directory == null) { + directory = getDirectory(); + } + if (directory!=null){ + synchronized (this.propertySources) { + this.propertySources.addAll(readConfiguration(directory)); + } + final Runnable runnable = new FileChangeListener(directory, this); + executor.execute(runnable); + } else { + executor.shutdown(); + } + } + + /** + * Read the initial configuration. + * + * @param directory the target directory, not null. + */ + private List<PropertySource> readConfiguration(Path directory) { + final List<PropertySource> result = new ArrayList<>(); + try { + synchronized (propertySources) { + for (final Path path : Files.newDirectoryStream(directory, "*")) { + result.addAll(getPropertySources(path)); + } + return result; + } + } catch (final IOException e) { + LOG.log(Level.WARNING, "Failed to read configuration from dir: " + directory, e); + } + return result; + } + + /** + * Read property sources from the given file. + * + * @param file source of the property sources. + * @return property sources from the given file. + */ + protected Collection<PropertySource> getPropertySources(final Path file) { + return Arrays.asList(new PropertySource[]{new BasePropertySource() { + private final Map<String,String> props = readProperties(file); + + @Override + public Map<String, String> getProperties() { + return props; + } + }}); + } + + /** + * Load a single file. + * + * @param file the file, not null. + * @return properties as read from the given file. + */ + protected static Map<String,String> readProperties(Path file) { + try (InputStream is = file.toUri().toURL().openStream()){ + final Properties props = new Properties(); + props.load(is); + final Map<String,String> result = new HashMap<>(); + for(final Map.Entry<Object,Object> en:props.entrySet()){ + result.put(String.valueOf(en.getKey()), String.valueOf(en.getValue())); + } + return result; + } catch (final Exception e) { + LOG.log(Level.INFO, "Error reading file: " + file.toString() + + ", using format: properties", e); + } + return Collections.emptyMap(); + } + + + /** + * Evaluates the target directory from system property (tamaya.configdir) or classpath. + * + * @return the directory to be read, or null. + */ + private Path getDirectory() { + final String absolutePath = System.getProperty("tamaya.configdir"); + if (null!=absolutePath) { + final Path path = Paths.get(absolutePath); + if (Files.isDirectory(path)) { + return path; + } + } + final URL resource = ObservingPropertySourceProvider.class.getResource("/META-INF/config/"); + if (null!=resource) { + try { + return Paths.get(resource.toURI()); + } catch (final URISyntaxException e) { + throw new ConfigException("An error to find the directory to watch", e); + } + } + return null; + } + + + @Override + public void directoryChanged(Path directory) { + synchronized (this.propertySources) { + final List<PropertySource> existingPropertySources = new ArrayList<>(propertySources); + propertySources.clear(); + final Collection<PropertySource> sourcesRead = readConfiguration(directory); + this.propertySources.addAll(sourcesRead); + triggerConfigChange(existingPropertySources, propertySources); + } + } + + + private void triggerConfigChange(List<PropertySource> originalPropertySources, + List<PropertySource> newPropertySources) { + final ConfigurationContextChangeBuilder b = ConfigurationContextChangeBuilder.of(); + for (final PropertySource ps : originalPropertySources) { + b.removedPropertySource(ps); + } + for (final PropertySource ps : newPropertySources) { + b.newPropertySource(ps); + } + final ConfigurationContextChange changeEvent = b.build(); + LOG.fine("Trigger Config Context Change: " + changeEvent); + ConfigEventManager.fireEvent(changeEvent); + } + + @Override + public Collection<PropertySource> getPropertySources() { + synchronized (propertySources) { + return new ArrayList<>(this.propertySources); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/folderobserver/package-info.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/folderobserver/package-info.java b/events/src/main/java/org/apache/tamaya/events/folderobserver/package-info.java new file mode 100644 index 0000000..347f2d8 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/folderobserver/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +/** + * This package contains code to observe a folder for file changes and to trigger + * corresponding events, that are handled by an according {@link org.apache.tamaya.events.folderobserver.ObservingPropertySourceProvider} + * instance. + */ +package org.apache.tamaya.events.folderobserver; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigChangeObserver.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigChangeObserver.java b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigChangeObserver.java new file mode 100644 index 0000000..f4457b2 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigChangeObserver.java @@ -0,0 +1,111 @@ +/* + * 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.tamaya.events.internal; + +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.events.ConfigEventManager; +import org.apache.tamaya.events.ConfigurationChange; +import org.apache.tamaya.events.ConfigurationChangeBuilder; +import org.apache.tamaya.events.FrozenConfiguration; + +import java.util.*; +import java.util.logging.Logger; + +/** + * Timer task that regularly checks the configuration for changes. + */ +public class DefaultConfigChangeObserver { + + private static final long START_DELAY = 5000L; + + private static final Logger LOG = Logger.getLogger(DefaultConfigChangeObserver.class.getName()); + + private Timer timer = new Timer("DefaultConfigChangeObserver", true); + + private long checkPeriod = 2000L; + + private volatile FrozenConfiguration lastConfig; + + private volatile boolean running; + + /** + * Constructor. Also loads all registered listeners. + */ + public DefaultConfigChangeObserver() { + LOG.info("Registering config change observer, rechecking config changes every " + checkPeriod + " ms."); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if(running) { + checkConfigurationUpdate(); + } + } + }, START_DELAY, checkPeriod); + } + + + public void checkConfigurationUpdate() { + LOG.finest("Checking configuration for changes..."); + FrozenConfiguration newConfig = FrozenConfiguration.of(ConfigurationProvider.getConfiguration()); + ConfigurationChange changes; + if(lastConfig==null){ + changes = ConfigurationChangeBuilder.of(newConfig).putAll(newConfig.getProperties()) + .build(); + }else{ + changes = ConfigurationChangeBuilder.of(lastConfig).addChanges(newConfig) + .build(); + } + if(!changes.isEmpty()) { + LOG.info("Identified configuration changes, publishing change event..."); + ConfigEventManager.fireEvent(changes); + } + } + + public long getCheckPeriod() { + return checkPeriod; + } + + public boolean isMonitoring(){ + return running; + } + + public void enableMonitoring(boolean enable){ + this.running = true; + } + + /** + * Sets the new check period, cancels the currently running timer and schedules a new task with the new checkperiod + * and a startup delay of 500ms. + * @param checkPeriod the period in ms, for checking on changes. + */ + public void setCheckPeriod(long checkPeriod) { + LOG.finest("Resetting check period to " + checkPeriod + " ms, reregistering timer."); + this.checkPeriod = checkPeriod; + timer.cancel(); + timer = new Timer("DefaultConfigChangeObserver", true); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if(running) { + checkConfigurationUpdate(); + } + } + }, 500L, checkPeriod); + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigEventManagerSpi.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigEventManagerSpi.java b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigEventManagerSpi.java new file mode 100644 index 0000000..586df5c --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigEventManagerSpi.java @@ -0,0 +1,202 @@ +/* + * 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.tamaya.events.internal; + +import org.apache.tamaya.events.ConfigEvent; +import org.apache.tamaya.events.ConfigEventListener; +import org.apache.tamaya.events.spi.ConfigEventManagerSpi; +import org.apache.tamaya.spi.ServiceContextManager; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default implementation of {@link DefaultConfigEventManagerSpi} just forwarding all + * events synchronously to the listeners. + */ +public class DefaultConfigEventManagerSpi implements ConfigEventManagerSpi { + + private static final Logger LOG = Logger.getLogger(DefaultConfigEventManagerSpi.class.getName()); + + private final Map<Class,List<ConfigEventListener>> listeners = new ConcurrentHashMap<>(); + + private final ExecutorService publisher = Executors.newCachedThreadPool(); + + private final DefaultConfigChangeObserver changeObserver = new DefaultConfigChangeObserver(); + + /** + * Constructor. Also loads all registered listeners. + */ + public DefaultConfigEventManagerSpi() { + try { + for (ConfigEventListener l : ServiceContextManager.getServiceContext().getServices(ConfigEventListener.class)) { + try { + addListener(l); + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to load configured listener: " + l.getClass().getName(), e); + } + } + } catch (Exception e) { + LOG.log(Level.WARNING, "Failed to load configured listeners.", e); + } + } + + @Override + public void addListener(ConfigEventListener l){ + addListener(l, ConfigEvent.class); + } + + @Override + public <T extends ConfigEvent> void addListener(ConfigEventListener l, Class<T> eventType){ + List<ConfigEventListener> ls = listeners.get(eventType); + if(ls==null){ + ls = Collections.synchronizedList(new ArrayList<ConfigEventListener>()); + listeners.put(eventType, ls); + } + synchronized (ls){ + if(!ls.contains(l)){ + ls.add(l); + } + } + } + + @Override + public void removeListener(ConfigEventListener l){ + removeListener(l, ConfigEvent.class); + } + + @Override + public <T extends ConfigEvent> void removeListener(ConfigEventListener l, Class<T> eventType) { + List<ConfigEventListener> targets = this.listeners.get(eventType); + if(targets!=null) { + // forward to explicit listeners + synchronized (targets) { + targets.remove(l); + } + } + } + + @Override + public Collection<? extends ConfigEventListener> getListeners(Class<? extends ConfigEvent> eventType) { + List<ConfigEventListener> targets = this.listeners.get(eventType); + if(targets!=null){ + synchronized(targets){ + return new ArrayList<>(targets); + } + } + return Collections.emptyList(); + } + + @Override + public Collection<? extends ConfigEventListener> getListeners() { + Set<ConfigEventListener> targets = new HashSet<>(); + for(List<ConfigEventListener> l:this.listeners.values()){ + targets.addAll(l); + } + return targets; + } + + @Override + public void fireEvent(ConfigEvent<?> event) { + List<ConfigEventListener> targets = this.listeners.get(event.getClass()); + if(targets!=null) { + // forward to explicit listeners + synchronized (targets) { + for (ConfigEventListener l : targets) { + l.onConfigEvent(event); + } + } + } + // forward to global listeners + targets = this.listeners.get(ConfigEvent.class); + if(targets!=null) { + synchronized (targets) { + for (ConfigEventListener l : targets) { + l.onConfigEvent(event); + } + } + } + } + + @Override + public void fireEventAsynch(ConfigEvent<?> event) { + List<ConfigEventListener> targets = this.listeners.get(event.getClass()); + if(targets!=null) { + // forward to explicit listeners + synchronized (targets) { + for (ConfigEventListener l : targets) { + publisher.execute(new PublishConfigChangeTask(l, event)); + } + } + } + // forward to global listeners + targets = this.listeners.get(ConfigEvent.class); + if(targets!=null) { + synchronized (targets) { + for (ConfigEventListener l : targets) { + publisher.execute(new PublishConfigChangeTask(l, event)); + } + } + } + } + + @Override + public long getChangeMonitoringPeriod() { + return changeObserver.getCheckPeriod(); + } + + @Override + public void setChangeMonitoringPeriod(long millis){ + changeObserver.setCheckPeriod(millis); + } + + @Override + public boolean isChangeMonitorActive() { + return changeObserver.isMonitoring(); + } + + @Override + public void enableChangeMonitor(boolean enable) { + changeObserver.enableMonitoring(enable); + } + + + /** + * Tasks to inform observers on detected configuration changes. + */ + private static final class PublishConfigChangeTask implements Runnable{ + + private final ConfigEventListener l; + private final ConfigEvent<?> changes; + + public PublishConfigChangeTask(ConfigEventListener l, ConfigEvent<?> changes) { + this.l = Objects.requireNonNull(l); + this.changes = Objects.requireNonNull(changes); + } + + @Override + public void run() { + l.onConfigEvent(changes); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigurationContextChangeListener.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigurationContextChangeListener.java b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigurationContextChangeListener.java new file mode 100644 index 0000000..e49856d --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/internal/DefaultConfigurationContextChangeListener.java @@ -0,0 +1,74 @@ +/* + * 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.tamaya.events.internal; + +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.events.ConfigEvent; +import org.apache.tamaya.events.ConfigEventListener; +import org.apache.tamaya.events.ConfigurationContextChange; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConfigurationContextBuilder; +import org.apache.tamaya.spi.PropertySource; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default ConfigEventListener for ConfigurationContextChange events that updates the current context, if resources were + * affected. + */ +public class DefaultConfigurationContextChangeListener implements ConfigEventListener { + + private static final Logger LOG = Logger.getLogger(DefaultConfigurationContextChangeListener.class.getName()); + + @Override + public void onConfigEvent(ConfigEvent<?> event) { + if(event.getClass() == ConfigurationContextChange.class) { + ConfigurationContextChange contextChange = (ConfigurationContextChange) event; + ConfigurationContext context = ConfigurationProvider.getConfigurationContext(); + List<PropertySource> affectedPropertySources = new ArrayList<>(); + for (PropertySource ps : context.getPropertySources()) { + if (contextChange.isAffected(ps)) { + affectedPropertySources.add(ps); + } + } + ConfigurationContextBuilder newContextBuilder = ConfigurationProvider.getConfigurationContextBuilder() + .setContext(context); + if (!affectedPropertySources.isEmpty()) { + Set<String> propertySourceNames = new HashSet<>(); + for (PropertySource removed : contextChange.getRemovedPropertySources()) { + propertySourceNames.add(removed.getName()); + } + newContextBuilder.removePropertySources(propertySourceNames); + } + newContextBuilder.addPropertySources(contextChange.getAddedPropertySources()); + newContextBuilder.addPropertySources(contextChange.getUpdatedPropertySources()); + ConfigurationContext newContext = newContextBuilder.build(); + try { + ConfigurationProvider.setConfigurationContext(newContext); + } catch (Exception e) { + LOG.log(Level.INFO, "Failed to update the current ConfigurationContext due to config model changes", e); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/internal/LoggingConfigListener.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/internal/LoggingConfigListener.java b/events/src/main/java/org/apache/tamaya/events/internal/LoggingConfigListener.java new file mode 100644 index 0000000..be8c404 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/internal/LoggingConfigListener.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.tamaya.events.internal; + +import org.apache.tamaya.Configuration; +import org.apache.tamaya.events.ConfigEvent; +import org.apache.tamaya.events.ConfigEventListener; + +import java.util.logging.Logger; + +/** + * Simple ConfigListener that simply logs any detected config changes to INFO level. + */ +public class LoggingConfigListener implements ConfigEventListener { + + private static final Logger LOG = Logger.getLogger(LoggingConfigListener.class.getName()); + + @Override + public void onConfigEvent(ConfigEvent<?> event) { + if(event.getResourceType()== Configuration.class) { + LOG.info("Configuration changed: " + event); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/internal/package-info.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/internal/package-info.java b/events/src/main/java/org/apache/tamaya/events/internal/package-info.java new file mode 100644 index 0000000..9df5ac3 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/internal/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +/** + * This package contains internal default implementations for the config events module. + */ +package org.apache.tamaya.events.internal; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/package-info.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/package-info.java b/events/src/main/java/org/apache/tamaya/events/package-info.java new file mode 100644 index 0000000..e175ceb --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +/** + * This package provides the main building blocks for handling configuration changes, such as + * {@link org.apache.tamaya.events.ConfigEventManager}, {@link org.apache.tamaya.events.ConfigEventListener} and + * artifacts to describe the changes (delta) of a Configuration or a PropertySource. + */ +package org.apache.tamaya.events; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/spi/BaseConfigEvent.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/spi/BaseConfigEvent.java b/events/src/main/java/org/apache/tamaya/events/spi/BaseConfigEvent.java new file mode 100644 index 0000000..f6856d9 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/spi/BaseConfigEvent.java @@ -0,0 +1,69 @@ +/* + * 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.tamaya.events.spi; + +import org.apache.tamaya.events.ConfigEvent; + +import java.util.Objects; +import java.util.UUID; + +/** + * Abstract base class for implementing your own configuration events. + * @param <T> the vent type + */ +public abstract class BaseConfigEvent<T> implements ConfigEvent<T> { + protected long timestamp = System.currentTimeMillis(); + protected String version = UUID.randomUUID().toString(); + protected final T paylod; + private final Class<T> type; + + public BaseConfigEvent(T paylod, Class<T> type){ + this.paylod = Objects.requireNonNull(paylod); + this.type = Objects.requireNonNull(type); + } + + @Override + public Class<T> getResourceType() { + return type; + } + + @Override + public T getResource() { + return paylod; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '{' + + "timestamp=" + timestamp + + ", version='" + version + '\'' + + ", paylod='" + paylod + '\'' + + '}'; + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/spi/ConfigEventManagerSpi.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/spi/ConfigEventManagerSpi.java b/events/src/main/java/org/apache/tamaya/events/spi/ConfigEventManagerSpi.java new file mode 100644 index 0000000..66a8f73 --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/spi/ConfigEventManagerSpi.java @@ -0,0 +1,128 @@ +/* + * 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.tamaya.events.spi; + +import org.apache.tamaya.events.ConfigEvent; +import org.apache.tamaya.events.ConfigEventListener; + +import java.util.Collection; + +/** + * SPI interface to implement the {@link org.apache.tamaya.events.ConfigEventManager} singleton. + * Implementations of this interface must be registered with the current {@link org.apache.tamaya.spi.ServiceContext}, + * by default this equals to registering it with {@link java.util.ServiceLoader}. Add {@link javax.annotation.Priority} + * annotations for overriding (higher values override lower values). + */ +public interface ConfigEventManagerSpi { + /** + * Adds a listener for observing events. References of this + * component to the listeners must be managed as weak references. + * + * @param <T> the type of the events listened to. + * @param l the listener not null. + */ + <T> void addListener(ConfigEventListener l); + + /** + * Adds a listener for observing events of a given type. + * + * @param <T> the type of the events listened to. + * @param l the listener not null. + * @param eventType the type of concrete configuration event this listeners should be informed about. All other + * event types will never be delivered to this listener instance. + */ + <T extends ConfigEvent> void addListener(ConfigEventListener l, Class<T> eventType); + + /** + * Removes a listener for observing events. + * + * @param l the listener not null. + */ + void removeListener(ConfigEventListener l); + + /** + * Removes a listener for observing events of a certain type. + * + * @param <T> the type of the events listened to. + * @param l the listener not null. + * @param eventType the type of concrete configuration event this listeners should be informed about. All other + * event types will never be delivered toe this listener instance. + */ + <T extends ConfigEvent> void removeListener(ConfigEventListener l, Class<T> eventType); + + /** + * Access all globally registered listeners. + * + * @return the listeners found, never null. + */ + Collection<? extends ConfigEventListener> getListeners(); + + /** + * Access all listeners listening for a certain event type, including any global listeners. + * @param eventType the type of concrete configuration event this listeners should be informed about. All other + * event types will never be delivered toe this listener instance. + * @return the listeners found, never null. + */ + Collection<? extends ConfigEventListener> getListeners(Class<? extends ConfigEvent> eventType); + + /** + * Publishes an event to all interested listeners, hereby executing all registered listeners sequentually and + * synchronously., + * + * @param event the event, not null. + */ + void fireEvent(ConfigEvent<?> event); + + /** + * Publishes an event to all interested listeners, hereby publishing the change events asynchrously and in + * parallel (multithreaded). + * + * @param event the event, not null. + */ + void fireEventAsynch(ConfigEvent<?> event); + + /** + * Get the current check period to check for configuration changes. + * + * @return the check period in ms. + */ + long getChangeMonitoringPeriod(); + + void setChangeMonitoringPeriod(long millis); + + /** + * Check if the observer is running currently. + * + * @return true, if the change monitoring service is currently running. + */ + boolean isChangeMonitorActive(); + + /** + * Start/stop the change monitoring service, which will observe/reevaluate the current configuration regularly + * and trigger ConfigurationChange events if something is changed. This is quite handy for publishing + * configuration changes to whatever systems are interested in. Hereby the origin of a configuration change + * can be on this machine, or also remotedly. For handling corresponding {@link ConfigEventListener} have + * to be registered, e.g. listening on {@link org.apache.tamaya.events.ConfigurationChange} events. + * + * @param enable whether to enable or disable the change monitoring. + */ + void enableChangeMonitor(boolean enable); + + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/java/org/apache/tamaya/events/spi/package-info.java ---------------------------------------------------------------------- diff --git a/events/src/main/java/org/apache/tamaya/events/spi/package-info.java b/events/src/main/java/org/apache/tamaya/events/spi/package-info.java new file mode 100644 index 0000000..63d2b3b --- /dev/null +++ b/events/src/main/java/org/apache/tamaya/events/spi/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +/** + * This package contains the SPI to implement the + * {@link org.apache.tamaya.events.ConfigEventManager} singleton. + */ +package org.apache.tamaya.events.spi; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/resources/META-INF/services/org.apache.tamaya.events.ConfigEventListener ---------------------------------------------------------------------- diff --git a/events/src/main/resources/META-INF/services/org.apache.tamaya.events.ConfigEventListener b/events/src/main/resources/META-INF/services/org.apache.tamaya.events.ConfigEventListener new file mode 100644 index 0000000..f9942c1 --- /dev/null +++ b/events/src/main/resources/META-INF/services/org.apache.tamaya.events.ConfigEventListener @@ -0,0 +1,19 @@ +# +# 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. +# +org.apache.tamaya.events.internal.DefaultConfigurationContextChangeListener http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/main/resources/META-INF/services/org.apache.tamaya.events.spi.ConfigEventManagerSpi ---------------------------------------------------------------------- diff --git a/events/src/main/resources/META-INF/services/org.apache.tamaya.events.spi.ConfigEventManagerSpi b/events/src/main/resources/META-INF/services/org.apache.tamaya.events.spi.ConfigEventManagerSpi new file mode 100644 index 0000000..d45dc43 --- /dev/null +++ b/events/src/main/resources/META-INF/services/org.apache.tamaya.events.spi.ConfigEventManagerSpi @@ -0,0 +1,19 @@ +# +# 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. +# +org.apache.tamaya.events.internal.DefaultConfigEventManagerSpi \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/ChangeableGlobalPropertySource.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/ChangeableGlobalPropertySource.java b/events/src/test/java/org/apache/tamaya/events/ChangeableGlobalPropertySource.java new file mode 100644 index 0000000..0384064 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/ChangeableGlobalPropertySource.java @@ -0,0 +1,62 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.core.propertysource.BasePropertySource; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * PropertySource implementation that accesses properties that are statically stored. + */ +public class ChangeableGlobalPropertySource extends BasePropertySource{ + + private static final Map<String,String> STORED_ENTRIES = new ConcurrentHashMap<>(); + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public Map<String, String> getProperties() { + return null; + } + + /** + * Put a value (globally) into this property source. + * @param key the key, not null + * @param value the value, not null + * @return the entry replaced, or null. + */ + public static String put(String key, String value){ + return STORED_ENTRIES.put(key,value); + } + + /** + * Put all the properties, overriding any existing ones with the same key. + * @param properties the properties, not null. + */ + public static void putAll(Map<String,String> properties){ + STORED_ENTRIES.putAll(properties); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/ChangeableThreadLocalPropertySource.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/ChangeableThreadLocalPropertySource.java b/events/src/test/java/org/apache/tamaya/events/ChangeableThreadLocalPropertySource.java new file mode 100644 index 0000000..cc6c812 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/ChangeableThreadLocalPropertySource.java @@ -0,0 +1,57 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.core.propertysource.BasePropertySource; + +import java.util.HashMap; +import java.util.Map; + + +/** + * PropertySource implementation that accesses properties that are stored on ThreadLocal level, e.g. good to use for + * testing.. + */ +public class ChangeableThreadLocalPropertySource extends BasePropertySource{ + + private static final ThreadLocal<Map<String,String>> STORED_ENTRIES = new ThreadLocal<Map<String,String>>(){ + protected Map<String,String> initialValue(){ + return new HashMap<>(); + } + }; + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public Map<String, String> getProperties() { + return null; + } + + public static String put(String key, String value){ + return STORED_ENTRIES.get().put(key,value); + } + + public static void putAll(Map<String,String> properties){ + STORED_ENTRIES.get().putAll(properties); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/ConfigEventManagerTest.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/ConfigEventManagerTest.java b/events/src/test/java/org/apache/tamaya/events/ConfigEventManagerTest.java new file mode 100644 index 0000000..b56407c --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/ConfigEventManagerTest.java @@ -0,0 +1,66 @@ +/* + * 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.tamaya.events; + +import org.junit.Test; + +import java.util.UUID; + +import static org.junit.Assert.*; + +/** + * Tests for {@link ConfigEventManager}. + */ +public class ConfigEventManagerTest { + + private Object testAddListenerValue; + + @Test + public void testAddRemoveListener() throws Exception { + ConfigEventListener testListener = new ConfigEventListener() { + @Override + public void onConfigEvent(ConfigEvent<?> event) { + testAddListenerValue = event.getResource(); + } + }; + ConfigEventManager.addListener(testListener); + ConfigEventManager.fireEvent(new SimpleEvent("Event1")); + assertEquals(testAddListenerValue, "Event1"); + ConfigEventManager.removeListener(testListener); + ConfigEventManager.fireEvent(new SimpleEvent("Event2")); + assertEquals(testAddListenerValue, "Event1"); + } + + @Test + public void testFireEvent() throws Exception { + ConfigEventListener testListener = new ConfigEventListener() { + @Override + public void onConfigEvent(ConfigEvent<?> event) { + testAddListenerValue = event.getResource(); + } + }; + ConfigEventManager.addListener(testListener); + ConfigEventManager.fireEvent(new SimpleEvent("Event1")); + assertEquals(testAddListenerValue, "Event1"); + ConfigEventManager.removeListener(testListener); + ConfigEventManager.fireEvent(new SimpleEvent("Event2")); + assertEquals(testAddListenerValue, "Event1"); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/FrozenPropertySourceTest.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/FrozenPropertySourceTest.java b/events/src/test/java/org/apache/tamaya/events/FrozenPropertySourceTest.java new file mode 100644 index 0000000..1431228 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/FrozenPropertySourceTest.java @@ -0,0 +1,106 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.core.propertysource.SystemPropertySource; +import org.apache.tamaya.spi.PropertySource; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.*; + +/** + * Tests for {@link org.apache.tamaya.events.FrozenPropertySource}. + */ +public class FrozenPropertySourceTest { + + private static final PropertySource myPS = new SystemPropertySource(); + + @Test + public void testOf() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + assertNotNull(ps); + } + + @Test + public void testGetName() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + String name = ps.getName(); + assertNotNull(name); + assertEquals(name, ps.getName()); + } + + @Test + public void testGetOrdinal() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + assertEquals(myPS.getOrdinal(), ps.getOrdinal()); + } + + @Test + public void testGet() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + assertNotNull(ps); + for (Map.Entry<String, String> e : myPS.getProperties().entrySet()) { + assertEquals(ps.get(e.getKey()).getValue(), e.getValue()); + } + } + + @Test + public void testGetProperties() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + assertNotNull(ps); + assertNotNull(ps.getProperties()); + assertFalse(ps.getProperties().isEmpty()); + } + + @Test + public void testEquals() throws Exception { + PropertySource ps1 = FrozenPropertySource.of(myPS); + PropertySource ps2 = FrozenPropertySource.of(myPS); + assertEquals(ps1.getName(), ps2.getName()); + assertEquals(ps1.getProperties().size(), ps2.getProperties().size()); + } + + @Test + public void testHashCode() throws Exception { + boolean alwaysDifferent = true; + for(int i=0;i<10;i++){ + PropertySource ps1 = FrozenPropertySource.of(myPS); + PropertySource ps2 = FrozenPropertySource.of(myPS); + // sometimes not same, because frozenAt in ms maybe different + if(ps1.hashCode()==ps2.hashCode()){ + alwaysDifferent=false; + break; + } + } + if(alwaysDifferent){ + fail("HashCode should be same if frozenAt is in the same ms..."); + } + } + + @Test + public void testToString() throws Exception { + PropertySource ps = FrozenPropertySource.of(myPS); + String toString = ps.toString(); + assertNotNull(toString); + assertTrue(toString.contains("FrozenPropertySource")); + assertTrue(toString.contains(myPS.getName())); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/ObservedConfigTest.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/ObservedConfigTest.java b/events/src/test/java/org/apache/tamaya/events/ObservedConfigTest.java new file mode 100644 index 0000000..0cd9e2b --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/ObservedConfigTest.java @@ -0,0 +1,69 @@ +/* + * 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.tamaya.events; + +import org.apache.commons.io.FileUtils; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertEquals; + +/** + * Test (currently manual) to test configuration changes. + */ +public class ObservedConfigTest { + + @Test + @Ignore // reactivate later... + public void testChangingConfig() throws IOException { + Configuration config = ConfigurationProvider.getConfiguration().with(TestConfigView.of()); + + Map<String, String> props = config.getProperties(); + assertEquals(props.get("test"), "test2"); + assertEquals(props.get("testValue1"), "value"); + assertNull(props.get("testValue2")); + + //insert a new properties file into the tempdirectory + FileUtils.writeStringToFile( + new File(TestObservingProvider.propertyLocation.toFile(), "test2.properties"), + "testValue2=anotherValue"); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + config = ConfigurationProvider.getConfiguration().with(TestConfigView.of()); + + props = config.getProperties(); + + assertEquals(props.get("test"), "test2"); + assertEquals(props.get("testValue1"), "value"); + assertEquals(props.get("testValue2"), "anotherValue"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/RandomPropertySource.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/RandomPropertySource.java b/events/src/test/java/org/apache/tamaya/events/RandomPropertySource.java new file mode 100644 index 0000000..dead0d9 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/RandomPropertySource.java @@ -0,0 +1,66 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.spi.PropertySource; +import org.apache.tamaya.spi.PropertyValue; + +import java.util.HashMap; +import java.util.Map; + +/** + * PropertySource that provides a randome entry, different on each access! + */ +public class RandomPropertySource implements PropertySource{ + + private Map<String, String> data = new HashMap<>(); + + @Override + public int getOrdinal() { + return 0; + } + + @Override + public String getName() { + return "random"; + } + + @Override + public PropertyValue get(String key) { + if(key.equals("random.new")){ + return PropertyValue.of(key, String.valueOf(Math.random()),getName()); + } + return null; + } + + @Override + public Map<String, String> getProperties() { + synchronized(data) { + data.put("random.new", String.valueOf(Math.random())); + data.put("_random.new.source", getName()); + data.put("_random.new.timestamp", String.valueOf(System.currentTimeMillis())); + return new HashMap<>(data); + } + } + + @Override + public boolean isScannable() { + return true; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/SimpleEvent.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/SimpleEvent.java b/events/src/test/java/org/apache/tamaya/events/SimpleEvent.java new file mode 100644 index 0000000..5017aa1 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/SimpleEvent.java @@ -0,0 +1,32 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.events.spi.BaseConfigEvent; + +/** + * Simple test event for testing only. + */ +public class SimpleEvent extends BaseConfigEvent<String> { + + public SimpleEvent(String paylod) { + super(paylod, String.class); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/TestConfigView.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/TestConfigView.java b/events/src/test/java/org/apache/tamaya/events/TestConfigView.java new file mode 100644 index 0000000..8e5b397 --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/TestConfigView.java @@ -0,0 +1,156 @@ +/* + * 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.tamaya.events; + +import org.apache.tamaya.ConfigException; +import org.apache.tamaya.ConfigOperator; +import org.apache.tamaya.ConfigQuery; +import org.apache.tamaya.Configuration; +import org.apache.tamaya.ConfigurationProvider; +import org.apache.tamaya.TypeLiteral; +import org.apache.tamaya.spi.ConfigurationContext; +import org.apache.tamaya.spi.ConversionContext; +import org.apache.tamaya.spi.PropertyConverter; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Created by Anatole on 24.03.2015. + */ +public class TestConfigView implements ConfigOperator{ + + private static final TestConfigView INSTANCE = new TestConfigView(); + + private TestConfigView(){} + + public static ConfigOperator of(){ + return INSTANCE; + } + + @Override + public Configuration operate(final Configuration config) { + return new Configuration() { + @Override + public Map<String, String> getProperties() { + Map<String, String> result = new HashMap<>(); + for (Map.Entry<String, String> en : config.getProperties().entrySet()) { + if (en.getKey().startsWith("test")) { + result.put(en.getKey(), en.getValue()); + } + } + return result; +// return config.getProperties().entrySet().stream().filter(e -> e.getKey().startsWith("test")).collect( +// Collectors.toMap(en -> en.getKey(), en -> en.getValue())); + } + + @Override + public Configuration with(ConfigOperator operator) { + return null; + } + + @Override + public <T> T query(ConfigQuery<T> query) { + return null; + } + + @Override + public ConfigurationContext getContext() { + return config.getContext(); + } + + @Override + public String get(String key) { + return getProperties().get(key); + } + + @Override + public String getOrDefault(String key, String defaultValue) { + String val = get(key); + if(val==null){ + return defaultValue; + } + return val; + } + + @Override + public <T> T getOrDefault(String key, Class<T> type, T defaultValue) { + T val = get(key, type); + if(val==null){ + return defaultValue; + } + return val; + } + + @Override + public <T> T get(String key, Class<T> type) { + return (T) get(key, TypeLiteral.of(type)); + } + + /** + * Accesses the current String value for the given key and tries to convert it + * using the {@link org.apache.tamaya.spi.PropertyConverter} instances provided by the current + * {@link org.apache.tamaya.spi.ConfigurationContext}. + * + * @param key the property's absolute, or relative path, e.g. @code + * a/b/c/d.myProperty}. + * @param type The target type required, not null. + * @param <T> the value type + * @return the converted value, never null. + */ + @Override + public <T> T get(String key, TypeLiteral<T> type) { + String value = get(key); + if (value != null) { + List<PropertyConverter<T>> converters = ConfigurationProvider.getConfigurationContext() + .getPropertyConverters(type); + ConversionContext context = new ConversionContext.Builder( + key,type).build(); + for (PropertyConverter<T> converter : converters) { + try { + T t = converter.convert(value, context); + if (t != null) { + return t; + } + } catch (Exception e) { + Logger.getLogger(getClass().getName()) + .log(Level.FINEST, "PropertyConverter: " + converter + " failed to convert value: " + + value, e); + } + } + throw new ConfigException("Unparseable config value for type: " + type.getRawType().getName() + ": " + + key + ", supportedFormats: " + context.getSupportedFormats()); + } + return null; + } + + @Override + public <T> T getOrDefault(String key, TypeLiteral<T> type, T defaultValue) { + T val = get(key, type); + if(val==null){ + return defaultValue; + } + return val; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-tamaya-extensions/blob/89223dcd/events/src/test/java/org/apache/tamaya/events/TestObservingProvider.java ---------------------------------------------------------------------- diff --git a/events/src/test/java/org/apache/tamaya/events/TestObservingProvider.java b/events/src/test/java/org/apache/tamaya/events/TestObservingProvider.java new file mode 100644 index 0000000..2685d3e --- /dev/null +++ b/events/src/test/java/org/apache/tamaya/events/TestObservingProvider.java @@ -0,0 +1,92 @@ +/* + * 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.tamaya.events; + +import org.apache.commons.io.FileUtils; +import org.apache.tamaya.events.folderobserver.ObservingPropertySourceProvider; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Test configuration property source provider that observes a directory and updated the config if necessary. + */ +public class TestObservingProvider extends ObservingPropertySourceProvider{ + + public static Path propertyLocation; + + static{ + try { + // create some temporary config + Path tempDir = Files.createTempDirectory("observedFolder"); + + TestObservingProvider.propertyLocation = tempDir; + + FileUtils.copyInputStreamToFile( + TestObservingProvider.class.getResourceAsStream("/test.properties"), + new File(tempDir.toFile(), "test.properties")); + + Runtime.getRuntime().addShutdownHook(new Thread(){ + @Override + public void run(){ + try{ + // cleanup directory + Files.deleteIfExists(getTargetFile("test1.properties")); + Files.deleteIfExists(getTargetFile("test2.properties")); + Files.deleteIfExists(getTargetFile("test3.properties")); + } + catch(Exception e){ + Logger.getLogger("TestObservingProvider").log(Level.WARNING, + "Failed to cleanup config test dir", e); + } + } + }); + } + catch(Exception e){ + Logger.getLogger("TestObservingProvider").log(Level.WARNING, "Failed to init config test dir", e); + } + } + + private static Path getTargetFile(String name) { + File testFile = new File(TestObservingProvider.getTestDirectory(), name); + return Paths.get(testFile.toURI()); + } + + public TestObservingProvider(){ + super(propertyLocation); + Logger.getLogger(getClass().getName()).info("Using test directory: " + getTestPath()); + } + + public static File getTestDirectory(){ + String tempDir = System.getProperty("java.io.tmpdir"); + File dir = new File(tempDir, "tamaya-events-testdir"); + if(!dir.exists()){ + dir.mkdirs(); + } + return dir; + } + + private static String getTestPath(){ + return getTestDirectory().getAbsolutePath(); + } +}