/* * 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 conf.SSSyncConnectionsFactory; import conf.SSSyncTasksFactory; import data.io.ConnectionsHolder; import sync.BasicSyncTask; 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()); private static final String LOG_PROPERTIES_FILE = "conf/log4j.properties"; private static final String CONFIG_MAIN_FILE = "conf/sssync.yaml"; private static final String 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 */ 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 = CONFIG_MAIN_FILE; String connConfigFile = 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 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. * In a separate thread, it runs all the tasks sequentially. * * @param list * @param timeOutInMinute * @return * @throws ExecutionException * @throws InterruptedException */ private static boolean safeTaskRun(List list, long timeOutInMinute, boolean dryRun) { ExecutorService executor = Executors.newSingleThreadExecutor(); List> results; boolean aborted = false; logger.info("Starting " + (dryRun?"dry-run":"real-run") + " synchronization pass"); for ( BasicSyncTask t : list ) { t.setDryRun(dryRun); } try { results = executor.invokeAll(list, 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); } }