package eu.bavenir.ogwapi; import java.io.File; import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.logging.ConsoleHandler; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import org.apache.commons.configuration2.XMLConfiguration; import org.apache.commons.configuration2.builder.fluent.Configurations; import org.apache.commons.configuration2.ex.ConfigurationException; import com.mashape.unirest.http.Unirest; import eu.bavenir.ogwapi.commons.monitoring.MessageCounter; import eu.bavenir.ogwapi.restapi.RestletThread; import eu.bavenir.ogwapi.commons.connectors.NeighbourhoodManagerConnector; /** * Main class of the Gateway API program. Loads configuration, initialises * logger and runs the threads. It also waits for signal from the OS to run its * shutdown hook and perform a cleanup. * * @author sulfo * */ public class App { /* === CONSTANTS === */ /** * Path to configuration file. */ private static final String CONFIG_PATH = "config/GatewayConfig.xml"; /** * Name of configuration parameter for switching the additional logging of * events to console (aside from logging them into the log file) on or off. */ private static final String CONFIG_PARAM_LOGGINGCONSOLEOUTPUT = "logging.consoleOutput"; /** * Name of the configuration parameter for path to log file. */ private static final String CONFIG_PARAM_LOGGINGFILE = "logging.file"; /** * Name of the configuration parameter for logging level. */ private static final String CONFIG_PARAM_LOGGINGLEVEL = "logging.level"; /** * Name of the configuration parameter for enabling logging rotation. */ private static final String CONFIG_PARAM_LOGGINGROTATION = "logging.logRotation"; /** * Name of the configuration parameter for size of the logging files. */ private static final String CONFIG_PARAM_LOGGINGSIZE = "logging.maxLogSize"; /** * Name of the configuration parameter for max number of log files to keep. */ private static final String CONFIG_PARAM_LOGGINGMAXFILES = "logging.maxNumFiles"; /** * Default value of {@link #CONFIG_PARAM_LOGGINGCONSOLEOUTPUT * CONFIG_PARAM_LOGGINGCONSOLEOUTPUT} configuration parameter. This value is * taken into account when no suitable value is found in the configuration file. */ private static final Boolean CONFIG_DEF_LOGGINGCONSOLEOUTPUT = false; /** * Default value of {@link #CONFIG_PARAM_LOGGINGLEVEL CONFIG_PARAM_LOGGINGLEVEL} * configuration parameter. This value is taken into account when no suitable * value is found in the configuration file. */ private static final String CONFIG_DEF_LOGGINGLEVEL = "INFO"; /** * Default value of {@link #CONFIG_PARAM_LOGGINGROTATION} configuration parameter. * This value is taken into account when no suitable value is found in the configuration file. */ private static final Boolean CONFIG_DEF_LOGGINGROTATION = true; /** * Default value of {@link #CONFIG_PARAM_LOGGINGSIZE} configuration parameter. * This value is taken into account when no suitable value is found in the configuration file. */ private static final Integer CONFIG_DEF_LOGGINGSIZE = 10485760; /** * Default value of {@link #CONFIG_PARAM_LOGGINGMAXFILES} configuration parameter. * This value is taken into account when no suitable value is found in the configuration file. */ private static final Integer CONFIG_DEF_LOGGINGMAXFILES = 7; /** * Error message for configuration loading failure. */ private static final String ERR_CONF = "Failure during loading the configuration. Does the config file exist?"; /** * Error message for failure during the logging file creation. */ private static final String ERR_LOGGING = "The log file could not be created. Please check the write permissions " + "for given directory."; /** * Error message for initialisation failure. */ private static final String ERR_INIT = "Initialization failed. See standard error stream."; /** * Thread sleep time in milliseconds, used while waiting for signal. */ private static final long THREAD_SLEEP = 100; /** * Name of the configuration parameter for keys path. */ private static final String CONFIG_PARAM_PATH = "platformSecurity.path"; /** * Default value for {@link #CONFIG_PARAM_PATH } parameter. */ private static final String CONFIG_DEF_PATH = "keystore/"; /* === OTHER FIELDS === */ // logging private static Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); private static FileHandler logfileTxt; private static SimpleFormatter formatterTxt; // Apache commons configuration private static Configurations configurations; private static XMLConfiguration config; // executing threads private static RestletThread restletThread; // Classes started on-init private static MessageCounter messageCounter; private static NeighbourhoodManagerConnector nmConnector; // Security token temporary path private static String path; /* === METHODS === */ /** * Initialisation method called during application startup. Loads program * configuration and sets logging facilities. * * @return True if initialisation is successful. False otherwise. */ private static boolean initialize(final String configPath) { // === load the configuration file === configurations = new Configurations(); try { if (configPath != null) { System.out.println("Attempt to load configuration file from path: " + configPath); config = configurations.xml(configPath); } else { System.out.println("Attempt to load configuration file from path: " + CONFIG_PATH); config = configurations.xml(CONFIG_PATH); } } catch (final ConfigurationException e) { e.printStackTrace(); System.err.println(ERR_CONF); return false; } // === set up the logger === // set LOGGER logging level final String confLoggingLevel = config.getString(CONFIG_PARAM_LOGGINGLEVEL, CONFIG_DEF_LOGGINGLEVEL); final Level logLevel = translateLoggingLevel(confLoggingLevel); logger.setLevel(logLevel); // get whether the logger should log to console final Boolean confLoggingConsoleOutput = config.getBoolean(CONFIG_PARAM_LOGGINGCONSOLEOUTPUT, CONFIG_DEF_LOGGINGCONSOLEOUTPUT); final Logger rootLogger = Logger.getLogger(""); final Handler[] handlers = rootLogger.getHandlers(); if (handlers[0] instanceof ConsoleHandler) { if (confLoggingConsoleOutput == false) { // suppress the logging output to the console if set so rootLogger.removeHandler(handlers[0]); } else { // otherwise set the log level handlers[0].setLevel(logLevel); } } // log file - if set final String confLoggingFile = config.getString(CONFIG_PARAM_LOGGINGFILE); if (confLoggingFile != null && !confLoggingFile.isEmpty()) { // Check if log rotation is activated final Boolean logRotation = config.getBoolean(CONFIG_PARAM_LOGGINGROTATION, CONFIG_DEF_LOGGINGROTATION); final Integer logSize = config.getInt(CONFIG_PARAM_LOGGINGSIZE, CONFIG_DEF_LOGGINGSIZE); final Integer logMaxFiles = config.getInt(CONFIG_PARAM_LOGGINGMAXFILES, CONFIG_DEF_LOGGINGMAXFILES); // create the filename string (essentially adding the time stamp to the string) final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy-MM-dd'T'HH_mm_ss.SSS"); final String logFileName = String.format(confLoggingFile, LocalDateTime.now().format(formatter)); // this is for the application itself - the RESTLET needs to be taken care of // later try { if (logRotation) { logfileTxt = new FileHandler(logFileName, logSize, logMaxFiles); } else { logfileTxt = new FileHandler(logFileName); } } catch (SecurityException | IOException e) { e.printStackTrace(); System.err.println(ERR_LOGGING); return false; } formatterTxt = new SimpleFormatter(); // put it together logfileTxt.setFormatter(formatterTxt); logfileTxt.setLevel(logLevel); logger.addHandler(logfileTxt); // now set the same log file for RESTLET - no kidding, this is from their site System.setProperty("java.util.logging.config.file", logFileName); // log message logger.config("Log file: " + logFileName); } // Generate the JWT and perform the handshake with the NM nmConnector = new NeighbourhoodManagerConnector(config, logger); nmConnector.handshake(); // Initialize counters messageCounter = new MessageCounter(config, logger); // === set up the API thread === restletThread = new RestletThread(config, logger, messageCounter); return true; } /** * Translates the string value of logging level configuration parameter to * {@link java.util.logging.Level Level} object, that can be fed to * {@link java.util.logging.logger Logger}. If the stringValue is null, it will * return the default logging level set by {@link #CONFIG_DEF_LOGGINGLEVEL * CONFIG_DEF_LOGGINGLEVEL} constant. If the string contains other unexpected * value (worst case) returns {@link java.util.logging.level#INFO INFO}. * * @param stringValue String value of the configuration parameter. * @return String translated into {@link java.util.logging.level Level} object. */ private static Level translateLoggingLevel(String stringValue) { if (stringValue == null) { stringValue = CONFIG_DEF_LOGGINGLEVEL; } switch (stringValue) { case "OFF": return Level.OFF; case "SEVERE": return Level.SEVERE; case "WARNING": return Level.WARNING; case "INFO": return Level.INFO; case "CONFIG": return Level.CONFIG; case "FINE": return Level.FINE; case "FINER": return Level.FINER; case "FINEST": return Level.FINEST; default: return Level.INFO; } } /** * Main method of the Vicinity Gateway application. In starts the thread and * registers a shutdown hook that waits for OS signal to terminate. * * @param args */ public static void main(final String[] args) { // config path String confPath = null; // load args if (args.length > 0) { for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("-C")) { if (args.length <= i + 1) { System.out.println("You have to put the value (path) after -c option!"); System.exit(1); } confPath = args[++i]; } else if (args[i].equalsIgnoreCase("-H")) { System.out.println("-c pathToTheConfigurationFile"); System.exit(1); } else { System.out.println("Wrong Argument : " + args[i] + " :: -h for Help."); System.exit(1); } } } // attempt to initialise if (!initialize(confPath)) { System.out.println(ERR_INIT); System.exit(1); } // log message logger.info("Vicinity Gateway API initialized."); // start threads restletThread.start(); // log message logger.fine("API thread started."); // register a shutdown hook - this will be executed after catching a signal to // terminate Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { try { // only system.out style logging can be executed at this phase // (but there's nothing too much interesting anyway...) System.out.println( "Vicinity Gateway API: Shutdown hook run, terminating threads and storing counters."); // Save counters messageCounter.saveCounters(); // Remove token file path = config.getString(CONFIG_PARAM_PATH, CONFIG_DEF_PATH) + "ogwapi-token"; final File file = new File(path); file.delete(); // Terminate threads restletThread.terminateThread(); restletThread.join(); // close the Unirest Unirest.shutdown(); } catch (final InterruptedException e) { // nothing else to do e.printStackTrace(); } catch (final IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); while (true) { try { Thread.sleep(THREAD_SLEEP); } catch (final InterruptedException e) { // nothing else to do e.printStackTrace(); } } } }