org.transitime.applications.Core.java Source code

Java tutorial

Introduction

Here is the source code for org.transitime.applications.Core.java

Source

/*
 * This file is part of Transitime.org
 * 
 * Transitime.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Transitime.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Transitime.org .  If not, see <http://www.gnu.org/licenses/>.
 */
package org.transitime.applications;

import java.io.PrintWriter;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.transitime.config.ConfigFileReader;
import org.transitime.configData.AgencyConfig;
import org.transitime.configData.CoreConfig;
import org.transitime.core.ServiceUtils;
import org.transitime.core.TimeoutHandlerModule;
import org.transitime.core.dataCache.PredictionDataCache;
import org.transitime.core.dataCache.VehicleDataCache;
import org.transitime.db.hibernate.DataDbLogger;
import org.transitime.db.hibernate.HibernateUtils;
import org.transitime.db.structs.ActiveRevisions;
import org.transitime.db.structs.Agency;
import org.transitime.gtfs.DbConfig;
import org.transitime.ipc.servers.CommandsServer;
import org.transitime.ipc.servers.ConfigServer;
import org.transitime.ipc.servers.PredictionsServer;
import org.transitime.ipc.servers.ServerStatusServer;
import org.transitime.ipc.servers.VehiclesServer;
import org.transitime.modules.Module;
import org.transitime.monitoring.PidFile;
import org.transitime.utils.SettableSystemTime;
import org.transitime.utils.SystemTime;
import org.transitime.utils.SystemCurrentTime;
import org.transitime.utils.Time;

/**
 * The main class for running a Transitime Core real-time data processing
 * system. Handles command line arguments and then initiates AVL feed.
 * 
 * @author SkiBu Smith
 * 
 */
public class Core {

    private static Core singleton = null;

    // Contains the configuration data read from database
    private final DbConfig configData;

    // For logging data such as AVL reports and arrival times to database
    private final DataDbLogger dataDbLogger;

    private final TimeoutHandlerModule timeoutHandlerModule;

    private final ServiceUtils service;
    private final Time time;

    // So that can access the current time, even when in playback mode
    private SystemTime systemTime = new SystemCurrentTime();

    // Set by command line option. Specifies config rev to use if set
    private static String configRevStr = null;

    // Read in configuration files. This should be done statically before
    // the logback LoggerFactory.getLogger() is called so that logback can
    // also be configured using a transitime config file. The files are
    // specified using the java system property -Dtransitime.configFiles .
    static {
        ConfigFileReader.processConfig();
    }

    private static final Logger logger = LoggerFactory.getLogger(Core.class);

    /********************** Member Functions **************************/

    /**
     * Construct the Core object and read in the config data. This is private
     * so that the createCore() factory method must be used.
     * 
     * @param agencyId
     */
    public Core(String agencyId) {
        // Determine configuration rev to use. If one specified on command
        // line, use it. If not, then use revision stored in db.
        int configRev;
        if (configRevStr != null) {
            // Use config rev from command line

            configRev = Integer.parseInt(configRevStr);
        } else {
            // Read in config rev from ActiveRevisions table in db
            ActiveRevisions activeRevisions = ActiveRevisions.get(agencyId);

            // If config rev not set properly then simply log error.
            // Originally would also exit() but found that want system to 
            // work even without GTFS configuration so that can test AVL feed.
            if (!activeRevisions.isValid()) {
                logger.error("ActiveRevisions in database is not valid. The "
                        + "configuration revs must be set to proper values. {}", activeRevisions);
            }
            configRev = activeRevisions.getConfigRev();
        }

        // Set the timezone so that when dates are read from db or are logged 
        // the time will be correct. Therefore this needs to be done right at
        // the start of the application, before db is read.
        TimeZone timeZone = Agency.getTimeZoneFromDb(agencyId);
        TimeZone.setDefault(timeZone);

        // Clears out the session factory so that a new one will be created for
        // future db access. This way new db connections are made. This is
        // useful for dealing with timezones and postgres. For that situation
        // want to be able to read in timezone from db so can set default 
        // timezone. Problem with postgres is that once a factory is used to 
        // generate sessions the database will continue to use the default 
        // timezone that was configured at that time. This means that future 
        // calls to the db will use the wrong timezone! Through this function 
        // one can read in timezone from database, set the default timezone, 
        // clear the factory so that future db connections will use the newly 
        // configured timezone, and then successfully process dates.
        HibernateUtils.clearSessionFactory();

        // Read in all GTFS based config data from the database
        configData = new DbConfig(agencyId);
        configData.read(configRev);

        // Create the DataDBLogger so that generated data can be stored
        // to database via a robust queue. But don't actually log data
        // if in playback mode since then would be writing data again 
        // that was first written when predictor was run in real time.
        // Note: DataDbLogger needs to be started after the timezone is set.
        // Otherwise when running for a different timezone than what the
        // computer is setup for then can log data using the wrong time!
        // This is strange since setting TimeZone.setDefault() is supposed
        // to work across all threads it appears that sometimes it wouldn't
        // work if Db logger started first.
        dataDbLogger = DataDbLogger.getDataDbLogger(agencyId, CoreConfig.storeDataInDatabase(),
                CoreConfig.pauseIfDbQueueFilling());

        // Start mandatory modules
        timeoutHandlerModule = new TimeoutHandlerModule(AgencyConfig.getAgencyId());
        timeoutHandlerModule.start();

        service = new ServiceUtils(configData);
        time = new Time(configData);
    }

    /**
     * Creates the Core object for the application. There can only be one Core
     * object per application. Uses CoreConfig.getAgencyId() to determine the
     * agencyId. This means it typically uses the agency ID specified by the
     * Java property -Dtransitime.core.agencyId .
     * <p>
     * Usually doesn't need to be called directly because can simply use
     * Core.getInstance().
     * <p>
     * Synchronized to ensure that don't create more than a single Core.
     * 
     * @return The Core singleton, or null if could not create it
     */
    synchronized public static Core createCore() {
        String agencyId = AgencyConfig.getAgencyId();

        // If agencyId not set then can't create a Core. This can happen
        // when doing testing.
        if (agencyId == null) {
            logger.error("No agencyId specified for when creating Core.");
            return null;
        }

        // Make sure only can have a single Core object
        if (Core.singleton != null) {
            logger.error("Core singleton already created. Cannot create another one.");
            return null;
        }

        Core core = new Core(agencyId);
        Core.singleton = core;
        return core;
    }

    /**
     * For obtaining singleton Core object
     * 
     * @returns the Core singleton object for this application, or null if it
     *          could not be created
     */
    public static Core getInstance() {
        if (Core.singleton == null)
            createCore();

        return singleton;
    }

    /**
     * Returns true if core application. If GTFS processing or other application
     * then not a Core application and should't try to read in data such as
     * route names for a trip.
     * 
     * @return true if core application
     */
    public static boolean isCoreApplication() {
        return Core.singleton != null;
    }

    /**
     * Makes the config data available to all
     * @return
     */
    public DbConfig getDbConfig() {
        return configData;
    }

    /**
     * Returns the ServiceUtils object that can be reused for efficiency.
     * @return
     */
    public ServiceUtils getServiceUtils() {
        return service;
    }

    /**
     * For when want to use methods in Time. This is important when need
     * methods that access a Calendar a lot. By putting the Calendar in
     * Time it can be shared.
     * @return
     */
    public Time getTime() {
        return time;
    }

    /**
     * For when need system time but might be in playback mode. If not in
     * playback mode then the time will be the time of the system clock. But if
     * in playback mode then will be using a SettableSystemTime and the time
     * will be that of the last AVL report.
     * 
     * @return The system epoch time
     */
    public long getSystemTime() {
        return systemTime.get();
    }

    /**
     * For when need system time but might be in playback mode. If not in
     * playback mode then the time will be the time of the system clock. But if
     * in playback mode then will be using a SettableSystemTime and the time
     * will be that of the last AVL report.
     * 
     * @return The system epoch time
     */
    public Date getSystemDate() {
        return new Date(getSystemTime());
    }

    /**
     * For setting the system time when in playback or batch mode.
     * 
     * @param systemTime
     */
    public void setSystemTime(long systemEpochTime) {
        this.systemTime = new SettableSystemTime(systemEpochTime);
    }

    /**
     * Returns the Core logger so that each class doesn't need to create
     * its own and have it be configured properly.
     * @return
     */
    public static final Logger getLogger() {
        return logger;
    }

    /**
     * This method logs status of the logger system to the console. Could 
     * be useful for seeing if there are problems with the logger config file.
     */
    private static void outputLoggerStatus() {
        // For debugging output current state of logger
        // Commented out for now because not truly useful
        //       LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        //       StatusPrinter.print(lc);      
    }

    /**
     * Returns the DataDbLogger for logging data to db.
     * @return
     */
    public DataDbLogger getDbLogger() {
        return dataDbLogger;
    }

    /**
     * Returns the timeout handler module
     * @return
     */
    public TimeoutHandlerModule getTimeoutHandlerModule() {
        return timeoutHandlerModule;
    }

    /**
     * Processes all command line options using Apache CLI.
     * Further info at http://commons.apache.org/proper/commons-cli/usage.html
     */
    @SuppressWarnings("static-access") // Needed for using OptionBuilder
    private static void processCommandLineOptions(String[] args) throws ParseException {
        // Specify the options
        Options options = new Options();
        options.addOption("h", "help", false, "Display usage and help info.");

        options.addOption(OptionBuilder.withArgName("configRev").hasArg()
                .withDescription("Specifies optional configuration revision. "
                        + "If not set then the configuration rev will be read "
                        + "from the ActiveRevisions table in the database.")
                .create("configRev"));

        // Parse the options
        CommandLineParser parser = new BasicParser();
        CommandLine cmd = parser.parse(options, args);

        // Handle optional config rev
        if (cmd.hasOption("configRev")) {
            configRevStr = cmd.getOptionValue("configRev");
        }

        // Handle help option
        if (cmd.hasOption("h")) {
            // Display help
            final String commandLineSyntax = "java transitime.jar";
            final PrintWriter writer = new PrintWriter(System.out);
            final HelpFormatter helpFormatter = new HelpFormatter();
            helpFormatter.printHelp(writer, 80, // printedRowWidth
                    commandLineSyntax, "args:", // header
                    options, 2, // spacesBeforeOption
                    2, // spacesBeforeOptionDescription
                    null, // footer
                    true); // displayUsage
            writer.close();
            System.exit(0);
        }
    }

    /**
     * Start the RMI Servers so that clients can obtain data
     * on predictions, vehicles locations, etc.
     *  
     * @param agencyId
     */
    public static void startRmiServers(String agencyId) {
        // Start up all of the RMI servers
        PredictionsServer.start(agencyId, PredictionDataCache.getInstance());
        VehiclesServer.start(agencyId, VehicleDataCache.getInstance());
        ConfigServer.start(agencyId);
        ServerStatusServer.start(agencyId);
        CommandsServer.start(agencyId);
    }

    /**
     * The main program that runs the entire Transitime application.!
     * 
     * @param args
     */
    public static void main(String[] args) {
        try {
            try {
                processCommandLineOptions(args);
            } catch (ParseException e1) {
                e1.printStackTrace();
                System.exit(-1);
            }

            // Write pid file so that monit can automatically start
            // or restart this application
            PidFile.createPidFile(CoreConfig.getPidFileDirectory() + AgencyConfig.getAgencyId() + ".pid");

            // For making sure logger configured properly
            outputLoggerStatus();

            // Initialize the core now
            createCore();

            // Start any optional modules. 
            List<String> optionalModuleNames = CoreConfig.getOptionalModules();
            if (optionalModuleNames.size() > 0)
                logger.info("Starting up optional modules specified via "
                        + "transitime.modules.optionalModulesList param:");
            else
                logger.info("No optional modules to start up.");
            for (String moduleName : optionalModuleNames) {
                logger.info("Starting up optional module " + moduleName);
                Module.start(moduleName);
            }

            // Start the RMI Servers so that clients can obtain data
            // on predictions, vehicles locations, etc.
            String agencyId = AgencyConfig.getAgencyId();
            startRmiServers(agencyId);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

}