package org.atomnuke.util.config.update; import org.atomnuke.util.config.io.ConfigurationManager; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.atomnuke.plugin.context.LocalInstanceContext; import org.atomnuke.plugin.operation.ComplexOperation; import org.atomnuke.plugin.operation.OperationFailureException; import org.atomnuke.util.config.ConfigurationException; import org.atomnuke.util.config.io.UpdateTag; import org.atomnuke.util.config.update.listener.ConfigurationListener; import org.atomnuke.util.config.update.listener.ListenerContext; import org.atomnuke.util.remote.AtomicCancellationRemote; import org.atomnuke.util.remote.CancellationRemote; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author zinic */ public class UpdateContext<T> implements ConfigurationContext<T> { private static final Logger LOG = LoggerFactory.getLogger(UpdateContext.class); private final ComplexOperation<ConfigurationListener<T>, T> UPDATED_OPERATION = new ComplexOperation<ConfigurationListener<T>, T>() { @Override public void perform(ConfigurationListener<T> instance, T argument) throws OperationFailureException { try { instance.updated(argument); } catch (ConfigurationException ce) { throw new OperationFailureException(ce); } } }; private final List<ListenerContext<T>> ListenerContexts; private final CancellationRemote cancellationRemote; private final ConfigurationManager<T> manager; private UpdateTag latestUpdateTag; public UpdateContext(ConfigurationManager<T> manager, CancellationRemote cancellationRemote) { this.manager = manager; this.cancellationRemote = cancellationRemote; ListenerContexts = new LinkedList<ListenerContext<T>>(); } @Override public CancellationRemote cancellationRemote() { return cancellationRemote; } private synchronized void addListenerContext(ListenerContext<T> context) { ListenerContexts.add(context); } @Override public CancellationRemote addListener(ConfigurationListener<T> listener) throws ConfigurationException { final ListenerContext<T> newListenerContext = new ListenerContext<T>(new LocalInstanceContext<ConfigurationListener<T>>(listener), new AtomicCancellationRemote()); addListenerContext(newListenerContext); dispatch(); return newListenerContext.cancellationRemote(); } private synchronized List<ListenerContext<T>> getListeners() { final List<ListenerContext<T>> listeners = new LinkedList<ListenerContext<T>>(); for (Iterator<ListenerContext<T>> ListenerContextItr = ListenerContexts.iterator(); ListenerContextItr.hasNext();) { final ListenerContext<T> nextCtx = ListenerContextItr.next(); // Cancellation may happen at anytime, remotely - check for it on every poll if (nextCtx.cancellationRemote().canceled()) { ListenerContextItr.remove(); continue; } listeners.add(nextCtx); } return listeners; } public synchronized UpdateTag currentUpdateTag() throws ConfigurationException { final UpdateTag newUpdateTag = manager.readUpdateTag(); if (latestUpdateTag == null || !latestUpdateTag.equals(newUpdateTag)) { latestUpdateTag = newUpdateTag; } return latestUpdateTag; } public boolean updated() throws ConfigurationException { final UpdateTag previousUpdateTag = latestUpdateTag; return !previousUpdateTag.equals(currentUpdateTag()); } public void dispatch() throws ConfigurationException { final T configuration = manager.read(); // TODO: This might mask a deeper error - should use exception handling if (configuration != null) { final UpdateTag currentUpdateTag = currentUpdateTag(); for (ListenerContext<T> listenerContext : getListeners()) { if (listenerContext.lastUpdateTagSeen() == null || !listenerContext.lastUpdateTagSeen().equals(currentUpdateTag)) { try { listenerContext.configurationListener().perform(UPDATED_OPERATION, configuration); listenerContext.setLastUpdateTagSeen(currentUpdateTag); } catch (OperationFailureException ex) { LOG.error("Configuration exception during dispatch: " + ex.getMessage(), ex); } } } } } }