//Dstl (c) Crown Copyright 2017 package uk.gov.dstl.baleen.core.manager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Optional; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.gov.dstl.baleen.core.jobs.BaleenJobManager; import uk.gov.dstl.baleen.core.logging.BaleenLogging; import uk.gov.dstl.baleen.core.metrics.MetricsFactory; import uk.gov.dstl.baleen.core.pipelines.BaleenPipelineManager; import uk.gov.dstl.baleen.core.utils.YamlConfiguration; import uk.gov.dstl.baleen.core.web.BaleenWebApi; import uk.gov.dstl.baleen.exceptions.BaleenException; /** * Manages the complete Baleen instance, including all components such as pipelines and web APIs. * * * */ public class BaleenManager { /** * A listener which is passed to {@link BaleenManager} in order to be notified when the manager * is fully initialised. */ public interface BaleenManagerListener { /** * Called when the manager is full started. * * Returning from this function will cause shutdown of the Baleen manager. * * @param manager * the initialised and started manager. */ void onStarted(BaleenManager manager); } private static final Logger LOGGER = LoggerFactory.getLogger(BaleenManager.class); private final Optional<File> configurationFile; private BaleenLogging logging; private BaleenWebApi webApi; private BaleenPipelineManager pipelineManager; private BaleenJobManager jobManager; private boolean exit = false; private boolean started = true; private String yaml = ""; /** * New instance, with optional configuration. * * @param configurationFile */ public BaleenManager(Optional<File> configurationFile) { this.configurationFile = configurationFile; } /** * Initialise the sub components * * @throws Exception */ public synchronized void initiate() throws BaleenException { LOGGER.info("Logging has not yet been configured - any messages will be outputted to the console"); LOGGER.info("Initiating"); YamlConfiguration configuration = new YamlConfiguration(); if (configurationFile.isPresent()) { try (InputStream is = new FileInputStream(configurationFile.get())) { LOGGER.info("Configuration file provided {}", configurationFile.get().getAbsolutePath()); yaml = IOUtils.toString(is); configuration.read(yaml); } catch (IOException ioe) { throw new BaleenException("Unable to read configuration file", ioe); } } else { LOGGER.info("No configuration file provided - default configuration will be used"); } LOGGER.info("Initiating metrics"); MetricsFactory metrics = MetricsFactory.getInstance(); metrics.configure(configuration); metrics.start(); LOGGER.info("Initiating logging - further messages will be outputted as per the provided configuration"); logging = new BaleenLogging(); logging.configure(configuration); logging.start(); LOGGER.info("Initiating pipeline manager"); pipelineManager = new BaleenPipelineManager(); pipelineManager.configure(configuration); pipelineManager.start(); LOGGER.info("Initiating job manager"); jobManager = new BaleenJobManager(); jobManager.configure(configuration); jobManager.start(); LOGGER.info("Initiating web API"); webApi = new BaleenWebApi(this); webApi.configure(configuration); webApi.start(); started = true; LOGGER.info("Initialisation complete"); } /** * Shutdown the sub components. */ public synchronized void shutdown() { if (started) { LOGGER.info("Shutting down"); started = false; List<AbstractBaleenComponent> components = Arrays.asList(pipelineManager, jobManager, webApi, logging); for(AbstractBaleenComponent component : components){ if(component != null){ try { component.stop(); } catch (BaleenException e) { LOGGER.error("Failed to stop "+component.getClass().getSimpleName(), e); } } } MetricsFactory metrics = MetricsFactory.getInstance(); if (metrics != null) { metrics.stop(); } LOGGER.info("Shutdown complete"); } } /** * Start the manager. * */ public void runUntilStopped() { exit = false; run(manager -> { while (!isStopping()) { sleep(1000); } }); } /** * Start up a Baleen instance, then run the provided runnable before shutting down. * * This is useful for full integration tests, or building simple tools. * * @param runnable */ public void run(BaleenManagerListener listener) { exit = false; // Create a hook so we clean up when closed Thread shutdownHook = new Thread() { @Override public void run() { try { shutdown(); } catch (Exception e) { LOGGER.error("Shutting down in runtime error", e); } } }; Runtime.getRuntime().addShutdownHook(shutdownHook); try { initiate(); if (listener != null) { listener.onStarted(this); } shutdown(); // Remove the shutdown hook now we've shutdown ourselves Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (BaleenException be) { // Log error, but we can't rethrow it as we're not catching that // anyway in Baleen and // we'll shutdown here so there's no need to do anything apart from // log it LOGGER.error("Error running Baleen", be); // Make sure we've shutdown try { shutdown(); } catch (Exception e) { LOGGER.error("Error shutting down Baleen after a previous error", e); } } } /** * Sleep for the specified time, ignoring any exceptions that occur * * @param millis * The number of milliseconds to sleep for */ public void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { // Do nothing } } /** * Stop a running instance of BaleenManager. */ public void stop() { exit = true; } /** * Determines if the baleen manager should stop. * * @return true is the baleen manager has been asked to stop */ public boolean isStopping() { return exit; } /** * Get the pipeline manager. * * @return the pipeline manager (non-null have initiate()) */ public BaleenPipelineManager getPipelineManager() { return pipelineManager; } /** * Get the logging system. * * @return the logging instance (non-null have initiate()) */ public BaleenLogging getLogging() { return logging; } /** * Gets the job manager. * * @return the job manager */ public BaleenJobManager getJobManager() { return jobManager; } /** * Get the YAML used to configure this instance. * * @return */ public synchronized String getYaml() { return yaml; } }