/* * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes * Copyright (C) 2014 Ludovic Pouzenc * * This file is part of SSSync. * * SSSync is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SSSync 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 SSSync. If not, see */ import java.io.IOException; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import conf.ConfigConnectionsBean; import conf.ConfigGlobalsBean; import conf.ConfigRootBean; import conf.SSSyncConfParser; import data.io.ConnectionsHolder; import data.io.SSSyncConnectionsFactory; import sync.AbstractSyncTask; import sync.SSSyncTasksFactory; import utils.JVMStatsDumper; /** * Main class for Simple and Stupid Sync'er * * @author lpouzenc */ public class SSSync { private static final Logger logger = Logger.getLogger(SSSync.class.getName()); //XXX migrate to SLF4J ? private static final String LOG_PROPERTIES_FILE = "./conf/log4j.properties"; private static final String DEFAULT_CONFIG_MAIN_FILE = "./conf/sssync.yaml"; private static final String DEFAULT_CONFIG_CONN_FILE = "./conf/connections.yaml"; private static final int ERR_SUCCESS = 0; private static final int ERR_CONFIG_PARSE_ERROR = 1; private static final int ERR_CONN_INIT_ERROR = 2; private static final int ERR_TASK_INIT_ERROR = 3; private static final int ERR_DRYRUN_FAILURE = 4; private static final int ERR_REALRUN_FAILURE = 5; //TODO private static final int ERR_MAXTIME_REACHED = 6; /** * Main entry point. Takes care of cmdline parsing, config files interpretation, * tasks setup and start. * * @param args */ /* No OO-style here, only plain old static sequences using local vars */ public static void main(String[] args) { // log4j setup (first thing to do) PropertyConfigurator.configure(LOG_PROPERTIES_FILE); logger.info("Program start (user: '" + System.getProperty("user.name") + "', cwd: '" + System.getProperty("user.dir") + "')"); //TODO use cmdline args for config file path String mainConfigFile = DEFAULT_CONFIG_MAIN_FILE; String connConfigFile = DEFAULT_CONFIG_CONN_FILE; // Config parsing ConfigRootBean confMain = null; ConfigConnectionsBean confConn = null; try { confMain = SSSyncConfParser.loadMainConfig(mainConfigFile); confConn = SSSyncConfParser.loadConnConfig(connConfigFile); } catch (Exception e) { logger.fatal("Exception while loading configuration", e); end(ERR_CONFIG_PARSE_ERROR); } ConfigGlobalsBean confGlobals = confMain.getGlobals(); // Config dump if DEBUG level (or finer) if ( !logger.getLevel().isGreaterOrEqual(Level.INFO) ) { logger.debug("Current connection configuration :\n" + confConn); logger.debug("Current main configuration :\n" + confMain); } // Connections init logger.info("Connections initialization"); ConnectionsHolder connections = null; try { connections = SSSyncConnectionsFactory.setupConnections(confConn); } catch (Exception e) { logger.fatal("Exception while establishing connections", e); end(ERR_CONN_INIT_ERROR); } // Suggest garbage collector to forget our passwords since we are connected ///XXX If YaML parser is OK with a mutable String type, overwrite maybe is a good idea confConn=null; System.gc(); JVMStatsDumper.logMemoryUsage(); // Tasks init logger.info("Tasks initialization"); List tasks = null; try { tasks = SSSyncTasksFactory.setupTasks(connections, confMain); } catch (Exception e) { logger.fatal("Exception during tasks initialization", e); end(ERR_TASK_INIT_ERROR); } logger.info("Tasks are ready to start"); JVMStatsDumper.logMemoryUsage(); // Tasks first (dry) run if ( ! SSSync.safeTaskRun(tasks, confGlobals.getMaxExecTime(), true) ) { logger.error("Dry-run pass has shown problems, skipping real synchronization"); end(ERR_DRYRUN_FAILURE); } // Tasks second (real) run if ( SSSync.safeTaskRun(tasks, confGlobals.getMaxExecTime(), false) ) { logger.error("Real-run pass has shown problems, data could be messed up !"); end(ERR_REALRUN_FAILURE); } // Clean-up try { connections.close(); } catch (IOException e) { logger.info("Problem during connections closing"); } // Normal exit end(ERR_SUCCESS); } /** * Method to run safely a sequence of tasks within a given time period. * Uses a separate thread, runs all tasks sequentially. * * @param tasks : The list of tasks to run * @param timeOutInMinute : Max execution time of the whole sequence of tasks * @return true if the task has ran successfully, false otherwise */ private static boolean safeTaskRun(List tasks, long timeOutInMinute, boolean dryRun) { ExecutorService executor = Executors.newSingleThreadExecutor(); List> results; boolean aborted = false; logger.info("Starting " + (dryRun?"dry-run":"real-run") + " synchronization pass"); for ( AbstractSyncTask t : tasks ) { t.setDryRun(dryRun); } try { results = executor.invokeAll(tasks, timeOutInMinute, TimeUnit.MINUTES); // Join all tasks, seeking for an unsuccessful execution for (Future r: results) { if ( ! r.get() ) { aborted = true; } } } catch (CancellationException e) { logger.fatal("Global maximum execution time exhausted, aborting tasks !"); aborted = true; } catch (InterruptedException e) { logger.fatal("Worker thread for task execution was interrupted", e); aborted = true; } catch (ExecutionException e) { logger.error("Exception during tasks execution", e.getCause()); aborted = true; } JVMStatsDumper.logMemoryUsage(); executor.shutdown(); return !aborted; } /** * Helper function to always log the end of program * @param result */ private static void end(int result) { JVMStatsDumper.logGCStats(); logger.info("Program end (result code: " + result + ")"); System.exit(result); } }