2 * Reactor - ChainWatcher.java - Copyright © 2013 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.reactor.loader;
21 import java.io.FilenameFilter;
22 import java.util.HashMap;
24 import java.util.Map.Entry;
25 import java.util.concurrent.TimeUnit;
27 import javax.xml.bind.JAXBContext;
28 import javax.xml.bind.JAXBException;
29 import javax.xml.bind.Unmarshaller;
31 import net.pterodactylus.reactor.Reaction;
32 import net.pterodactylus.reactor.engine.Engine;
33 import net.pterodactylus.reactor.loader.Chain.Parameter;
34 import net.pterodactylus.reactor.loader.Chain.Part;
36 import org.apache.log4j.Logger;
38 import com.google.common.base.Predicate;
39 import com.google.common.collect.Maps;
40 import com.google.common.util.concurrent.AbstractExecutionThreadService;
41 import com.google.common.util.concurrent.Uninterruptibles;
44 * Watches a directory for chain XML files and loads and unloads
45 * {@link Reaction}s from the {@link Engine}.
47 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
49 public class ChainWatcher extends AbstractExecutionThreadService {
52 private static final Logger logger = Logger.getLogger(ChainWatcher.class);
54 /** The reaction loader. */
55 private final ReactionLoader reactionLoader = new ReactionLoader();
57 /** The engine to load reactions with. */
58 private final Engine engine;
60 /** The directory to watch for chain XML files. */
61 private final String directory;
64 * Creates a new chain watcher.
67 * The engine to load reactions with
69 * The directory to watch
71 public ChainWatcher(Engine engine, String directory) {
73 this.directory = directory;
77 // ABSTRACTEXECUTIONTHREADSERVICE METHODS
84 protected void run() throws Exception {
87 final Map<String, Chain> loadedChains = new HashMap<String, Chain>();
91 /* check if directory is there. */
92 File directoryFile = new File(directory);
93 if (!directoryFile.exists() || !directoryFile.isDirectory() || !directoryFile.canRead()) {
94 Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
98 /* list all files, scan for XMLs. */
99 logger.debug(String.format("Scanning %s...", directory));
100 File[] xmlFiles = directoryFile.listFiles(new FilenameFilter() {
103 public boolean accept(File dir, String name) {
104 return name.endsWith(".xml");
107 logger.debug(String.format("Found %d XML file(s), parsing...", xmlFiles.length));
109 /* now parse all XML files. */
110 Map<String, Chain> chains = new HashMap<String, Chain>();
111 for (File xmlFile : xmlFiles) {
113 /* parse XML file. */
114 Chain chain = parseXmlFile(xmlFile);
117 logger.debug(String.format(" Enabled: %s", chain.enabled()));
119 logger.debug(String.format(" Query: %s", chain.query().name()));
120 for (Parameter parameter : chain.query().parameters()) {
121 logger.debug(String.format(" Parameter: %s=%s", parameter.name(), parameter.value()));
123 for (Part filter : chain.filters()) {
124 logger.debug(String.format(" Filter: %s", filter.name()));
125 for (Parameter parameter : filter.parameters()) {
126 logger.debug(String.format(" Parameter: %s=%s", parameter.name(), parameter.value()));
129 logger.debug(String.format(" Trigger: %s", chain.trigger().name()));
130 for (Parameter parameter : chain.trigger().parameters()) {
131 logger.debug(String.format(" Parameter: %s=%s", parameter.name(), parameter.value()));
133 logger.debug(String.format(" Action: %s", chain.action().name()));
134 for (Parameter parameter : chain.action().parameters()) {
135 logger.debug(String.format(" Parameter: %s=%s", parameter.name(), parameter.value()));
138 chains.put(xmlFile.getName(), chain);
141 /* filter enabled chains. */
142 Map<String, Chain> enabledChains = Maps.filterEntries(chains, new Predicate<Entry<String, Chain>>() {
145 public boolean apply(Entry<String, Chain> chainEntry) {
146 return chainEntry.getValue().enabled();
149 logger.debug(String.format("Found %d enabled Chain(s).", enabledChains.size()));
151 /* check for removed chains. */
152 for (Entry<String, Chain> loadedChain : loadedChains.entrySet()) {
154 /* skip chains that still exist. */
155 if (enabledChains.containsKey(loadedChain.getKey())) {
159 logger.info(String.format("Removing Chain: %s", loadedChain.getKey()));
160 engine.removeReaction(loadedChain.getKey());
161 loadedChains.remove(loadedChain.getKey());
164 /* check for new chains. */
165 for (Entry<String, Chain> enabledChain : enabledChains.entrySet()) {
167 /* skip already loaded chains. */
168 if (loadedChains.containsValue(enabledChain.getValue())) {
172 logger.info(String.format("Loading new Chain: %s", enabledChain.getKey()));
174 Reaction reaction = reactionLoader.loadReaction(enabledChain.getValue());
175 engine.addReaction(enabledChain.getKey(), reaction);
176 loadedChains.put(enabledChain.getKey(), enabledChain.getValue());
179 /* wait before checking again. */
180 Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS);
189 * Parses the given XML file into a {@link Chain}.
192 * The XML file to parse
193 * @return The parsed chain
195 private static Chain parseXmlFile(File xmlFile) {
197 JAXBContext context = JAXBContext.newInstance(Chain.class);
198 Unmarshaller unmarshaller = context.createUnmarshaller();
199 logger.debug(String.format("Reading %s...", xmlFile.getPath()));
200 return (Chain) unmarshaller.unmarshal(xmlFile);
201 } catch (JAXBException e) {