Use JSON for configuration files, remove all references to XML.
[rhynodge.git] / src / main / java / net / pterodactylus / reactor / loader / ChainWatcher.java
1 /*
2  * Reactor - ChainWatcher.java - Copyright © 2013 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.reactor.loader;
19
20 import java.io.File;
21 import java.io.FilenameFilter;
22 import java.io.IOException;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.concurrent.TimeUnit;
27
28 import net.pterodactylus.reactor.Reaction;
29 import net.pterodactylus.reactor.engine.Engine;
30 import net.pterodactylus.reactor.loader.Chain.Parameter;
31 import net.pterodactylus.reactor.loader.Chain.Part;
32
33 import org.apache.log4j.Logger;
34
35 import com.fasterxml.jackson.core.JsonParseException;
36 import com.fasterxml.jackson.databind.JsonMappingException;
37 import com.fasterxml.jackson.databind.ObjectMapper;
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;
42
43 /**
44  * Watches a directory for chain configuration files and loads and unloads
45  * {@link Reaction}s from the {@link Engine}.
46  *
47  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48  */
49 public class ChainWatcher extends AbstractExecutionThreadService {
50
51         /** The logger. */
52         private static final Logger logger = Logger.getLogger(ChainWatcher.class);
53
54         /** The JSON object mapper. */
55         private static final ObjectMapper objectMapper = new ObjectMapper();
56
57         /** The reaction loader. */
58         private final ReactionLoader reactionLoader = new ReactionLoader();
59
60         /** The engine to load reactions with. */
61         private final Engine engine;
62
63         /** The directory to watch for chain configuration files. */
64         private final String directory;
65
66         /**
67          * Creates a new chain watcher.
68          *
69          * @param engine
70          *            The engine to load reactions with
71          * @param directory
72          *            The directory to watch
73          */
74         public ChainWatcher(Engine engine, String directory) {
75                 this.engine = engine;
76                 this.directory = directory;
77         }
78
79         //
80         // ABSTRACTEXECUTIONTHREADSERVICE METHODS
81         //
82
83         /**
84          * {@inheritDoc}
85          */
86         @Override
87         protected void run() throws Exception {
88
89                 /* loaded chains. */
90                 final Map<String, Chain> loadedChains = new HashMap<String, Chain>();
91
92                 while (isRunning()) {
93
94                         /* check if directory is there. */
95                         File directoryFile = new File(directory);
96                         if (!directoryFile.exists() || !directoryFile.isDirectory() || !directoryFile.canRead()) {
97                                 Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
98                                 continue;
99                         }
100
101                         /* list all files, scan for configuration files. */
102                         logger.debug(String.format("Scanning %s...", directory));
103                         File[] configurationFiles = directoryFile.listFiles(new FilenameFilter() {
104
105                                 @Override
106                                 public boolean accept(File dir, String name) {
107                                         return name.endsWith(".json");
108                                 }
109                         });
110                         logger.debug(String.format("Found %d configuration file(s), parsing...", configurationFiles.length));
111
112                         /* now parse all XML files. */
113                         Map<String, Chain> chains = new HashMap<String, Chain>();
114                         for (File configurationFile : configurationFiles) {
115
116                                 /* parse XML file. */
117                                 Chain chain = parseConfigurationFile(configurationFile);
118                                 if (chain == null) {
119                                         logger.warn(String.format("Could not parse %s.", configurationFile));
120                                         continue;
121                                 }
122
123                                 /* dump chain */
124                                 logger.debug(String.format(" Enabled: %s", chain.enabled()));
125
126                                 logger.debug(String.format(" Query: %s", chain.query().name()));
127                                 for (Parameter parameter : chain.query().parameters()) {
128                                         logger.debug(String.format("  Parameter: %s=%s", parameter.name(), parameter.value()));
129                                 }
130                                 for (Part filter : chain.filters()) {
131                                         logger.debug(String.format(" Filter: %s", filter.name()));
132                                         for (Parameter parameter : filter.parameters()) {
133                                                 logger.debug(String.format("  Parameter: %s=%s", parameter.name(), parameter.value()));
134                                         }
135                                 }
136                                 logger.debug(String.format(" Trigger: %s", chain.trigger().name()));
137                                 for (Parameter parameter : chain.trigger().parameters()) {
138                                         logger.debug(String.format("  Parameter: %s=%s", parameter.name(), parameter.value()));
139                                 }
140                                 logger.debug(String.format(" Action: %s", chain.action().name()));
141                                 for (Parameter parameter : chain.action().parameters()) {
142                                         logger.debug(String.format("  Parameter: %s=%s", parameter.name(), parameter.value()));
143                                 }
144
145                                 chains.put(configurationFile.getName(), chain);
146                         }
147
148                         /* filter enabled chains. */
149                         Map<String, Chain> enabledChains = Maps.filterEntries(chains, new Predicate<Entry<String, Chain>>() {
150
151                                 @Override
152                                 public boolean apply(Entry<String, Chain> chainEntry) {
153                                         return chainEntry.getValue().enabled();
154                                 }
155                         });
156                         logger.debug(String.format("Found %d enabled Chain(s).", enabledChains.size()));
157
158                         /* check for removed chains. */
159                         for (Entry<String, Chain> loadedChain : loadedChains.entrySet()) {
160
161                                 /* skip chains that still exist. */
162                                 if (enabledChains.containsKey(loadedChain.getKey())) {
163                                         continue;
164                                 }
165
166                                 logger.info(String.format("Removing Chain: %s", loadedChain.getKey()));
167                                 engine.removeReaction(loadedChain.getKey());
168                                 loadedChains.remove(loadedChain.getKey());
169                         }
170
171                         /* check for new chains. */
172                         for (Entry<String, Chain> enabledChain : enabledChains.entrySet()) {
173
174                                 /* skip already loaded chains. */
175                                 if (loadedChains.containsValue(enabledChain.getValue())) {
176                                         continue;
177                                 }
178
179                                 logger.info(String.format("Loading new Chain: %s", enabledChain.getKey()));
180
181                                 Reaction reaction = reactionLoader.loadReaction(enabledChain.getValue());
182                                 engine.addReaction(enabledChain.getKey(), reaction);
183                                 loadedChains.put(enabledChain.getKey(), enabledChain.getValue());
184                         }
185
186                         /* wait before checking again. */
187                         Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS);
188                 }
189         }
190
191         //
192         // STATIC METHODS
193         //
194
195         /**
196          * Parses the given configuration file into a {@link Chain}.
197          *
198          * @param configurationFile
199          *            The configuration file to parse
200          * @return The parsed chain
201          */
202         private static Chain parseConfigurationFile(File configurationFile) {
203                 try {
204                         return objectMapper.readValue(configurationFile, Chain.class);
205                 } catch (JsonParseException jpe1) {
206                         logger.warn(String.format("Could not parse %s.", configurationFile), jpe1);
207                 } catch (JsonMappingException jme1) {
208                         logger.warn(String.format("Could not parse %s.", configurationFile), jme1);
209                 } catch (IOException ioe1) {
210                         logger.info(String.format("Could not read %s.", configurationFile));
211                 }
212                 return null;
213         }
214
215 }