View Javadoc

1   /*
2    * Copyright 2013 University of Glasgow.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package broadwick;
17  
18  import broadwick.config.ConfigValidationErrors;
19  import broadwick.config.ConfigValidator;
20  import broadwick.config.generated.Logs;
21  import broadwick.config.generated.Models;
22  import broadwick.config.generated.Project;
23  import broadwick.data.DataReader;
24  import broadwick.data.Lookup;
25  import broadwick.model.Model;
26  import com.google.common.base.Throwables;
27  import com.google.common.util.concurrent.ThreadFactoryBuilder;
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.StringReader;
31  import java.io.StringWriter;
32  import java.util.HashMap;
33  import java.util.Map;
34  import java.util.Map.Entry;
35  import java.util.concurrent.ExecutorService;
36  import java.util.concurrent.Executors;
37  import java.util.concurrent.ThreadFactory;
38  import java.util.concurrent.TimeUnit;
39  import javax.xml.bind.JAXBContext;
40  import javax.xml.bind.JAXBException;
41  import javax.xml.bind.Unmarshaller;
42  import javax.xml.parsers.DocumentBuilder;
43  import javax.xml.parsers.DocumentBuilderFactory;
44  import javax.xml.parsers.ParserConfigurationException;
45  import javax.xml.transform.OutputKeys;
46  import javax.xml.transform.Transformer;
47  import javax.xml.transform.TransformerException;
48  import javax.xml.transform.TransformerFactory;
49  import javax.xml.transform.dom.DOMSource;
50  import javax.xml.transform.stream.StreamResult;
51  import org.apache.commons.lang3.time.StopWatch;
52  import org.slf4j.Logger;
53  import org.w3c.dom.Document;
54  import org.w3c.dom.NamedNodeMap;
55  import org.w3c.dom.NodeList;
56  import org.xml.sax.InputSource;
57  import org.xml.sax.SAXException;
58  
59  /**
60   * Broadwick: Project for Scientific Computing. The Broadwick framework allows for rapid epidemic modelling.
61   */
62  public final class Broadwick {
63  
64      /**
65       * Create the Broadwick project to read and verify the configuration files and initialise the project.
66       * <p>
67       * @param args the command line arguments supplied to the project.
68       */
69      public Broadwick(final String[] args) {
70  
71          final LoggingFacade logFacade = new LoggingFacade();
72          log = logFacade.getRootLogger();
73          try {
74              final CliOptions cli = new CliOptions(args);
75              readConfigFile(logFacade, cli.getConfigurationFileName());
76          } catch (BroadwickException ex) {
77              log.error("{}\nSomething went wrong starting project. See the error messages.", ex.getLocalizedMessage());
78              log.trace(Throwables.getStackTraceAsString(ex));
79          }
80      }
81  
82      /**
83       * Read the configuration file from the configuration file.
84       * <p>
85       * @param logFacade  the LoggingFacade object used to log any messages.
86       * @param configFile the name of the configuration file.
87       */
88      private void readConfigFile(final LoggingFacade logFacade, final String configFile) {
89          if (!configFile.isEmpty()) {
90              final File cfg = new File(configFile);
91              if (!cfg.exists()) {
92                  throw new BroadwickException("Configuration file [" + configFile + "] does not exist.");
93              }
94              try {
95                  // read the configuration file
96                  final JAXBContext jaxbContext = JAXBContext.newInstance(Constants.GENERATED_CONFIG_CLASSES_DIR);
97                  final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
98                  project = (Project) unmarshaller.unmarshal(cfg);
99  
100                 // save the config file as a string for later use
101                 final StringWriter writer = new StringWriter();
102                 jaxbContext.createMarshaller().marshal(project, writer);
103                 configXml = writer.toString();
104 
105                 // Validate the configuration file
106                 final ConfigValidator validator = new ConfigValidator(project);
107                 final ConfigValidationErrors validationErrors = validator.validate();
108 
109                 // now set up the logger as defined in the config file, we want to do this
110                 // BEFORE writing the results of validation
111                 final Logs.File file = project.getLogs().getFile();
112                 if (file != null) {
113                     logFacade.addFileLogger(file.getName(), file.getLevel(), file.getPattern(), file.isOverwrite());
114                 }
115                 final Logs.Console console = project.getLogs().getConsole();
116                 if (console != null) {
117                     logFacade.addConsoleLogger(console.getLevel(), console.getPattern());
118                 }
119 
120                 // Log any validation errors.
121                 if (validationErrors.getNumErrors() > 0) {
122                     log.error("Invalid configuration file.\n{}Correct any errors before continuing.", validationErrors.getValidationErrors());
123                     project = validator.getValidatedProject();
124                 }
125 
126             } catch (JAXBException ex) {
127                 log.error("Could not read configuration file. {}", ex.toString());
128                 log.trace(com.google.common.base.Throwables.getStackTraceAsString(ex));
129             }
130         } else {
131             throw new BroadwickException("No configuration file specified");
132         }
133     }
134 
135     /**
136      * Run the Broadwick framework.
137      */
138     @SuppressWarnings("squid:S1147")
139     public void run() {
140         if (project != null) {
141             final StopWatch sw = new StopWatch();
142             sw.start();
143 
144             // initialise the data, by reading the data files and/or the database.
145             log.info("Running broadwick {}", BroadwickVersion.getVersionAndTimeStamp());
146 
147             try (DataReader dr = new DataReader(project.getData())) {
148                 final Map<String, Model> registeredModels = registerModels(project, dr.getLookup());
149                 log.info("Running broadwick for the following models {}", registeredModels.keySet());
150 
151                 // Run the models, each on a separate thread.
152                 // TODO in a single-threaded grid environment we cannot do this - need to think again here....
153                 final int poolSize = registeredModels.size();
154                 if (poolSize > 0) {
155                     final ThreadFactory threadFactory = new ThreadFactoryBuilder()
156                             .setNameFormat("BroadwickModels-%d")
157                             .setDaemon(true)
158                             .build();
159                     final ExecutorService es = Executors.newFixedThreadPool(poolSize, threadFactory);
160 
161                     //final StopWatch sw = new StopWatch();
162                     for (final Entry<String, Model> entry : registeredModels.entrySet()) {
163                         es.submit(new Runnable() {
164                             @Override
165                             public void run() {
166                                 final String modelName = entry.getKey();
167                                 final Model model = entry.getValue();
168                                 try {
169                                     log.info("Running {} [{}]", modelName, model.getClass().getCanonicalName());
170                                     model.init();
171                                     model.run();
172                                     model.finalise();
173                                 } catch (Exception ex) {
174                                     log.error("Error running model {}. see stack trace from details.", modelName);
175                                     log.error("{}", Throwables.getStackTraceAsString(ex));
176                                 }
177                             }
178                         });
179                     }
180                     es.shutdown();
181                     while (!es.isTerminated()) {
182                         es.awaitTermination(10, TimeUnit.SECONDS);
183                     }
184                     //sw.stop();
185                     //log.trace("Finished {} simulations in {}.", maxSimulations, sw);
186                 }
187             } catch (Exception ex) {
188                 log.error("{}", ex.getLocalizedMessage());
189                 log.error("{}", Throwables.getStackTraceAsString(ex));
190                 log.error("Something went wrong. See previous messages for details.");
191             }
192 
193             log.info("Simulation complete. {}", sw.toString());
194             // In rare circumstances, where exceptions are caught and the simulation has completed but
195             // there are still tasks being submitted to the executor, we need to force the progam to quit.
196             Runtime.getRuntime().exit(0);
197         }
198     }
199 
200     /**
201      * Create and register the models internally. If there was a problem registering the models an empty cache is
202      * returned.
203      * <p>
204      * @param project the unmarshalled configuration file.
205      * @param lookup  the Lookuup object that allows the model to access the data specified in the data files.
206      * @return the registered models.
207      */
208     private Map<String, Model> registerModels(final Project project, final Lookup lookup) {
209         final Map<String, Model> registeredModels = new HashMap<>();
210         try {
211 
212             for (Models.Model model : project.getModels().getModel()) {
213                 // Create and register the new model object that we will be running later.
214                 final Model newInstance = Model.class.cast(Class.forName(model.getClassname()).newInstance());
215                 newInstance.setModelConfiguration(getModelsConfiguration(model.getId(), getAllModelConfigurations()));
216                 newInstance.setModelDataLookup(lookup);
217                 newInstance.setModelParameters(model.getParameter());
218                 if (model.getPriors() != null) {
219                     newInstance.setModelPriors(model.getPriors().getGaussianPriorAndUniformPrior());
220                 }
221                 registeredModels.put(model.getId(), newInstance);
222             }
223         } catch (ParserConfigurationException | SAXException | IOException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException ex) {
224             log.error("Could not create model ; {}", ex.getLocalizedMessage());
225             registeredModels.clear();
226         }
227 
228         return registeredModels;
229     }
230 
231     /**
232      * Get a collection of XML elements one for each <model> section.
233      * <p>
234      * @return a collection of XML elements of each <model>.
235      * @throws ParserConfigurationException if the nodes for the configured models cannot be found.
236      * @throws SAXException                 if the nodes for the configured models cannot be found.
237      * @throws IOException                  if the nodes for the configured models cannot be found.
238      */
239     private NodeList getAllModelConfigurations() throws ParserConfigurationException, SAXException, IOException {
240         final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
241         final DocumentBuilder docBuilder = xmlFactory.newDocumentBuilder();
242         final Document xmlDoc = docBuilder.parse(new InputSource(new StringReader(configXml)));
243         return xmlDoc.getElementsByTagName("model");
244     }
245 
246     /**
247      * Get the XML string of the model with the given id from a list of configured models.
248      * <p>
249      * @param id     the id of the model to be found.
250      * @param models a list of XML <model> nodes.
251      * @return the XML string for the model.
252      */
253     private String getModelsConfiguration(final String id, final NodeList models) {
254         try {
255             for (int i = 0; i < models.getLength(); i++) {
256                 final NamedNodeMap attributes = models.item(i).getAttributes();
257                 final String nodeId = attributes.getNamedItem("id").getNodeValue();
258 
259                 if (id.equals(nodeId)) {
260                     final TransformerFactory transFactory = TransformerFactory.newInstance();
261                     final Transformer transformer = transFactory.newTransformer();
262                     final StringWriter buffer = new StringWriter();
263                     transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
264                     transformer.transform(new DOMSource(models.item(i)),
265                                           new StreamResult(buffer));
266                     return buffer.toString();
267                 }
268             }
269         } catch (TransformerException ex) {
270             log.error("Could not get the configuration for the model [{}]. {}", id, ex.getLocalizedMessage());
271         }
272         return "";
273     }
274 
275     /**
276      * Invocation point.
277      * <p>
278      * @param args the command line arguments passed to Broadwick.
279      */
280     public static void main(final String[] args) {
281 
282         final Broadwick broadwick = new Broadwick(args);
283         broadwick.run();
284     }
285     private Project project;
286     private Logger log;
287     private String configXml;
288 }