summaryrefslogtreecommitdiff
path: root/src/main/src/SSSync.java
blob: fbf41cbb5c0cd966733271509b579fd0558088d1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/*
 * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes
 * Copyright (C) 2014  Ludovic Pouzenc <ludovic@pouzenc.fr>
 *  
 * 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 <http://www.gnu.org/licenses/>
 */

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<AbstractSyncTask> 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<AbstractSyncTask> tasks, long timeOutInMinute, boolean dryRun) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		List<Future<Boolean>> 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<Boolean> 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);
	}

}