Broadwick.java
/*
* Copyright 2013 University of Glasgow.
*
* Licensed 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 broadwick;
import broadwick.config.ConfigValidationErrors;
import broadwick.config.ConfigValidator;
import broadwick.config.generated.Logs;
import broadwick.config.generated.Models;
import broadwick.config.generated.Project;
import broadwick.data.DataReader;
import broadwick.data.Lookup;
import broadwick.model.Model;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Broadwick: Project for Scientific Computing. The Broadwick framework allows for rapid epidemic modelling.
*/
public final class Broadwick {
/**
* Create the Broadwick project to read and verify the configuration files and initialise the project.
* <p>
* @param args the command line arguments supplied to the project.
*/
public Broadwick(final String[] args) {
final LoggingFacade logFacade = new LoggingFacade();
log = logFacade.getRootLogger();
try {
final CliOptions cli = new CliOptions(args);
readConfigFile(logFacade, cli.getConfigurationFileName());
} catch (BroadwickException ex) {
log.error("{}\nSomething went wrong starting project. See the error messages.", ex.getLocalizedMessage());
log.trace(Throwables.getStackTraceAsString(ex));
}
}
/**
* Read the configuration file from the configuration file.
* <p>
* @param logFacade the LoggingFacade object used to log any messages.
* @param configFile the name of the configuration file.
*/
private void readConfigFile(final LoggingFacade logFacade, final String configFile) {
if (!configFile.isEmpty()) {
final File cfg = new File(configFile);
if (!cfg.exists()) {
throw new BroadwickException("Configuration file [" + configFile + "] does not exist.");
}
try {
// read the configuration file
final JAXBContext jaxbContext = JAXBContext.newInstance(Constants.GENERATED_CONFIG_CLASSES_DIR);
final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
project = (Project) unmarshaller.unmarshal(cfg);
// save the config file as a string for later use
final StringWriter writer = new StringWriter();
jaxbContext.createMarshaller().marshal(project, writer);
configXml = writer.toString();
// Validate the configuration file
final ConfigValidator validator = new ConfigValidator(project);
final ConfigValidationErrors validationErrors = validator.validate();
// now set up the logger as defined in the config file, we want to do this
// BEFORE writing the results of validation
final Logs.File file = project.getLogs().getFile();
if (file != null) {
logFacade.addFileLogger(file.getName(), file.getLevel(), file.getPattern(), file.isOverwrite());
}
final Logs.Console console = project.getLogs().getConsole();
if (console != null) {
logFacade.addConsoleLogger(console.getLevel(), console.getPattern());
}
// Log any validation errors.
if (validationErrors.getNumErrors() > 0) {
log.error("Invalid configuration file.\n{}Correct any errors before continuing.", validationErrors.getValidationErrors());
project = validator.getValidatedProject();
}
} catch (JAXBException ex) {
log.error("Could not read configuration file. {}", ex.toString());
log.trace(com.google.common.base.Throwables.getStackTraceAsString(ex));
}
} else {
throw new BroadwickException("No configuration file specified");
}
}
/**
* Run the Broadwick framework.
*/
@SuppressWarnings("squid:S1147")
public void run() {
if (project != null) {
final StopWatch sw = new StopWatch();
sw.start();
// initialise the data, by reading the data files and/or the database.
log.info("Running broadwick {}", BroadwickVersion.getVersionAndTimeStamp());
try (DataReader dr = new DataReader(project.getData())) {
final Map<String, Model> registeredModels = registerModels(project, dr.getLookup());
log.info("Running broadwick for the following models {}", registeredModels.keySet());
// Run the models, each on a separate thread.
// TODO in a single-threaded grid environment we cannot do this - need to think again here....
final int poolSize = registeredModels.size();
if (poolSize > 0) {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("BroadwickModels-%d")
.setDaemon(true)
.build();
final ExecutorService es = Executors.newFixedThreadPool(poolSize, threadFactory);
//final StopWatch sw = new StopWatch();
for (final Entry<String, Model> entry : registeredModels.entrySet()) {
es.submit(new Runnable() {
@Override
public void run() {
final String modelName = entry.getKey();
final Model model = entry.getValue();
try {
log.info("Running {} [{}]", modelName, model.getClass().getCanonicalName());
model.init();
model.run();
model.finalise();
} catch (Exception ex) {
log.error("Error running model {}. see stack trace from details.", modelName);
log.error("{}", Throwables.getStackTraceAsString(ex));
}
}
});
}
es.shutdown();
while (!es.isTerminated()) {
es.awaitTermination(10, TimeUnit.SECONDS);
}
//sw.stop();
//log.trace("Finished {} simulations in {}.", maxSimulations, sw);
}
} catch (Exception ex) {
log.error("{}", ex.getLocalizedMessage());
log.error("{}", Throwables.getStackTraceAsString(ex));
log.error("Something went wrong. See previous messages for details.");
}
log.info("Simulation complete. {}", sw.toString());
// In rare circumstances, where exceptions are caught and the simulation has completed but
// there are still tasks being submitted to the executor, we need to force the progam to quit.
Runtime.getRuntime().exit(0);
}
}
/**
* Create and register the models internally. If there was a problem registering the models an empty cache is
* returned.
* <p>
* @param project the unmarshalled configuration file.
* @param lookup the Lookuup object that allows the model to access the data specified in the data files.
* @return the registered models.
*/
private Map<String, Model> registerModels(final Project project, final Lookup lookup) {
final Map<String, Model> registeredModels = new HashMap<>();
try {
for (Models.Model model : project.getModels().getModel()) {
// Create and register the new model object that we will be running later.
final Model newInstance = Model.class.cast(Class.forName(model.getClassname()).newInstance());
newInstance.setModelConfiguration(getModelsConfiguration(model.getId(), getAllModelConfigurations()));
newInstance.setModelDataLookup(lookup);
newInstance.setModelParameters(model.getParameter());
if (model.getPriors() != null) {
newInstance.setModelPriors(model.getPriors().getGaussianPriorAndUniformPrior());
}
registeredModels.put(model.getId(), newInstance);
}
} catch (ParserConfigurationException | SAXException | IOException | ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException ex) {
log.error("Could not create model ; {}", ex.getLocalizedMessage());
registeredModels.clear();
}
return registeredModels;
}
/**
* Get a collection of XML elements one for each <model> section.
* <p>
* @return a collection of XML elements of each <model>.
* @throws ParserConfigurationException if the nodes for the configured models cannot be found.
* @throws SAXException if the nodes for the configured models cannot be found.
* @throws IOException if the nodes for the configured models cannot be found.
*/
private NodeList getAllModelConfigurations() throws ParserConfigurationException, SAXException, IOException {
final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder docBuilder = xmlFactory.newDocumentBuilder();
final Document xmlDoc = docBuilder.parse(new InputSource(new StringReader(configXml)));
return xmlDoc.getElementsByTagName("model");
}
/**
* Get the XML string of the model with the given id from a list of configured models.
* <p>
* @param id the id of the model to be found.
* @param models a list of XML <model> nodes.
* @return the XML string for the model.
*/
private String getModelsConfiguration(final String id, final NodeList models) {
try {
for (int i = 0; i < models.getLength(); i++) {
final NamedNodeMap attributes = models.item(i).getAttributes();
final String nodeId = attributes.getNamedItem("id").getNodeValue();
if (id.equals(nodeId)) {
final TransformerFactory transFactory = TransformerFactory.newInstance();
final Transformer transformer = transFactory.newTransformer();
final StringWriter buffer = new StringWriter();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(models.item(i)),
new StreamResult(buffer));
return buffer.toString();
}
}
} catch (TransformerException ex) {
log.error("Could not get the configuration for the model [{}]. {}", id, ex.getLocalizedMessage());
}
return "";
}
/**
* Invocation point.
* <p>
* @param args the command line arguments passed to Broadwick.
*/
public static void main(final String[] args) {
final Broadwick broadwick = new Broadwick(args);
broadwick.run();
}
private Project project;
private Logger log;
private String configXml;
}