View Javadoc
1   package fr.ifremer.quadrige2.synchro.service.client;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 Client Core
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.base.Function;
27  import com.google.common.base.Preconditions;
28  import com.google.common.base.Predicate;
29  import com.google.common.collect.*;
30  import fr.ifremer.common.synchro.config.SynchroConfiguration;
31  import fr.ifremer.common.synchro.dao.DataIntegrityViolationOnDeleteException;
32  import fr.ifremer.common.synchro.meta.SynchroTableMetadata;
33  import fr.ifremer.common.synchro.service.RejectedRow;
34  import fr.ifremer.common.synchro.service.SynchroContext;
35  import fr.ifremer.common.synchro.service.SynchroDatabaseConfiguration;
36  import fr.ifremer.common.synchro.service.SynchroResult;
37  import fr.ifremer.common.synchro.type.ProgressionModel;
38  import fr.ifremer.quadrige2.core.config.Quadrige2Configuration;
39  import fr.ifremer.quadrige2.core.dao.administration.user.PrivilegeCode;
40  import fr.ifremer.quadrige2.core.dao.data.survey.SurveyExtendDao;
41  import fr.ifremer.quadrige2.core.dao.referential.ReferentialJdbcDao;
42  import fr.ifremer.quadrige2.core.dao.referential.ReferentialJdbcDaoImpl;
43  import fr.ifremer.quadrige2.core.dao.referential.StatusCode;
44  import fr.ifremer.quadrige2.core.dao.technical.Daos;
45  import fr.ifremer.quadrige2.core.dao.technical.DatabaseSchemaDao;
46  import fr.ifremer.quadrige2.core.dao.technical.DateVersions;
47  import fr.ifremer.quadrige2.core.exception.DatabaseSchemaUpdateException;
48  import fr.ifremer.quadrige2.core.exception.Quadrige2BusinessException;
49  import fr.ifremer.quadrige2.core.exception.Quadrige2TechnicalException;
50  import fr.ifremer.quadrige2.core.service.administration.user.UserService;
51  import fr.ifremer.quadrige2.core.service.decorator.DecoratorService;
52  import fr.ifremer.quadrige2.core.vo.data.survey.LightSurveyVO;
53  import fr.ifremer.quadrige2.synchro.dao.SynchroClientDao;
54  import fr.ifremer.quadrige2.synchro.meta.administration.ProgramStrategySynchroTables;
55  import fr.ifremer.quadrige2.synchro.meta.data.DataSynchroTables;
56  import fr.ifremer.quadrige2.synchro.meta.referential.CampaignOccasionSynchroTables;
57  import fr.ifremer.quadrige2.synchro.meta.referential.ReferentialSynchroTables;
58  import fr.ifremer.quadrige2.synchro.meta.system.ContextAndFilterSynchroTables;
59  import fr.ifremer.quadrige2.synchro.meta.system.RuleSynchroTables;
60  import fr.ifremer.quadrige2.synchro.meta.system.TechnicalSynchroTables;
61  import fr.ifremer.quadrige2.synchro.service.SynchroDirection;
62  import fr.ifremer.quadrige2.synchro.service.client.vo.SynchroClientExportResult;
63  import fr.ifremer.quadrige2.synchro.service.client.vo.SynchroClientExportToFileResult;
64  import fr.ifremer.quadrige2.synchro.service.client.vo.SynchroClientImportFromFileResult;
65  import fr.ifremer.quadrige2.synchro.service.client.vo.SynchroClientImportResult;
66  import fr.ifremer.quadrige2.synchro.service.data.DataSynchroContext;
67  import fr.ifremer.quadrige2.synchro.service.data.DataSynchroService;
68  import fr.ifremer.quadrige2.synchro.service.referential.ReferentialSynchroContext;
69  import fr.ifremer.quadrige2.synchro.service.referential.ReferentialSynchroService;
70  import fr.ifremer.quadrige2.synchro.vo.SynchroChangesVO;
71  import fr.ifremer.quadrige2.synchro.vo.SynchroDateOperatorVO;
72  import fr.ifremer.quadrige2.synchro.vo.SynchroImportContextVO;
73  import org.apache.commons.collections4.CollectionUtils;
74  import org.apache.commons.collections4.MapUtils;
75  import org.apache.commons.io.FileUtils;
76  import org.apache.commons.lang3.StringUtils;
77  import org.apache.commons.lang3.time.DateUtils;
78  import org.apache.commons.logging.Log;
79  import org.apache.commons.logging.LogFactory;
80  import org.hibernate.cfg.Environment;
81  import org.hibernate.dialect.Dialect;
82  import org.nuiton.decorator.Decorator;
83  import org.nuiton.i18n.I18n;
84  import org.nuiton.jaxx.application.type.ApplicationProgressionModel;
85  import org.nuiton.util.ZipUtil;
86  import org.springframework.beans.factory.annotation.Autowired;
87  import org.springframework.context.annotation.Lazy;
88  import org.springframework.jdbc.datasource.DataSourceUtils;
89  import org.springframework.stereotype.Service;
90  import org.springframework.transaction.interceptor.TransactionInterceptor;
91  
92  import javax.annotation.Nullable;
93  import javax.annotation.Resource;
94  import javax.sql.DataSource;
95  import java.beans.PropertyChangeEvent;
96  import java.beans.PropertyChangeListener;
97  import java.io.File;
98  import java.io.IOException;
99  import java.nio.charset.Charset;
100 import java.sql.Connection;
101 import java.sql.SQLException;
102 import java.sql.Timestamp;
103 import java.util.*;
104 
105 import static org.nuiton.i18n.I18n.t;
106 
107 /**
108  * <p>
109  * SynchroClientServiceImpl class.
110  * </p>
111  * 
112  */
113 @Service("synchroClientService")
114 @Lazy
115 public class SynchroClientServiceImpl implements SynchroClientInternalService {
116 
117 	private static final String LOCAL_DB_VERSION_FILE = "version.appup";
118 	private static final String TABLE_SURVEY = "SURVEY";
119 
120 	private static final Log log = LogFactory.getLog(SynchroClientServiceImpl.class);
121 
122 	@Resource(name = "databaseSchemaDao")
123 	private DatabaseSchemaDao dbSchemaDao;
124 
125 	@Resource(name = "surveyDao")
126 	private SurveyExtendDao surveyDao;
127 
128 	@Resource(name = "synchroClientDao")
129 	private SynchroClientDao synchroClientDao;
130 
131 	@Resource(name = "dataSynchroService")
132 	private DataSynchroService dataSynchroService;
133 
134 	@Resource(name = "userService")
135 	private UserService userService;
136 
137 	@Resource(name = "referentialSynchroService")
138 	private ReferentialSynchroService referentialSynchroService;
139 
140 	@Resource
141 	private SynchroHistoryService synchroHistoryService;
142 
143 	@Resource
144 	private DataSource dataSource;
145 
146 	private DecoratorService decoratorService;
147 
148 	@Resource(name = "synchroClientService")
149 	private SynchroClientInternalService synchroClientInternalService;
150 
151 	private final Quadrige2Configuration config;
152 	private final SynchroConfiguration synchroConfig;
153 
154 	/**
155 	 * <p>
156 	 * Constructor for SynchroClientServiceImpl.
157 	 * </p>
158 	 * 
159 	 * @param config
160 	 *            a {@link fr.ifremer.quadrige2.core.config.Quadrige2Configuration} object.
161 	 * @param synchroConfig
162 	 *            a {@link fr.ifremer.common.synchro.config.SynchroConfiguration} object.
163 	 * @param decoratorService
164 	 *            a {@link fr.ifremer.quadrige2.core.service.decorator.DecoratorService} object.
165 	 */
166 	@Autowired
167 	public SynchroClientServiceImpl(Quadrige2Configuration config, SynchroConfiguration synchroConfig, DecoratorService decoratorService) {
168 		super();
169 		this.config = config;
170 		this.synchroConfig = synchroConfig;
171 		this.decoratorService = decoratorService;
172 	}
173 
174 	/** {@inheritDoc} */
175 	@Override
176 	public SynchroClientImportResult importFromTempDb(
177 			int userId,
178 			File dbDirToImport,
179 			SynchroImportContextVO importContext,
180 			SynchroRejectedRowResolver dataRejectResolver,
181 			ApplicationProgressionModel progressionModel,
182 			int progressionModelMaxCount) {
183 
184 		SynchroClientImportResult result = null;
185 		try {
186 			result = synchroClientInternalService.importFromTempDbTransactional(
187 					userId,
188 					dbDirToImport,
189 					importContext,
190 					dataRejectResolver,
191 					progressionModel,
192 					progressionModelMaxCount);
193 
194 		} catch (DataIntegrityViolationOnDeleteException deleteException) {
195 
196 			// Handles here the DataIntegrityViolationOnDeleteException, outside the transaction (see Mantis #29900)
197 			SynchroDatabaseConfiguration target = new SynchroDatabaseConfiguration(Quadrige2Configuration.getInstance().getConnectionProperties(),
198 					true);
199 			handleDeleteException(deleteException, target);
200 		}
201 
202 		return result;
203 	}
204 
205 	/** {@inheritDoc} */
206 	@Override
207 	public SynchroClientImportResult importFromTempDbTransactional(
208 			int userId,
209 			File dbDirToImport,
210 			SynchroImportContextVO importContext,
211 			SynchroRejectedRowResolver dataRejectResolver,
212 			ApplicationProgressionModel progressionModel,
213 			int progressionModelMaxCount) {
214 		Preconditions.checkNotNull(progressionModel);
215 		Preconditions.checkNotNull(importContext);
216 		SynchroClientImportResult result = new SynchroClientImportResult();
217 
218 		int progressionStepCount;
219 		if (importContext.isWithReferential() && importContext.isWithData()) {
220 			progressionStepCount = progressionModelMaxCount / 3;
221 		} else if (importContext.isWithReferential()) {
222 			progressionStepCount = progressionModelMaxCount / 2;
223 		} else {
224 			progressionStepCount = progressionModelMaxCount;
225 		}
226 		int progressionStepNumber = 1;
227 
228 		Properties tempDbConnectionProperties = getConnectionPropertiesFromDbDirectory(dbDirToImport);
229 
230 		File versionFile = new File(dbDirToImport, LOCAL_DB_VERSION_FILE);
231 		String newVersion = null;
232 		try {
233 			newVersion = FileUtils.readFileToString(versionFile, Charset.defaultCharset()).trim();
234 		} catch (IOException ex) {
235 			log.warn(t("quadrige2.error.read.file", versionFile.getAbsolutePath()));
236 		}
237 
238 		Date synchroDate = StringUtils.isEmpty(newVersion) ? new Date() : DateVersions.convertVersion2Date(newVersion,
239 				Quadrige2Configuration.getInstance().getDbTimezone());
240 
241 		try {
242 			// First import insert/update referential (without deletes)
243 			if (importContext.isWithReferential()) {
244 				ReferentialSynchroContext referentialContext = createContextAndImportReferentialWithoutDelete(userId,
245 						dbDirToImport,
246 						true,
247 						progressionModel,
248 						progressionStepNumber++,
249 						progressionStepCount);
250 				result.setReferentialContext(referentialContext);
251 			}
252 
253 			// Then import data
254 			if (importContext.isWithData()) {
255 				// Import Temp DB -> Local DB
256 				DataSynchroContext temp2LocalContext = createContextAndImportDataFromTempDB(
257 						userId,
258 						dbDirToImport,
259 						importContext,
260 						progressionModel, progressionStepNumber++,
261 						progressionStepCount);
262 
263 				// Resolve data reject
264 				resolveRejectsAndFinishImportData(temp2LocalContext, dataRejectResolver);
265 
266 				result.setDataContext(temp2LocalContext);
267 			}
268 
269 			// Then import referential delete
270 			if (importContext.isWithReferential()) {
271 
272 				updateContextAndImportReferentialDelete(
273 						result.getReferentialContext(),
274 						true,
275 						progressionModel,
276 						progressionStepNumber++,
277 						progressionStepCount);
278 			}
279 
280 			// Restore the context: set delete AND insert/update as enable
281 			// (because contexts could be reused later...)
282 			if (importContext.isWithData()) {
283 				result.getDataContext().setEnableInsertOrUpdate(true);
284 				result.getDataContext().setEnableDelete(true);
285 
286 				Date newSynchronizationDate = computeDataSynchronizationDate(result.getDataResult().getRejectedRows(), synchroDate,
287 						importContext.getDataUpdateDate());
288 				result.setDataSynchronizationDate(newSynchronizationDate);
289 			}
290 			if (importContext.isWithReferential()) {
291 				result.getReferentialContext().setEnableInsertOrUpdate(true);
292 				result.getReferentialContext().setEnableDelete(true);
293 				// Remove some minutes, to get updates done in other transaction
294 				result.setReferentialSynchronizationDate(DateUtils.addSeconds(synchroDate, config.getImportReferentialUpdateDateOffsetInSecond()));
295 			}
296 		} finally {
297 			shutdownDatabaseSilently(tempDbConnectionProperties);
298 		}
299 
300 		// Log into history file
301 		synchroHistoryService.save(userId, result);
302 
303 		return result;
304 	}
305 
306 	/** {@inheritDoc} */
307 	@Override
308 	public SynchroClientImportResult importFromServerDatabase(int userId,
309 			SynchroImportContextVO importContext,
310 			SynchroRejectedRowResolver dataRejectResolver,
311 			ApplicationProgressionModel progressionModel,
312 			int progressionModelMaxCount) {
313 
314 		SynchroClientImportResult result = null;
315 		try {
316 			synchroClientInternalService.importFromServerDatabaseTransactional(
317 					userId,
318 					importContext,
319 					dataRejectResolver,
320 					progressionModel,
321 					progressionModelMaxCount);
322 
323 		} catch (DataIntegrityViolationOnDeleteException deleteException) {
324 
325 			// Handles here the DataIntegrityViolationOnDeleteException, outside the transaction (see Mantis #29900)
326 			SynchroDatabaseConfiguration target = new SynchroDatabaseConfiguration(Quadrige2Configuration.getInstance().getConnectionProperties(),
327 					true);
328 			handleDeleteException(deleteException, target);
329 		}
330 
331 		return result;
332 	}
333 
334 	/** {@inheritDoc} */
335 	@Override
336 	public SynchroClientImportResult importFromServerDatabaseTransactional(int userId, SynchroImportContextVO importContext,
337 			SynchroRejectedRowResolver dataRejectResolver, ApplicationProgressionModel progressionModel, int progressionModelMaxCount) {
338 		Preconditions.checkNotNull(dataRejectResolver);
339 		Preconditions.checkNotNull(progressionModel);
340 		Preconditions.checkNotNull(importContext);
341 		SynchroClientImportResult result = new SynchroClientImportResult();
342 
343 		Properties sourceConnectionProperties = synchroConfig.getImportConnectionProperties();
344 
345 		boolean withReferential = importContext.isWithReferential();
346 		boolean withData = importContext.isWithData();
347 		Properties tempDbConnectionProperties = null;
348 		File tempDbDirectory = null;
349 
350 		Date synchronizationDate = getServerCurrentTimestamp(sourceConnectionProperties);
351 
352 		if (withData) {
353 			tempDbDirectory = new File(config.getSynchronizationDirectory(), String.format("%s/import/db-%s",
354 					userId,
355 					DateVersions.convertDate2Version(synchronizationDate).toString()
356 					));
357 			tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
358 		}
359 
360 		int progressionStepCount;
361 		if (withReferential && withData) {
362 			progressionStepCount = progressionModelMaxCount / 4;
363 		} else {
364 			progressionStepCount = progressionModelMaxCount / 2;
365 		}
366 		int progressionStepNumber = 1;
367 
368 		try {
369 			// First import referential (no deletes)
370 			if (withReferential) {
371 
372 				ReferentialSynchroContext referentialContext = createContextAndImportReferentialWithoutDelete(
373 						userId,
374 						sourceConnectionProperties,
375 						importContext,
376 						false,
377 						progressionModel,
378 						progressionStepNumber++,
379 						progressionStepCount);
380 				result.setReferentialContext(referentialContext);
381 			}
382 
383 			// Then import data
384 			if (withData) {
385 				// Oracle -> Temp DB
386 				DataSynchroContext server2TempContext = createContextAndImportDataFromServerToTempDB(
387 						userId,
388 						sourceConnectionProperties,
389 						tempDbConnectionProperties,
390 						importContext,
391 						progressionModel,
392 						progressionStepNumber++,
393 						progressionStepCount);
394 
395 				// Temp DB -> Local
396 				DataSynchroContext temp2LocalContext = createContextAndImportDataFromTempDB(
397 						userId,
398 						tempDbConnectionProperties,
399 						importContext,
400 						progressionModel,
401 						progressionStepNumber++,
402 						progressionStepCount);
403 
404 				// Resolve data reject
405 				resolveRejectsAndFinishImportData(temp2LocalContext, dataRejectResolver);
406 
407 				result.setDataContext(temp2LocalContext);
408 			}
409 
410 			// Then import referential delete
411 			if (withReferential) {
412 				updateContextAndImportReferentialDelete(
413 						result.getReferentialContext(),
414 						false, /* source is not a mirror DB */
415 						progressionModel,
416 						progressionStepNumber++,
417 						progressionStepCount);
418 			}
419 
420 			// Restore the context: set delete AND insert/update as enable
421 			// (because contexts could be reused later...)
422 			{
423 				if (importContext.isWithData()) {
424 					result.getDataContext().setEnableInsertOrUpdate(true);
425 					result.getDataContext().setEnableDelete(true);
426 
427 					Date newDataSynchrodDate = computeDataSynchronizationDate(result.getDataResult().getRejectedRows(), synchronizationDate,
428 							importContext.getDataUpdateDate());
429 					result.setDataSynchronizationDate(newDataSynchrodDate);
430 
431 				}
432 				if (importContext.isWithReferential()) {
433 					result.getReferentialContext().setEnableInsertOrUpdate(true);
434 					result.getReferentialContext().setEnableDelete(true);
435 					// Remove some minutes, to get updates done in other transaction
436 					result.setReferentialSynchronizationDate(DateUtils.addSeconds(synchronizationDate,
437 							config.getImportReferentialUpdateDateOffsetInSecond()));
438 				}
439 			}
440 		} finally {
441 			if (withData) {
442 				shutdownDatabaseSilently(tempDbConnectionProperties);
443 				deleteSilently(tempDbDirectory);
444 			}
445 		}
446 
447 		return result;
448 	}
449 
450 	/** {@inheritDoc} */
451 	@Override
452 	public SynchroChangesVO getImportFileInsertAndUpdateChangesTransactional(int userId, File dbZipFile, SynchroImportContextVO importContext,
453 			ApplicationProgressionModel progressionModel, int progressionModelMaxCount, boolean keepTempDirectory) {
454 		Preconditions.checkNotNull(dbZipFile);
455 		Preconditions.checkNotNull(importContext);
456 		Preconditions.checkNotNull(progressionModel);
457 		Preconditions.checkArgument(progressionModelMaxCount > 0);
458 
459 		if (log.isInfoEnabled()) {
460 			log.info(I18n.t("quadrige2.service.synchro.changelog"));
461 		}
462 
463 		int progressionStepCount;
464 		if (importContext.isWithReferential() && importContext.isWithData()) {
465 			progressionStepCount = (progressionModelMaxCount - (2 /* uncompress + decorator */)) / 2;
466 		} else {
467 			progressionStepCount = progressionModelMaxCount - (2 /* uncompress + decorator */);
468 		}
469 		int progressionStepNumber = 1;
470 
471 		// Check dbZipFile validity
472 		checkValidImportFile(dbZipFile);
473 
474 		File tempDirectory = new File(
475 				config.getSynchroImportDirectoryByUser(userId),
476 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
477 
478 		// Create a empty file, for changes output
479 		File changesFile = new File(tempDirectory, "changes.properties");
480 		if (changesFile.exists()) {
481 			try {
482 				FileUtils.forceDelete(changesFile);
483 			} catch (IOException e) {
484 				throw new Quadrige2TechnicalException("Unable to create change log file :" + changesFile.getPath(), e);
485 			}
486 		}
487 
488 		// Uncompress dbZipFile
489 		try {
490 			progressionModel.setMessage(t("quadrige2.synchro.progress.uncompress"));
491 
492 			FileUtils.forceMkdir(tempDirectory);
493 			ZipUtil.uncompress(dbZipFile, tempDirectory);
494 
495 			progressionModel.increments(1);
496 		} catch (IOException e) {
497 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.dbZipFile.notZipFile", dbZipFile.getPath()), e);
498 		}
499 
500 		// Check directory tree, and get DB directory inside
501 		File dbDirToImport = Daos.checkAndNormalizeDbDirectory(tempDirectory);
502 
503 		Properties tempDbConnectionProperties = getConnectionPropertiesFromDbDirectory(dbDirToImport);
504 
505 		// Create the result
506 		SynchroChangesVO result = new SynchroChangesVO();
507 		result.setConnectionProperties(tempDbConnectionProperties);
508 
509 		// Mark transaction as rollback only
510 		TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
511 
512 		try {
513 			Map<String, Map<String, Map<String, Object>>> dataRemapValues = null;
514 
515 			// 1- import insert/update referential (without deletes)
516 			if (importContext.isWithReferential()) {
517 				ReferentialSynchroContext referentialSynchroContext = createContextAndImportReferentialFromFile(
518 						userId,
519 						dbDirToImport,
520 						changesFile,
521 						true, /* rollback only */
522 						null, /* no pk to includes = all */
523 						t("quadrige2.service.synchro.changelog.message"),
524 						progressionModel,
525 						progressionStepNumber++,
526 						progressionStepCount);
527 
528 				// Resolve referential reject, then finish (see mantis #26721)
529 				resolveFileReferentialRejectsAndFinishImportFromFile(referentialSynchroContext, newReferentialSynchroRejectedRowResolver());
530 
531 				// SourceMissingUpdate used as value to remap, during data import
532 				dataRemapValues = referentialSynchroContext.getResult().getSourceMissingUpdates();
533 
534 				// Add rejects to change log
535 				result.addRejects(referentialSynchroContext.getResult().getRejectedRows());
536 			}
537 
538 			// 2- Import Data
539 			if (importContext.isWithData()) {
540 				DataSynchroContext dataContext = createContextAndImportFromFile(
541 						userId,
542 						dbDirToImport,
543 						changesFile,
544 						true, /* rollback only */
545 						null, /* no pk include = all */
546 						dataRemapValues,
547 						false, /* no duplication */
548 						t("quadrige2.service.synchro.changelog.message"),
549 						progressionModel,
550 						progressionStepNumber++,
551 						progressionStepCount);
552 
553 				// Add rejects to change log
554 				result.addRejects(dataContext.getResult().getRejectedRows());
555 			}
556 
557 			// Prepare result, from file
558 			progressionModel.setMessage(t("quadrige2.synchro.progress.changeLog"));
559 			if (changesFile.exists()) {
560 				// Deserialize change log file
561 				result.addFromFile(changesFile);
562 
563 				// Delete file (and parent directory)
564 				FileUtils.deleteQuietly(changesFile);
565 			}
566 
567 			if (!keepTempDirectory) {
568 				FileUtils.deleteQuietly(tempDirectory);
569 			}
570 
571 			progressionModel.increments(1);
572 
573 			return result;
574 
575 		} finally {
576 			shutdownDatabaseSilently(tempDbConnectionProperties);
577 		}
578 	}
579 
580 	/** {@inheritDoc} */
581 	@Override
582 	public SynchroChangesVO getImportFileReferentialDeleteChangesTransactional(int userId, File dbZipFile, SynchroImportContextVO importContext,
583 			ApplicationProgressionModel progressionModel, int progressionModelMaxCount, boolean keepTempDirectory) {
584 
585 		Preconditions.checkNotNull(dbZipFile);
586 		Preconditions.checkNotNull(importContext);
587 		Preconditions.checkNotNull(progressionModel);
588 		Preconditions.checkArgument(progressionModelMaxCount > 0);
589 
590 		if (log.isInfoEnabled()) {
591 			log.info(I18n.t("quadrige2.service.synchro.changelog"));
592 		}
593 
594 		int progressionStepCount = progressionModelMaxCount - (2 /* uncompress + decorator */);
595 		int progressionStepNumber = 1;
596 
597 		// Check dbZipFile validity
598 		checkValidImportFile(dbZipFile);
599 
600 		File tempDirectory = new File(
601 				config.getSynchroImportDirectoryByUser(userId),
602 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
603 
604 		// Create a empty file, for changes output
605 		File changesFile = new File(tempDirectory, "changes.properties");
606 		if (changesFile.exists()) {
607 			try {
608 				FileUtils.forceDelete(changesFile);
609 			} catch (IOException e) {
610 				throw new Quadrige2TechnicalException("Unable to create change log file :" + changesFile.getPath(), e);
611 			}
612 		}
613 
614 		// Uncompress dbZipFile
615 		try {
616 			progressionModel.setMessage(t("quadrige2.synchro.progress.uncompress"));
617 
618 			FileUtils.forceMkdir(tempDirectory);
619 			ZipUtil.uncompress(dbZipFile, tempDirectory);
620 
621 			progressionModel.increments(1);
622 		} catch (IOException e) {
623 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.dbZipFile.notZipFile", dbZipFile.getPath()), e);
624 		}
625 
626 		// Check directory tree, and get DB directory inside
627 		File dbDirToImport = Daos.checkAndNormalizeDbDirectory(tempDirectory);
628 
629 		Properties tempDbConnectionProperties = getConnectionPropertiesFromDbDirectory(dbDirToImport);
630 
631 		// Create the result
632 		SynchroChangesVO result = new SynchroChangesVO();
633 		result.setConnectionProperties(tempDbConnectionProperties);
634 
635 		// Mark transaction as rollback only
636 		TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
637 
638 		try {
639 
640 			// import delete referential
641 			if (importContext.isWithReferential()) {
642 				ReferentialSynchroContext referentialSynchroContext = createContextAndImportReferentialDeleteFromFile(
643 						userId,
644 						dbDirToImport,
645 						changesFile,
646 						true, /* rollback only */
647 						null, /* no pk to includes = all */
648 						t("quadrige2.service.synchro.changelog.message"),
649 						progressionModel,
650 						progressionStepNumber++,
651 						progressionStepCount);
652 
653 				// Add rejects to change log
654 				result.addRejects(referentialSynchroContext.getResult().getRejectedRows());
655 			}
656 
657 			// Prepare result, from file
658 			progressionModel.setMessage(t("quadrige2.synchro.progress.changeLog"));
659 			if (changesFile.exists()) {
660 				// Deserialize change log file
661 				result.addFromFile(changesFile);
662 
663 				// Delete file (and parent directory)
664 				FileUtils.deleteQuietly(changesFile);
665 			}
666 
667 			if (!keepTempDirectory) {
668 				FileUtils.deleteQuietly(tempDirectory);
669 			}
670 
671 			progressionModel.increments(1);
672 
673 			return result;
674 
675 		} finally {
676 			shutdownDatabaseSilently(tempDbConnectionProperties);
677 		}
678 	}
679 
680 	/** {@inheritDoc} */
681 	@Override
682 	public void cleanUpUnusedData(int userId) {
683 
684 		List<Integer> personIdsToRemove = Lists.newArrayList();
685 
686 		// Remove old person sessions, and collect user directory
687 		/*
688 		 * List<PersonSession> personSessionsToRemove =
689 		 * personSessionDao.getOldPersonSessions(config.getSynchronizationNbYearToKeepPersonSession(), personId);
690 		 * if (CollectionUtils.isNotEmpty(personSessionsToRemove)) {
691 		 * // Retrieve the list of all user directories
692 		 * for (PersonSession personSession : personSessionsToRemove) {
693 		 * if (personSession.getPerson() != null
694 		 * && personSession.getPerson().getId() != null) {
695 		 * int personIdToRemove = personSession.getPerson().getId().intValue();
696 		 * PersonDTO personToRemove = quserDao.transformToPersonDTO(personSession.getPerson());
697 		 * personIdsToRemove.add(personIdToRemove);
698 		 * 
699 		 * // Log the deletion
700 		 * log.info(t("quadrige2.service.synchro.import.cleanUpUnusedData.person",
701 		 * decorator(personToRemove),
702 		 * personIdToRemove,
703 		 * config.getSynchronizationNbYearToKeepPersonSession()
704 		 * ));
705 		 * }
706 		 * }
707 		 * 
708 		 * // Remove all person session
709 		 * personSessionDao.remove(personSessionsToRemove);
710 		 * }
711 		 */
712 
713 		// Remove all unused observed locations (without ANY user access rights)
714 		List<Integer> observedLocationIdsToRemove = surveyDao.getCleanableSurveyIds();
715 		if (CollectionUtils.isNotEmpty(observedLocationIdsToRemove)) {
716 			surveyDao.removeByIds(observedLocationIdsToRemove);
717 		}
718 
719 		// Delete user directories (should be done when all DB deletion have been done)
720 		if (CollectionUtils.isNotEmpty(personIdsToRemove)) {
721 			for (Integer personIdToRemove : personIdsToRemove) {
722 				File personDirectory = getSynchroDirectoryByUser(personIdToRemove);
723 
724 				if (personDirectory.exists()) {
725 					FileUtils.deleteQuietly(personDirectory);
726 				}
727 			}
728 		}
729 	}
730 
731 	/** {@inheritDoc} */
732 	@Override
733 	public SynchroClientExportResult exportDataToTempDb(int userId, Set<String> programCodes, ApplicationProgressionModel progressionModel,
734 			int progressionModelMaxCount) {
735 
736 		Preconditions.checkNotNull(progressionModel);
737 
738 		SynchroClientExportResult result = new SynchroClientExportResult();
739 
740 		// creates an empty temporary database
741 		File tempDbDirectory = new File(
742 				config.getSynchroExportDirectoryByUser(userId),
743 				"quadrige2-db-" + DateVersions.convertDate2Version(new Date()).toString());
744 
745 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
746 
747 		if (!Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
748 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
749 		}
750 
751 		result.setTempDbExportDirectory(tempDbDirectory);
752 
753 		// only one step
754 		int progressionStepCount = progressionModelMaxCount;
755 		int progressionStepNumber = 1;
756 
757 		try {
758 			// Export data
759 			DataSynchroContext dataContext = createContextAndExportDataToTempDb(tempDbConnectionProperties,
760 					userId,
761 					programCodes,
762 					progressionModel,
763 					progressionStepNumber++,
764 					progressionStepCount);
765 
766 			result.setDataContext(dataContext);
767 
768 		} finally {
769 			shutdownDatabaseSilently(tempDbConnectionProperties);
770 		}
771 
772 		return result;
773 	}
774 
775 	/** {@inheritDoc} */
776 	@Override
777 	public SynchroClientExportResult exportNationalProgramsToTempDb(int userId,
778 			Set<String> programCodes,
779 
780 			ApplicationProgressionModel progressionModel, int progressionModelMaxCount) {
781 		Preconditions.checkNotNull(progressionModel);
782 
783 		SynchroClientExportResult result = new SynchroClientExportResult();
784 
785 		// creates an empty temporary database
786 		File tempDbDirectory = new File(
787 				config.getSynchroExportDirectoryByUser(userId),
788 				"quadrige2-db-" + DateVersions.convertDate2Version(new Date()).toString());
789 
790 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
791 
792 		if (!fr.ifremer.quadrige2.core.dao.technical.Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
793 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
794 		}
795 
796 		result.setTempDbExportDirectory(tempDbDirectory);
797 
798 		// only one step
799 		int progressionStepCount = progressionModelMaxCount;
800 		int progressionStepNumber = 1;
801 
802 		try {
803 			// Export data
804 			ReferentialSynchroContext context = createContextAndExportProgramsToTempDb(tempDbConnectionProperties,
805 					userId,
806 					programCodes,
807 					progressionModel,
808 					progressionStepNumber++,
809 					progressionStepCount);
810 			result.setReferentialContext(context);
811 
812 		} finally {
813 			shutdownDatabaseSilently(tempDbConnectionProperties);
814 		}
815 
816 		return result;
817 	}
818 
819 	/** {@inheritDoc} */
820 	@Override
821 	public SynchroClientExportResult exportToServerDatabase(int userId, Set<String> programCodes, SynchroRejectedRowResolver rejectResolver,
822 			ApplicationProgressionModel progressionModel, int progressionModelMaxCount) {
823 
824 		// pick temp DB directory
825 		File tempDbDirectory = new File(
826 				config.getSynchroExportDirectoryByUser(userId),
827 				"quadrige2-db-" + DateVersions.convertDate2Version(new Date()).toString());
828 
829 		// creates an empty temporary database
830 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
831 
832 		if (!Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
833 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
834 		}
835 
836 		// 2-step progression model
837 		int progressionStepCount = progressionModelMaxCount / 2;
838 		int progressionStepNumber = 1;
839 
840 		SynchroClientExportResult result = new SynchroClientExportResult();
841 
842 		try {
843 			// 1. export LOCAL -> TEMP DB
844 			DataSynchroContext local2tempSynchroContext = createContextAndExportDataToTempDb(tempDbConnectionProperties,
845 					userId,
846 					programCodes,
847 					progressionModel,
848 					progressionStepNumber++,
849 					progressionStepCount);
850 			result.setDataContext(local2tempSynchroContext);
851 
852 			boolean hasData = local2tempSynchroContext.getResult().getTotalTreated() > 0;
853 
854 			// If no data to export, stop here
855 			if (hasData) {
856 				// follow up
857 
858 				// 2. export TEMP DB -> QUADRIGE2
859 				DataSynchroContext temp2ServerSynchroContext = createContextAndExportDataFromTempDbToServer(tempDbConnectionProperties,
860 						userId,
861 						progressionModel,
862 						progressionStepNumber++,
863 						progressionStepCount);
864 				result.setServerResult(temp2ServerSynchroContext.getResult());
865 
866 				// 3. finalize (revertPk, import)
867 				finishExportData(userId,
868 						result,
869 						rejectResolver,
870 						false /* synchro did not fail */,
871 						true /* revert PKs there */);
872 			}
873 			result.setDataContext(local2tempSynchroContext);
874 
875 		} finally {
876 			shutdownDatabaseSilently(tempDbConnectionProperties);
877 			deleteSilently(tempDbDirectory);
878 		}
879 
880 		return result;
881 	}
882 
883 	/** {@inheritDoc} */
884 	@Override
885 	public boolean finishExportData(int userId, SynchroClientExportResult exportResult,
886 			SynchroRejectedRowResolver rejectResolver, boolean synchroFailed, boolean runPkRevert) {
887 		Preconditions.checkNotNull(exportResult.getServerResult());
888 
889 		boolean hasShownRejectMessage = false;
890 		DataSynchroContext dataSynchroContext = exportResult.getDataContext();
891 		SynchroResult serverResult = exportResult.getServerResult();
892 
893 		// Inverse targetPkStr and pkStr
894 		// Needed in export because source and target are inverted: targetPk=remoteId et pk=localId
895 		inverseRejectsSourceAndTargetPks(serverResult);
896 
897 		Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies = Maps.newHashMap();
898 
899 		// build rejections map with decorated object
900 		Map<RejectedRow.Cause, String> rejectedRows = decorateRejectedRows(serverResult.getRejectedRows());
901 
902 		// show error message if there are some duplicate keys
903 		if (rejectedRows.containsKey(RejectedRow.Cause.DUPLICATE_KEY)) {
904 			rejectResolver.showRejectMessage(rejectedRows, RejectedRow.Cause.DUPLICATE_KEY, synchroFailed);
905 			hasShownRejectMessage = true;
906 		}
907 
908 		// show error message if there are some locked data
909 		if (rejectedRows.containsKey(RejectedRow.Cause.LOCKED)) {
910 			rejectResolver.showRejectMessage(rejectedRows, RejectedRow.Cause.LOCKED, synchroFailed);
911 			hasShownRejectMessage = true;
912 		}
913 
914 		// show error message if there are some deletes rows
915 		if (!synchroFailed) {
916 			RejectedRow.ResolveStrategy deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.DELETED, rejectResolver);
917 			rejectStrategies.put(RejectedRow.Cause.DELETED, deletedRowStrategy);
918 		} else if (rejectedRows.containsKey(RejectedRow.Cause.DELETED)) {
919 			rejectResolver.showRejectMessage(rejectedRows, RejectedRow.Cause.DELETED, true);
920 			hasShownRejectMessage = true;
921 		}
922 
923 		// show error message if there are bad update_date rows
924 		if (!synchroFailed) {
925 			RejectedRow.ResolveStrategy badUpdateDateStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.BAD_UPDATE_DATE, rejectResolver);
926 			rejectStrategies.put(RejectedRow.Cause.BAD_UPDATE_DATE, badUpdateDateStrategy);
927 		} else if (rejectedRows.containsKey(RejectedRow.Cause.BAD_UPDATE_DATE)) {
928 			rejectResolver.showRejectMessage(rejectedRows, RejectedRow.Cause.BAD_UPDATE_DATE, true);
929 			hasShownRejectMessage = true;
930 		}
931 
932 		if (synchroFailed) { // no need to execute the last step
933 			return hasShownRejectMessage;
934 		} // else continue
935 
936 		// execute the last step
937 		// context result might change
938 		final SynchroResult result = serverResult;
939 		SynchroDatabaseConfiguration target = dataSynchroContext.getTarget();
940 		dataSynchroContext.setTarget(dataSynchroContext.getSource());
941 		dataSynchroContext.setSource(null);
942 
943 		// finalize (will restore the dataSynchroContext: result, source and target)
944 		try {
945 			SynchroResult tempResult = new SynchroResult();
946 			dataSynchroContext.setResult(tempResult);
947 
948 			// Beware, synchro context used for finalization must have no source and its target should be the local
949 			// database
950 			dataSynchroService.finish(dataSynchroContext, serverResult, rejectStrategies);
951 
952 			// Reinverse order targetPk/sourcePk
953 			inverseRejectsSourceAndTargetPks(tempResult);
954 
955 			if (!tempResult.isSuccess()) {
956 				// put back the error into the original result
957 				result.setError(tempResult.getError());
958 				throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.finish"), tempResult.getError());
959 			}
960 
961 			// Append result from finish() into original result
962 			result.getRejectedRows().clear();
963 			result.getSourceMissingUpdates().clear();
964 			result.getSourceMissingDeletes().clear();
965 			result.getRejectedRows().putAll(tempResult.getRejectedRows());
966 
967 			Multimap<String, String> pksToRevert = ArrayListMultimap.create(tempResult.getSourceMissingReverts());
968 			if (pksToRevert.isEmpty()) {
969 				// no row to revert: stop here
970 				return hasShownRejectMessage;
971 			}
972 
973 			// if revert could be executed, do it (direct connection to server DB)
974 			if (runPkRevert) {
975 				SynchroImportContextVO importContextVO = new SynchroImportContextVO();
976 				importContextVO.setWithData(true);
977 				importContextVO.setWithReferential(false);
978 				importContextVO.setDataPkIncludes(pksToRevert);
979 				importContextVO.setDataForceEditedRowOverride(true);
980 
981 				ApplicationProgressionModel progressionModel = new ApplicationProgressionModel();
982 				progressionModel.setCurrent(0);
983 				progressionModel.setTotal(100);
984 
985 				importFromServerDatabase(userId, importContextVO, rejectResolver, progressionModel, 100);
986 			} else {
987 				// Save the pk to reverts. The caller will be responsible to re-import it
988 				result.getSourceMissingReverts().putAll(pksToRevert);
989 			}
990 
991 		} finally {
992 			// restore the original context
993 			dataSynchroContext.setResult(result);
994 			dataSynchroContext.setSource(dataSynchroContext.getTarget());
995 			dataSynchroContext.setTarget(target);
996 
997 			// Log result to file
998 			synchroHistoryService.save(userId, exportResult);
999 		}
1000 
1001 		return hasShownRejectMessage;
1002 	}
1003 
1004 	/** {@inheritDoc} */
1005 	@Override
1006 	public SynchroClientExportToFileResult exportToFile(int userId,
1007 			File file, Set<String> programCodes,
1008 			boolean dirtyOnly,
1009 			SynchroDateOperatorVO dateOperator,
1010 			Date startDate,
1011 			Date endDate,
1012 			ApplicationProgressionModel progressionModel, int progressionModelMaxCount) {
1013 		// Check arguments
1014 		Preconditions.checkNotNull(file);
1015 		Preconditions.checkNotNull(progressionModel);
1016 		Preconditions.checkArgument(progressionModelMaxCount > 4);
1017 
1018 		// Creates an empty temporary database
1019 		File tempDirectory = new File(
1020 				config.getSynchroExportDirectoryByUser(userId),
1021 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
1022 		File tempDbDirectory = new File(tempDirectory, fr.ifremer.quadrige2.core.dao.technical.Daos.DB_DIRECTORY);
1023 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
1024 		if (!Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
1025 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
1026 		}
1027 
1028 		// Check if the user is a local admin
1029 		boolean isUserLocalAdmin = userService.hasPrivilege(userId, PrivilegeCode.LOCAL_ADMINISTRATOR.getValue());
1030 		boolean exportNationalProgramsAndRules = isUserLocalAdmin;
1031 
1032 		int progressionStepCount;
1033 		if (exportNationalProgramsAndRules) {
1034 			// 3-step progression model
1035 			progressionStepCount = (progressionModelMaxCount - 2 /* = compression + finish */) / 3;
1036 		} else {
1037 			// 2-step progression model
1038 			progressionStepCount = (progressionModelMaxCount - 2 /* = compression + finish */) / 2;
1039 		}
1040 		int progressionStepNumber = 1;
1041 
1042 		SynchroClientExportToFileResult result = new SynchroClientExportToFileResult();
1043 		result.setFile(file);
1044 
1045 		// export to temp
1046 		try {
1047 			// 1. export data : LOCAL -> TEMP DB
1048 			DataSynchroContext dataSynchroContext = createContextAndExportDataToFile(tempDbConnectionProperties,
1049 					userId,
1050 					programCodes,
1051 					dirtyOnly,
1052 					dateOperator,
1053 					startDate,
1054 					endDate,
1055 					progressionModel,
1056 					progressionStepNumber++,
1057 					progressionStepCount);
1058 			result.setDataContext(dataSynchroContext);
1059 
1060 			// 2. export referential : LOCAL -> TEMP DB
1061 			ReferentialSynchroContext referentialSynchroContext = createContextAndExportLocalReferential(
1062 					userId,
1063 					tempDbConnectionProperties,
1064 					progressionModel,
1065 					progressionStepNumber++,
1066 					progressionStepCount);
1067 			result.setReferentialContext(referentialSynchroContext);
1068 
1069 			// 3. export national programs and rules : LOCAL -> TEMP DB
1070 			if (exportNationalProgramsAndRules) {
1071 				ReferentialSynchroContext nationalProgramsAndRulesContext = createContextAndExportAdditionalReferentialToFile(
1072 						userId,
1073 						null /* no filter on programs = all programs */,
1074 						tempDbConnectionProperties,
1075 						progressionModel,
1076 						progressionStepNumber++,
1077 						progressionStepCount);
1078 
1079 				// Add this result to existing referential context
1080 				referentialSynchroContext.getResult().addAll(nationalProgramsAndRulesContext.getResult());
1081 			}
1082 
1083 			// Close temp DB
1084 			shutdownDatabaseSilently(tempDbConnectionProperties);
1085 
1086 			// Has some rows ?
1087 			boolean hasDataRows = result.getDataResult().getTotalTreated() > 0;
1088 			boolean hasReferentialRows = result.getReferentialResult().getTotalTreated() > 0;
1089 
1090 			// If some rows has been exported, create the ZIP result file
1091 			if (hasDataRows || hasReferentialRows) {
1092 
1093 				progressionModel.increments(t("quadrige2.synchro.progress.compress"));
1094 
1095 				try {
1096 					ZipUtil.compress(file, tempDbDirectory);
1097 				} catch (IOException e) {
1098 					throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.compress"), e);
1099 				}
1100 
1101 				// Finish (update source data has FILE_SYNC)
1102 				if (hasDataRows) {
1103 					progressionModel.increments(t("quadrige2.synchro.progress.finishExport"));
1104 					finishExportDataToFile(result);
1105 				}
1106 
1107 				// Log result to file
1108 				synchroHistoryService.save(userId, result);
1109 			} else {
1110 				progressionModel.increments(2);
1111 			}
1112 
1113 		} finally {
1114 			// Delete the temp DB directory
1115 			deleteSilently(tempDbDirectory);
1116 		}
1117 
1118 		return result;
1119 	}
1120 
1121 	/** {@inheritDoc} */
1122 	@Override
1123 	public SynchroClientExportToFileResult exportReferentialToFile(int userId,
1124 			File file,
1125 			Set<String> programCodes,
1126 			ApplicationProgressionModel progressionModel,
1127 			int progressionModelMaxCount) {
1128 		// Check arguments
1129 		Preconditions.checkNotNull(file);
1130 		Preconditions.checkNotNull(progressionModel);
1131 		Preconditions.checkArgument(progressionModelMaxCount > 1);
1132 
1133 		// Creates an empty temporary database
1134 		File tempDirectory = new File(
1135 				config.getSynchroExportDirectoryByUser(userId),
1136 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
1137 		File tempDbDirectory = new File(tempDirectory, fr.ifremer.quadrige2.core.dao.technical.Daos.DB_DIRECTORY);
1138 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
1139 		if (!Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
1140 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
1141 		}
1142 
1143 		// Check if the user is a local admin
1144 		boolean isUserLocalAdmin = userService.hasPrivilege(userId, PrivilegeCode.LOCAL_ADMINISTRATOR.getValue());
1145 		Preconditions.checkArgument(isUserLocalAdmin || CollectionUtils.isEmpty(programCodes),
1146 				"Not implemented: only local administrator is allow to export referential with a filter on programs");
1147 		boolean exportAdditionalReferential = isUserLocalAdmin;
1148 
1149 		int progressionStepCount;
1150 		if (exportAdditionalReferential) {
1151 			// 2-step progression model
1152 			progressionStepCount = (progressionModelMaxCount - 1 /* = compression */) / 2;
1153 		} else {
1154 			// one step progression model
1155 			progressionStepCount = (progressionModelMaxCount - 1 /* = compression */);
1156 		}
1157 		int progressionStepNumber = 1;
1158 
1159 		SynchroClientExportToFileResult result = new SynchroClientExportToFileResult();
1160 		result.setFile(file);
1161 
1162 		// export to temp
1163 		try {
1164 			// 1. export referential : LOCAL -> TEMP DB
1165 			ReferentialSynchroContext referentialSynchroContext = createContextAndExportLocalReferential(
1166 					userId,
1167 					tempDbConnectionProperties,
1168 					progressionModel,
1169 					progressionStepNumber++,
1170 					progressionStepCount);
1171 			result.setReferentialContext(referentialSynchroContext);
1172 
1173 			// 2. export national programs and rules : LOCAL -> TEMP DB
1174 			if (exportAdditionalReferential) {
1175 				ReferentialSynchroContext programsAndRulesContext = createContextAndExportAdditionalReferentialToFile(
1176 						userId,
1177 						programCodes,
1178 						tempDbConnectionProperties,
1179 						progressionModel,
1180 						progressionStepNumber++,
1181 						progressionStepCount);
1182 
1183 				// Add this result to existing referential context
1184 				referentialSynchroContext.getResult().addAll(programsAndRulesContext.getResult());
1185 			}
1186 
1187 			// Close temp DB
1188 			shutdownDatabaseSilently(tempDbConnectionProperties);
1189 
1190 			// Has some rows ?
1191 			boolean hasReferentialRows = result.getReferentialResult().getTotalTreated() > 0;
1192 
1193 			// If some rows has been exported, create the ZIP result file
1194 			if (hasReferentialRows) {
1195 
1196 				progressionModel.increments(t("quadrige2.synchro.progress.compress"));
1197 				try {
1198 					ZipUtil.compress(file, tempDbDirectory);
1199 				} catch (IOException e) {
1200 					throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.compress"), e);
1201 				}
1202 
1203 				// Log result to file
1204 				synchroHistoryService.save(userId, result);
1205 			}
1206 
1207 		} finally {
1208 			// Delete the temp DB directory
1209 			deleteSilently(tempDbDirectory);
1210 		}
1211 
1212 		return result;
1213 	}
1214 
1215 	/** {@inheritDoc} */
1216 	@Override
1217 	public SynchroClientExportToFileResult exportAllReferentialToFile(int userId, File file, ApplicationProgressionModel progressionModel,
1218 			int progressionModelMaxCount) {
1219 		// Check arguments
1220 		Preconditions.checkNotNull(file);
1221 		Preconditions.checkNotNull(progressionModel);
1222 		Preconditions.checkArgument(progressionModelMaxCount > 1);
1223 
1224 		// Creates an empty temporary database
1225 		File tempDirectory = new File(
1226 				config.getSynchroExportDirectoryByUser(userId),
1227 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
1228 		File tempDbDirectory = new File(tempDirectory, fr.ifremer.quadrige2.core.dao.technical.Daos.DB_DIRECTORY);
1229 		Properties tempDbConnectionProperties = createTempEmptyDb(tempDbDirectory);
1230 		if (!Daos.isValidConnectionProperties(tempDbConnectionProperties)) {
1231 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.create"));
1232 		}
1233 
1234 		// one step progression model
1235 		int progressionStepCount = (progressionModelMaxCount - 1 /* = compression */);
1236 		int progressionStepNumber = 1;
1237 
1238 		SynchroClientExportToFileResult result = new SynchroClientExportToFileResult();
1239 		result.setFile(file);
1240 
1241 		// export to temp
1242 		try {
1243 
1244 			// Copy version.appup file
1245 			{
1246 				File srcDbVersionFile = new File(config.getDbDirectory(), "version.appup");
1247 				if (srcDbVersionFile.exists()) {
1248 					File destDbVersionFile = new File(tempDbDirectory, "version.appup");
1249 					try {
1250 						FileUtils.copyFile(srcDbVersionFile, destDbVersionFile);
1251 					} catch (IOException e) {
1252 						throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.copy", srcDbVersionFile.getPath()), e);
1253 					}
1254 
1255 				}
1256 			}
1257 
1258 			// 1. export all referential : LOCAL -> TEMP DB
1259 			ReferentialSynchroContext referentialSynchroContext = createContextAndExportAllReferential(
1260 					userId,
1261 					tempDbConnectionProperties,
1262 					progressionModel,
1263 					progressionStepNumber++,
1264 					progressionStepCount);
1265 			result.setReferentialContext(referentialSynchroContext);
1266 
1267 			// Close temp DB
1268 			shutdownDatabaseSilently(tempDbConnectionProperties);
1269 
1270 			// Has some rows ?
1271 			boolean hasReferentialRows = result.getReferentialResult().getTotalTreated() > 0;
1272 
1273 			// If some rows has been exported, create the ZIP result file
1274 			if (hasReferentialRows) {
1275 
1276 				progressionModel.increments(t("quadrige2.synchro.progress.compress"));
1277 				try {
1278 					ZipUtil.compress(file, tempDbDirectory);
1279 				} catch (IOException e) {
1280 					throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.compress"), e);
1281 				}
1282 
1283 				// Log result to file
1284 				synchroHistoryService.save(userId, result);
1285 			}
1286 
1287 		} finally {
1288 			// Delete the temp DB directory
1289 			deleteSilently(tempDbDirectory);
1290 		}
1291 
1292 		return result;
1293 	}
1294 
1295 	/** {@inheritDoc} */
1296 	@Override
1297 	public SynchroClientImportFromFileResult importFromFile(int userId,
1298 			File file,
1299 			SynchroImportContextVO importContext,
1300 			SynchroRejectedRowResolver dataRejectResolver,
1301 			ApplicationProgressionModel progressionModel,
1302 			int progressionModelMaxCount) {
1303 		Preconditions.checkNotNull(file);
1304 		Preconditions.checkNotNull(importContext);
1305 		Preconditions.checkNotNull(dataRejectResolver);
1306 		Preconditions.checkNotNull(progressionModel);
1307 		Preconditions.checkArgument(progressionModelMaxCount > 0);
1308 
1309 		int progressionStepCount;
1310 		if (importContext.isWithReferential() && importContext.isWithData()) {
1311 			progressionStepCount = progressionModelMaxCount / 2;
1312 		} else {
1313 			progressionStepCount = progressionModelMaxCount;
1314 		}
1315 		int progressionStepNumber = 1;
1316 
1317 		try {
1318 			DataSourceUtils.getConnection(dataSource).setAutoCommit(false);
1319 		} catch (SQLException ignored) {
1320 		}
1321 
1322 		SynchroClientImportFromFileResult result = new SynchroClientImportFromFileResult();
1323 		result.setFile(file);
1324 
1325 		// Check file validity
1326 		checkValidImportFile(file);
1327 
1328 		File tempDirectory = new File(
1329 				config.getSynchroImportDirectoryByUser(userId),
1330 				config.getSynchroZipFilePrefix() + DateVersions.convertDate2Version(new Date()).toString());
1331 
1332 		// Uncompress file
1333 		try {
1334 			progressionModel.setMessage(t("quadrige2.synchro.progress.uncompress"));
1335 
1336 			FileUtils.forceMkdir(tempDirectory);
1337 			ZipUtil.uncompress(file, tempDirectory);
1338 		} catch (IOException e) {
1339 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.file.notZipFile", file.getPath()), e);
1340 		}
1341 
1342 		// Check directory tree, and get DB directory inside
1343 		File dbDirToImport = Daos.checkAndNormalizeDbDirectory(tempDirectory);
1344 
1345 		Properties tempDbConnectionProperties = getConnectionPropertiesFromDbDirectory(dbDirToImport);
1346 
1347 		try {
1348 			// 1- import insert/update referential (without deletes)
1349 			if (importContext.isWithReferential()) {
1350 				ReferentialSynchroContext temp2LocalContext = createContextAndImportReferentialFromFile(
1351 						userId,
1352 						dbDirToImport,
1353 						importContext.getReferentialPkIncludes(),
1354 						progressionModel,
1355 						progressionStepNumber++,
1356 						progressionStepCount);
1357 
1358 				// Resolve referential reject, then finish (see mantis #26721)
1359 				resolveFileReferentialRejectsAndFinishImportFromFile(
1360 						temp2LocalContext,
1361 						newReferentialSynchroRejectedRowResolver());
1362 
1363 				result.setReferentialContext(temp2LocalContext);
1364 			}
1365 
1366 			// 2- Import Data
1367 			if (importContext.isWithData()) {
1368 				// If there is source missing updates on referential result,
1369 				// use it as remap values
1370 				Map<String, Map<String, Map<String, Object>>> dataRemapValues = getDataRemapValuesFromReferentialResult(result.getReferentialResult());
1371 
1372 				DataSynchroContext temp2LocalContext = createContextAndImportFromFile(
1373 						userId,
1374 						dbDirToImport,
1375 						importContext.getDataPkIncludes(),
1376 						dataRemapValues,
1377 						importContext.isForceDuplication(),
1378 						progressionModel,
1379 						progressionStepNumber++,
1380 						progressionStepCount);
1381 
1382 				// Resolve data reject, then finish
1383 				resolveFileRejectsAndFinishImportFromFile(temp2LocalContext, dataRejectResolver);
1384 
1385 				result.setDataContext(temp2LocalContext);
1386 			}
1387 
1388 			// 3- Import Referential Deletes (see Mantis #29865)
1389 			if (importContext.isWithReferential()) {
1390 
1391 				updateContextAndImportReferentialDelete(
1392 						result.getReferentialContext(),
1393 						true,
1394 						progressionModel,
1395 						progressionStepNumber,
1396 						progressionStepCount);
1397 
1398 			}
1399 
1400 			// Restore the context: set delete AND insert/update as enable
1401 			// (because contexts could be reused later...)
1402 			{
1403 				if (importContext.isWithReferential()) {
1404 					result.getReferentialContext().setEnableInsertOrUpdate(true);
1405 					result.getReferentialContext().setEnableDelete(false);
1406 					result.setReferentialSynchronizationDate(null);
1407 				}
1408 				if (importContext.isWithData()) {
1409 					result.getDataContext().setEnableInsertOrUpdate(true);
1410 					result.getDataContext().setEnableDelete(false);
1411 					result.setDataSynchronizationDate(null); // Could not known a unique date, from a file
1412 				}
1413 			}
1414 		} finally {
1415 			shutdownDatabaseSilently(tempDbConnectionProperties);
1416 		}
1417 
1418 		// Log into history file
1419 		synchroHistoryService.save(userId, result);
1420 
1421 		return result;
1422 	}
1423 
1424 	/**
1425 	 * {@inheritDoc}
1426 	 * 
1427 	 * Compute change log (for insert and update changes only) from a file to import
1428 	 */
1429 	@Override
1430 	public SynchroChangesVO getImportFileInsertAndUpdateChanges(
1431 			int userId,
1432 			File dbZipFile,
1433 			SynchroImportContextVO importContext,
1434 			ApplicationProgressionModel progressionModel,
1435 			int progressionModelMaxCount) {
1436 
1437 		// Do not keep temp db dir
1438 		boolean keepTempDbAfter = false;
1439 
1440 		return synchroClientInternalService.getImportFileInsertAndUpdateChangesTransactional(
1441 				userId,
1442 				dbZipFile,
1443 				importContext,
1444 				progressionModel,
1445 				progressionModelMaxCount,
1446 				keepTempDbAfter);
1447 	}
1448 
1449 	/** {@inheritDoc} */
1450 	@Override
1451 	public SynchroChangesVO getImportFileReferentialDeleteChanges(
1452 			int userId,
1453 			File dbZipFile,
1454 			SynchroImportContextVO importContext,
1455 			ApplicationProgressionModel progressionModel,
1456 			int progressionModelMaxCount) {
1457 
1458 		// Do not keep temp db dir
1459 		boolean keepTempDbAfter = false;
1460 
1461 		return synchroClientInternalService.getImportFileReferentialDeleteChangesTransactional(
1462 				userId,
1463 				dbZipFile,
1464 				importContext,
1465 				progressionModel,
1466 				progressionModelMaxCount,
1467 				keepTempDbAfter);
1468 	}
1469 
1470 	/* -- Internal methods -- */
1471 
1472 	/**
1473 	 * <p>
1474 	 * createTempEmptyDb.
1475 	 * </p>
1476 	 * 
1477 	 * @param tempDbDirectory
1478 	 *            a {@link java.io.File} object.
1479 	 * @return a {@link java.util.Properties} object.
1480 	 */
1481 	protected Properties createTempEmptyDb(File tempDbDirectory) {
1482 
1483 		// init connection properties
1484 		String tempDbName = config.getDbName();
1485 		Properties tempConnectionProperties = new Properties();
1486 		tempConnectionProperties.putAll(config.getConnectionProperties());
1487 		String tempDbUrl = Daos.getJdbcUrl(tempDbDirectory, tempDbName);
1488 
1489 		// DEV ONLY: temp DB could be overwritten (e.g. Temp DB run in server mode)
1490 		String overridedTempDbUrl = config.getApplicationConfig().getOption("quadrige2.synchro.import.tempDb.jdbc.url");
1491 		if (StringUtils.isNotBlank(overridedTempDbUrl)) {
1492 			tempDbUrl = overridedTempDbUrl;
1493 		}
1494 
1495 		tempConnectionProperties.setProperty(Environment.URL, tempDbUrl);
1496 
1497 		// If need to create the Temp DB
1498 		if (Daos.isFileDatabase(tempDbUrl)) {
1499 
1500 			boolean isDbCreated = false;
1501 
1502 			// Get local DB script file to use
1503 			if (Daos.isFileDatabase(config.getJdbcURL())) {
1504 				// try to get script file from URL path
1505 				String dbDirectoryFromUrl = fr.ifremer.quadrige2.core.dao.technical.Daos.getDbDirectoryFromJdbcUrl(config.getJdbcURL());
1506 				File scriptFile = new File(dbDirectoryFromUrl, config.getDbName() + ".script");
1507 
1508 				// If URL could not be parse, try to use the dbDirectory config option
1509 				// WARNING: this option could be NOT synchronize this JDBC URL (e.g. when using quadrige2-core-client in
1510 				// command line)
1511 				if (!scriptFile.exists()) {
1512 					scriptFile = new File(config.getDbDirectory(), config.getDbName() + ".script");
1513 				}
1514 
1515 				// If script file has been found, use it
1516 				if (scriptFile.exists()) {
1517 					// Create the new temps db
1518 					dbSchemaDao.generateNewDb(tempDbDirectory, true, scriptFile, tempConnectionProperties, true/*
1519 																												 * temporary
1520 																												 * DB
1521 																												 */);
1522 					isDbCreated = true;
1523 				}
1524 			}
1525 
1526 			// If DB not created yet, try using the script file from classpath, and update it
1527 			// DEV ONLY (=local DB run in server mode)
1528 			if (!isDbCreated) {
1529 
1530 				// Create the new temps db
1531 				dbSchemaDao.generateNewDb(tempDbDirectory, true, null, tempConnectionProperties, true/* temporary DB */);
1532 
1533 				// Update schema, because script file could be not updated
1534 				try {
1535 					dbSchemaDao.updateSchema(tempConnectionProperties);
1536 				} catch (DatabaseSchemaUpdateException e) {
1537 					throw new Quadrige2TechnicalException(e.getMessage(), e);
1538 				}
1539 			}
1540 		}
1541 
1542 		// DEV ONLY : just create a empty directory
1543 		else {
1544 			try {
1545 				FileUtils.forceMkdir(tempDbDirectory);
1546 			} catch (IOException e) {
1547 				throw new Quadrige2TechnicalException("Could not create temp DB directory", e);
1548 			}
1549 		}
1550 
1551 		return tempConnectionProperties;
1552 	}
1553 
1554 	private ReferentialSynchroContext createContextAndImportReferentialWithoutDelete(int userId,
1555 			Properties sourceConnectionProperties,
1556 			SynchroImportContextVO contextVO,
1557 			boolean isSourceTemporaryDb,
1558 			ApplicationProgressionModel progressionModel,
1559 			int progressionStepNumber, int progressionStepCount) {
1560 
1561 		// Create the synchro context
1562 		ReferentialSynchroContext synchroContext = referentialSynchroService.createSynchroContext(sourceConnectionProperties,
1563 				getTimestampOrNull(contextVO.getReferentialUpdateDate()),
1564 				false,
1565 				true,
1566 				SynchroDirection.IMPORT_TEMP2LOCAL,
1567 				userId,
1568 				null /* all status */);
1569 
1570 		doImportReferential(
1571 				synchroContext,
1572 				isSourceTemporaryDb,
1573 				t("quadrige2.service.synchro.import.referential.message"),
1574 				progressionModel,
1575 				progressionStepNumber,
1576 				progressionStepCount);
1577 
1578 		return synchroContext;
1579 	}
1580 
1581 	private ReferentialSynchroContext createContextAndImportReferentialWithoutDelete(int userId,
1582 			File dbDirToImport,
1583 			boolean isSourceTemporaryDb,
1584 			ApplicationProgressionModel progressionModel,
1585 			int progressionStepNumber,
1586 			int progressionStepCount) {
1587 		// Create the synchro context
1588 		ReferentialSynchroContext synchroContext = referentialSynchroService.createSynchroContext(dbDirToImport,
1589 				null/* all data from Temp DB */,
1590 				false,
1591 				true,
1592 				SynchroDirection.IMPORT_TEMP2LOCAL,
1593 				userId,
1594 				null /* all status */);
1595 
1596 		// Force reimport of program/strategy tables (see mantis #27946)
1597 		synchroContext.setTableNamesForced(ProgramStrategySynchroTables.tableNames());
1598 
1599 		// + Rule tables (need by Q3-déchet)
1600 		if (config.isEnableImportTablesRules()) {
1601 			Set<String> tableNames = Sets.newLinkedHashSet(synchroContext.getTableNames());
1602 			tableNames.addAll(RuleSynchroTables.tableNames());
1603 			synchroContext.setTableNames(tableNames);
1604 		}
1605 
1606 		// Launch the import
1607 		doImportReferential(synchroContext,
1608 				isSourceTemporaryDb,
1609 				t("quadrige2.service.synchro.import.referential.message"),
1610 				progressionModel,
1611 				progressionStepNumber,
1612 				progressionStepCount);
1613 
1614 		return synchroContext;
1615 	}
1616 
1617 	private ReferentialSynchroContext createContextAndImportReferentialFromFile(int userId,
1618 			File dbDirToImport,
1619 			Multimap<String, String> referentialPkIncludes,
1620 			ApplicationProgressionModel progressionModel,
1621 			int progressionStepNumber,
1622 			int progressionStepCount) {
1623 		return createContextAndImportReferentialFromFile(
1624 				userId,
1625 				dbDirToImport,
1626 				null /* disable change log */,
1627 				false /* no rollback only */,
1628 				referentialPkIncludes,
1629 				t("quadrige2.service.synchro.import.referential.message"),
1630 				progressionModel,
1631 				progressionStepNumber,
1632 				progressionStepCount);
1633 	}
1634 
1635 	private ReferentialSynchroContext createContextAndImportReferentialFromFile(int userId,
1636 			File dbDirToImport,
1637 			File changeLogFile,
1638 			boolean rollbackOnly,
1639 			Multimap<String, String> referentialPkIncludes,
1640 			String progressionBaseMessage,
1641 			ApplicationProgressionModel progressionModel,
1642 			int progressionStepNumber,
1643 			int progressionStepCount) {
1644 		// Create the synchro context
1645 		ReferentialSynchroContext synchroContext = referentialSynchroService.createSynchroContext(dbDirToImport,
1646 				null/* all data from Temp DB */,
1647 				false, /* disable delete */
1648 				true, /* enable insert/update */
1649 				SynchroDirection.IMPORT_FILE2LOCAL,
1650 				userId,
1651 				null /* all status */);
1652 
1653 		// change log file (could be null)
1654 		synchroContext.setChangeLogFile(changeLogFile);
1655 
1656 		// pk to includes
1657 		synchroContext.setPkIncludes(referentialPkIncludes);
1658 
1659 		// rollback only (could be set to true, to force transaction to be mark)
1660 		synchroContext.getTarget().setRollbackOnly(rollbackOnly);
1661 
1662 		// tables to import: all referential + program/strategy + rule + campaign/occasion
1663 		{
1664 			Set<String> tableNames = Sets.newLinkedHashSet(synchroContext.getTableNames());
1665 
1666 			// + Programs/Strategy
1667 			// (not really need, because already include inside referential tables)
1668 			tableNames.addAll(ProgramStrategySynchroTables.tableNames());
1669 
1670 			// + Rule
1671 			tableNames.addAll(RuleSynchroTables.tableNames());
1672 
1673 			// + Campaign/Occasion
1674 			// (not really need, because already include inside referential tables)
1675 			tableNames.addAll(CampaignOccasionSynchroTables.tableNames());
1676 
1677 			synchroContext.setTableNames(tableNames);
1678 		}
1679 
1680 		// table to import (without check table max(update_dt) in method 'prepare()')
1681 		{
1682 			Set<String> tableNamesForced = Sets.newLinkedHashSet();
1683 			if (CollectionUtils.isNotEmpty(synchroContext.getTableNamesForced())) {
1684 				tableNamesForced.addAll(synchroContext.getTableNamesForced());
1685 			}
1686 
1687 			// + Program/Strategy: forced, because file could contain new program with an older update_dt - mantis 27946
1688 			tableNamesForced.addAll(ProgramStrategySynchroTables.tableNames());
1689 
1690 			// + Rules (maybe useless)
1691 			tableNamesForced.addAll(RuleSynchroTables.tableNames());
1692 
1693 			synchroContext.setTableNamesForced(tableNamesForced);
1694 		}
1695 
1696 		doImportReferential(synchroContext,
1697 				true /* isSourceTemporaryDb */,
1698 				progressionBaseMessage,
1699 				progressionModel,
1700 				progressionStepNumber,
1701 				progressionStepCount);
1702 
1703 		return synchroContext;
1704 	}
1705 
1706 	private ReferentialSynchroContext createContextAndImportReferentialDeleteFromFile(int userId,
1707 			File dbDirToImport,
1708 			File changeLogFile,
1709 			boolean rollbackOnly,
1710 			Multimap<String, String> referentialPkIncludes,
1711 			String progressionBaseMessage,
1712 			ApplicationProgressionModel progressionModel,
1713 			int progressionStepNumber,
1714 			int progressionStepCount) {
1715 		// Create the synchro context
1716 		ReferentialSynchroContext synchroContext = referentialSynchroService.createSynchroContext(dbDirToImport,
1717 				null/* all data from Temp DB */,
1718 				true, /* enable delete */
1719 				false, /* disable insert/update */
1720 				SynchroDirection.IMPORT_FILE2LOCAL,
1721 				userId,
1722 				ImmutableSet.of(StatusCode.LOCAL_ENABLE.getValue(), StatusCode.LOCAL_DISABLE.getValue()) /* local only */);
1723 
1724 		// change log file (could be null)
1725 		synchroContext.setChangeLogFile(changeLogFile);
1726 
1727 		// pk to includes
1728 		synchroContext.setPkIncludes(referentialPkIncludes);
1729 
1730 		// rollback only (could be set to true, to force transaction to be mark)
1731 		synchroContext.getTarget().setRollbackOnly(rollbackOnly);
1732 
1733 		// tables to import: only program/strategy + rule
1734 		{
1735 			Set<String> tableNames = Sets.newLinkedHashSet(synchroContext.getTableNames());
1736 
1737 			// + Programs/Strategy
1738 			// (not really need, because already include inside referential tables)
1739 			tableNames.addAll(ProgramStrategySynchroTables.tableNames());
1740 
1741 			// + Rule
1742 			tableNames.addAll(RuleSynchroTables.tableNames());
1743 
1744 			synchroContext.setTableNames(tableNames);
1745 		}
1746 
1747 		// no need to force table for delete
1748 		synchroContext.setTableNamesForced(new HashSet<String>());
1749 
1750 		doImportReferential(synchroContext,
1751 				true /* isSourceTemporaryDb */,
1752 				progressionBaseMessage,
1753 				progressionModel,
1754 				progressionStepNumber,
1755 				progressionStepCount);
1756 
1757 		return synchroContext;
1758 	}
1759 
1760 	private void updateContextAndImportReferentialDelete(
1761 			ReferentialSynchroContext synchroContext,
1762 			boolean isSourceTemporaryDb,
1763 			ApplicationProgressionModel progressionModel,
1764 			int progressionStepNumber,
1765 			int progressionStepCount) {
1766 
1767 		// Make sure the given context is OK
1768 		synchroContext.setEnableDelete(true);
1769 		synchroContext.setEnableInsertOrUpdate(false);
1770 
1771 		// Create a new empty result
1772 		SynchroResult previousResult = synchroContext.getResult();
1773 		SynchroResult newResult = new SynchroResult();
1774 		synchroContext.setResult(newResult);
1775 
1776 		doImportReferential(
1777 				synchroContext,
1778 				isSourceTemporaryDb,
1779 				t("quadrige2.service.synchro.import.referential.delete.message"),
1780 				progressionModel,
1781 				progressionStepNumber,
1782 				progressionStepCount);
1783 
1784 		previousResult.addAll(newResult);
1785 		synchroContext.setResult(previousResult);
1786 	}
1787 
1788 	private void doImportReferential(
1789 			ReferentialSynchroContext synchroContext,
1790 			boolean isSourceTemporaryDb,
1791 			String progressionBaseMessage,
1792 			ApplicationProgressionModel progressionModel,
1793 			int progressionStepNumber,
1794 			int progressionStepCount) {
1795 		Preconditions.checkArgument(progressionStepNumber > 0);
1796 
1797 		// Need to known that source=temp DB (see ReferentialSynchroServiceImpl.getRootDeleteOperations())
1798 		synchroContext.getSource().setIsMirrorDatabase(isSourceTemporaryDb);
1799 		// Need to known that target=local DB
1800 		synchroContext.getTarget().setIsMirrorDatabase(false);
1801 		SynchroResult result = synchroContext.getResult();
1802 
1803 		// Add listener on synchro progression model
1804 		int progressionStepOffset = progressionStepCount * (progressionStepNumber - 1);
1805 		addProgressionListeners(
1806 				progressionBaseMessage,
1807 				progressionModel,
1808 				result.getProgressionModel(),
1809 				progressionStepOffset,
1810 				progressionStepCount);
1811 
1812 		referentialSynchroService.prepare(synchroContext);
1813 
1814 		if (!result.isSuccess()) {
1815 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.prepare"), result.getError());
1816 		}
1817 
1818 		referentialSynchroService.synchronize(synchroContext);
1819 
1820 		if (!result.isSuccess()) {
1821 			Exception error = result.getError();
1822 
1823 			// If deletion error, display a special message (mantis #23134)
1824 			// Process this exception if enabled by context (Mantis #29900)
1825 			if (error instanceof DataIntegrityViolationOnDeleteException) {
1826 				DataIntegrityViolationOnDeleteException deleteException = (DataIntegrityViolationOnDeleteException) error;
1827 				if (!synchroContext.isEnableDelete()) {
1828 					handleDeleteException(deleteException, synchroContext.getTarget());
1829 				} else {
1830 					throw deleteException;
1831 				}
1832 			}
1833 
1834 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.synchro"), error);
1835 		}
1836 
1837 		// Make sure the current has the max value
1838 		progressionModel.setCurrent(progressionStepOffset + progressionStepCount);
1839 	}
1840 
1841 	private DataSynchroContext createContextAndImportDataFromTempDB(int userId,
1842 			File dbDirToImport,
1843 			SynchroImportContextVO contextVO,
1844 			ApplicationProgressionModel progressionModel, int stepNumber,
1845 			int progressionModelStepCount) {
1846 
1847 		DataSynchroContext synchroContext = dataSynchroService.createSynchroContext(
1848 				dbDirToImport,
1849 				SynchroDirection.IMPORT_TEMP2LOCAL,
1850 				userId,
1851 				null/* all data from Temp DB */,
1852 				true,
1853 				true
1854 				);
1855 
1856 		// give the force override option to context
1857 		synchroContext.setForceEditedRowOverride(contextVO.isDataForceEditedRowOverride());
1858 
1859 		doImportData(synchroContext,
1860 				t("quadrige2.service.synchro.import.data.message"),
1861 				progressionModel,
1862 				stepNumber,
1863 				progressionModelStepCount);
1864 
1865 		return synchroContext;
1866 	}
1867 
1868 	private DataSynchroContext createContextAndImportDataFromTempDB(
1869 			int userId,
1870 			Properties sourceConnectionProperties,
1871 			SynchroImportContextVO contextVO,
1872 			ApplicationProgressionModel progressionModel,
1873 			int stepNumber,
1874 			int progressionModelStepCount) {
1875 
1876 		DataSynchroContext synchroContext = dataSynchroService.createSynchroContext(
1877 				sourceConnectionProperties,
1878 				SynchroDirection.IMPORT_TEMP2LOCAL,
1879 				userId,
1880 				null/* all data from Temp DB */,
1881 				true,
1882 				true
1883 				);
1884 
1885 		// give the force override option to context
1886 		synchroContext.setForceEditedRowOverride(contextVO.isDataForceEditedRowOverride());
1887 
1888 		doImportData(synchroContext,
1889 				t("quadrige2.service.synchro.import.data.message"),
1890 				progressionModel,
1891 				stepNumber,
1892 				progressionModelStepCount);
1893 
1894 		return synchroContext;
1895 	}
1896 
1897 	private DataSynchroContext createContextAndImportDataFromServerToTempDB(
1898 			int userId,
1899 			Properties sourceConnectionProperties,
1900 			Properties targetConnectionProperties,
1901 			SynchroImportContextVO contextVO,
1902 			ApplicationProgressionModel progressionModel,
1903 			int stepNumber,
1904 			int progressionModelStepCount) {
1905 		Preconditions.checkNotNull(sourceConnectionProperties);
1906 		Preconditions.checkNotNull(targetConnectionProperties);
1907 
1908 		Timestamp lastDataUpdateDate = getTimestampOrNull(contextVO.getDataUpdateDate());
1909 
1910 		// Import deletes only if
1911 		// - last update date has been specified
1912 		// - OR local DB has some data (survey)
1913 		boolean enableDelete = (lastDataUpdateDate != null)
1914 				|| !synchroClientDao.isAllTablesEmpty(DataSynchroTables.getImportTablesIncludes());
1915 
1916 		DataSynchroContext synchroContext = dataSynchroService.createSynchroContext(
1917 				sourceConnectionProperties,
1918 				SynchroDirection.IMPORT_SERVER2TEMP,
1919 				userId,
1920 				lastDataUpdateDate,
1921 				enableDelete,
1922 				true /* always enable insert/update */
1923 				);
1924 
1925 		synchroContext.getTarget().putAllProperties(targetConnectionProperties);
1926 		synchroContext.setDataStartDate(getTimestampOrNull(contextVO.getDataStartDate()));
1927 		synchroContext.setDataEndDate(getTimestampOrNull(contextVO.getDataEndDate()));
1928 		synchroContext.setPkIncludes(contextVO.getDataPkIncludes());
1929 
1930 		doImportData(synchroContext,
1931 				t("quadrige2.service.synchro.import.data.message"),
1932 				progressionModel,
1933 				stepNumber,
1934 				progressionModelStepCount);
1935 
1936 		return synchroContext;
1937 	}
1938 
1939 	private SynchroContext doImportData(DataSynchroContext synchroContext,
1940 			String progressionBaseMessage,
1941 			ApplicationProgressionModel progressionModel,
1942 			int progressionStepNumber,
1943 			int progressionStepCount) {
1944 		Preconditions.checkArgument(progressionStepNumber > 0);
1945 		SynchroResult result = synchroContext.getResult();
1946 
1947 		// Add listener on synchro progression model
1948 		int progressionStepOffset = progressionStepCount * (progressionStepNumber - 1);
1949 		addProgressionListeners(
1950 				progressionBaseMessage,
1951 				progressionModel,
1952 				result.getProgressionModel(),
1953 				progressionStepOffset,
1954 				progressionStepCount);
1955 
1956 		dataSynchroService.prepare(synchroContext);
1957 
1958 		if (!result.isSuccess()) {
1959 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.prepare", result.getError()));
1960 		}
1961 
1962 		dataSynchroService.synchronize(synchroContext);
1963 
1964 		if (!result.isSuccess()) {
1965 			Exception error = result.getError();
1966 
1967 			// If deletion error, display a special message (mantis #23134)
1968 			if (error instanceof DataIntegrityViolationOnDeleteException) {
1969 				throw new Quadrige2BusinessException(t("quadrige2.error.synchro.import.synchro.delete",
1970 						error.getMessage()));
1971 			}
1972 
1973 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.synchro"), error);
1974 		}
1975 
1976 		// Make sure the current has the max value
1977 		progressionModel.setCurrent(progressionStepOffset + progressionStepCount);
1978 
1979 		return synchroContext;
1980 	}
1981 
1982 	private DataSynchroContext createContextAndExportDataToTempDb(Properties tempDbConnectionProperties,
1983 			int userId,
1984 			Set<String> programCodes,
1985 			ApplicationProgressionModel progressionModel,
1986 			int progressionStepNumber,
1987 			int progressionStepCount) {
1988 
1989 		Properties localConnectionProperties = config.getConnectionProperties();
1990 
1991 		// create synchro context
1992 		DataSynchroContext context = dataSynchroService.createSynchroContext(localConnectionProperties,
1993 				SynchroDirection.EXPORT_LOCAL2TEMP,
1994 				userId,
1995 				null, /* no synchronization date */
1996 				true, /* enableDelete */
1997 				true /* enableInsertUpdate */);
1998 		context.getTarget().putAllProperties(tempDbConnectionProperties);
1999 		context.setProgramCodes(programCodes);
2000 
2001 		context = doExportData(context,
2002 				t("quadrige2.synchro.progress.export"),
2003 				progressionModel,
2004 				progressionStepNumber,
2005 				progressionStepCount);
2006 
2007 		return context;
2008 	}
2009 
2010 	private ReferentialSynchroContext createContextAndExportProgramsToTempDb(Properties tempDbConnectionProperties,
2011 			int userId,
2012 			Set<String> programCodes,
2013 			ApplicationProgressionModel progressionModel,
2014 			int progressionStepNumber,
2015 			int progressionStepCount) {
2016 
2017 		Properties localConnectionProperties = config.getConnectionProperties();
2018 
2019 		// create synchro context
2020 		ReferentialSynchroContext context = referentialSynchroService.createSynchroContext(localConnectionProperties,
2021 				null, /* no synchronization date */
2022 				true, /* enableDelete */
2023 				true /* enableInsertUpdate */,
2024 				SynchroDirection.EXPORT_LOCAL2TEMP,
2025 				userId,
2026 				null // no limit on status (pk includes is enought)
2027 				);
2028 		context.getTarget().putAllProperties(tempDbConnectionProperties);
2029 
2030 		// Limit to tables on programs
2031 		context.setTableNames(ProgramStrategySynchroTables.tableNames());
2032 
2033 		// Limit to programs codes
2034 		context.setProgramCodes(programCodes);
2035 
2036 		doExportReferential(context,
2037 				t("quadrige2.synchro.progress.export"),
2038 				progressionModel,
2039 				progressionStepNumber,
2040 				progressionStepCount);
2041 
2042 		return context;
2043 	}
2044 
2045 	private DataSynchroContext createContextAndExportDataFromTempDbToServer(Properties tempDbConnectionProperties,
2046 			int userId,
2047 			ApplicationProgressionModel progressionModel,
2048 			int progressionStepNumber,
2049 			int progressionStepCount) {
2050 
2051 		Properties serverConnectionProperties = synchroConfig.getImportConnectionProperties();
2052 
2053 		// create synchro context
2054 		DataSynchroContext dataSynchroContext = dataSynchroService.createSynchroContext(tempDbConnectionProperties,
2055 				SynchroDirection.EXPORT_TEMP2SERVER,
2056 				userId,
2057 				null, /* no synchronization date */
2058 				true /* enable delete */,
2059 				true /* enable insert */);
2060 		dataSynchroContext.getTarget().putAllProperties(serverConnectionProperties);
2061 
2062 		dataSynchroContext = doExportData(dataSynchroContext,
2063 				t("quadrige2.synchro.progress.export"),
2064 				progressionModel,
2065 				progressionStepNumber,
2066 				progressionStepCount);
2067 
2068 		return dataSynchroContext;
2069 	}
2070 
2071 	private DataSynchroContext doExportData(DataSynchroContext synchroContext,
2072 			String progressionBaseMessage,
2073 			ApplicationProgressionModel progressionModel,
2074 			int progressionStepNumber,
2075 			int progressionStepCount) {
2076 
2077 		Preconditions.checkArgument(progressionStepNumber > 0);
2078 		SynchroResult result = synchroContext.getResult();
2079 
2080 		// Add listener on synchro progression model
2081 		int progressionStepOffset = progressionStepCount * (progressionStepNumber - 1);
2082 		addProgressionListeners(
2083 				progressionBaseMessage,
2084 				progressionModel,
2085 				result.getProgressionModel(),
2086 				progressionStepOffset,
2087 				progressionStepCount);
2088 
2089 		dataSynchroService.prepare(synchroContext);
2090 
2091 		if (!result.isSuccess()) {
2092 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.prepare"), result.getError());
2093 		}
2094 
2095 		dataSynchroService.synchronize(synchroContext);
2096 
2097 		if (!result.isSuccess()) {
2098 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.synchro"), result.getError());
2099 		}
2100 
2101 		// Make sure the current has the max value
2102 		progressionModel.setCurrent(progressionStepOffset + progressionStepCount);
2103 
2104 		return synchroContext;
2105 
2106 	}
2107 
2108 	private void doExportReferential(
2109 			ReferentialSynchroContext synchroContext,
2110 			String progressionBaseMessage,
2111 			ApplicationProgressionModel progressionModel,
2112 			int progressionStepNumber,
2113 			int progressionStepCount) {
2114 		Preconditions.checkArgument(progressionStepNumber > 0);
2115 
2116 		// Need to known that source=local DB (see ReferentialSynchroServiceImpl.getRootDeleteOperations())
2117 		synchroContext.getSource().setIsMirrorDatabase(false);
2118 		// Need to known that target=temp DB
2119 		synchroContext.getTarget().setIsMirrorDatabase(true);
2120 		SynchroResult result = synchroContext.getResult();
2121 
2122 		// Add listener on synchro progression model
2123 		int progressionStepOffset = progressionStepCount * (progressionStepNumber - 1);
2124 		addProgressionListeners(
2125 				progressionBaseMessage,
2126 				progressionModel,
2127 				result.getProgressionModel(),
2128 				progressionStepOffset,
2129 				progressionStepCount);
2130 
2131 		referentialSynchroService.prepare(synchroContext);
2132 
2133 		if (!result.isSuccess()) {
2134 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.prepare"), result.getError());
2135 		}
2136 
2137 		referentialSynchroService.synchronize(synchroContext);
2138 
2139 		if (!result.isSuccess()) {
2140 			Exception error = result.getError();
2141 
2142 			// If deletion error, display a special message (mantis #23134)
2143 			if (error instanceof DataIntegrityViolationOnDeleteException) {
2144 				throw new Quadrige2BusinessException(t("quadrige2.error.synchro.export.synchro.delete",
2145 						error.getMessage()));
2146 			}
2147 
2148 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.synchro"), error);
2149 		}
2150 
2151 		// Make sure the current has the max value
2152 		progressionModel.setCurrent(progressionStepOffset + progressionStepCount);
2153 	}
2154 
2155 	/**
2156 	 * Remap synchro progression model to our progression model
2157 	 * 
2158 	 * @param baseMessage
2159 	 *            a {@link java.lang.String} object.
2160 	 * @param progressionModel
2161 	 * @param synchroProgressionModel
2162 	 *            a {@link fr.ifremer.common.synchro.type.ProgressionModel} object.
2163 	 * @param progressionModelOffset
2164 	 *            a int.
2165 	 * @param progressionCount
2166 	 *            a int.
2167 	 */
2168 	protected void addProgressionListeners(
2169 			final String baseMessage,
2170 			final ApplicationProgressionModel progressionModel,
2171 			final ProgressionModel synchroProgressionModel,
2172 			final int progressionModelOffset,
2173 			final int progressionCount) {
2174 		// Listen 'current' attribute changes
2175 		synchroProgressionModel.addPropertyChangeListener(ProgressionModel.PROPERTY_CURRENT,
2176 				new PropertyChangeListener() {
2177 					@Override
2178 					public void propertyChange(PropertyChangeEvent evt) {
2179 						Integer current = (Integer) evt.getNewValue();
2180 
2181 						onProgressionCurrentChanged(progressionModel,
2182 								current,
2183 								synchroProgressionModel.getTotal(),
2184 								progressionModelOffset,
2185 								progressionCount);
2186 					}
2187 				});
2188 
2189 		// Listen message changes
2190 		synchroProgressionModel.addPropertyChangeListener(ProgressionModel.PROPERTY_MESSAGE,
2191 				new PropertyChangeListener() {
2192 					@Override
2193 					public void propertyChange(PropertyChangeEvent evt) {
2194 						String message = (String) evt.getNewValue();
2195 						progressionModel.setMessage(String.format(baseMessage, message));
2196 					}
2197 				});
2198 	}
2199 
2200 	/**
2201 	 * <p>
2202 	 * onProgressionCurrentChanged.
2203 	 * </p>
2204 	 * 
2205 	 * @param progressionModel
2206 	 *            a {@link org.nuiton.jaxx.application.type.ApplicationProgressionModel} object.
2207 	 * @param current
2208 	 *            a {@link java.lang.Integer} object.
2209 	 * @param total
2210 	 *            a {@link java.lang.Integer} object.
2211 	 * @param progressionOffset
2212 	 *            a int.
2213 	 * @param progressionCount
2214 	 *            a int.
2215 	 */
2216 	protected void onProgressionCurrentChanged(ApplicationProgressionModel progressionModel,
2217 			Integer current,
2218 			Integer total,
2219 			int progressionOffset,
2220 			int progressionCount) {
2221 
2222 		if (current == null || total == null) {
2223 			progressionModel.setCurrent(progressionOffset);
2224 			return;
2225 		}
2226 		int progression = progressionOffset
2227 				+ Math.round(progressionCount * current / total);
2228 		if (progression >= progressionOffset + progressionCount) {
2229 			progression = progressionOffset + progressionCount - 2; // max - 2, to avoid ProgressionPanel to run
2230 			// onComplet()
2231 		}
2232 		progressionModel.setCurrent(progression);
2233 	}
2234 
2235 	private DataSynchroContext createContextAndExportDataToFile(Properties tempDbConnectionProperties,
2236 			int userId,
2237 			Set<String> programCodes,
2238 			boolean dirtyOnly, SynchroDateOperatorVO dateOperator, Date startDate, Date endDate,
2239 			ApplicationProgressionModel progressionModel,
2240 			int progressionStepNumber,
2241 			int progressionStepCount) {
2242 
2243 		Properties localConnectionProperties = config.getConnectionProperties();
2244 
2245 		// create synchro context
2246 		DataSynchroContext context = dataSynchroService.createSynchroContext(localConnectionProperties,
2247 				SynchroDirection.EXPORT_LOCAL2FILE,
2248 				userId,
2249 				null, /* no synchronization date */
2250 				true, /* enableDelete */
2251 				true /* enableInsertUpdate */);
2252 		context.getTarget().putAllProperties(tempDbConnectionProperties);
2253 		context.setProgramCodes(programCodes);
2254 		context.setDirtyOnly(dirtyOnly);
2255 		context.setDataStartDate(startDate);
2256 		context.setDataEndDate(endDate);
2257 		context.setDateOperator(dateOperator);
2258 
2259 		context = doExportData(context,
2260 				t("quadrige2.synchro.progress.export"),
2261 				progressionModel,
2262 				progressionStepNumber,
2263 				progressionStepCount);
2264 
2265 		return context;
2266 	}
2267 
2268 	private ReferentialSynchroContext createContextAndExportLocalReferential(int userId,
2269 			Properties tempDbConnectionProperties,
2270 			ApplicationProgressionModel progressionModel,
2271 			int progressionStepNumber,
2272 			int progressionStepCount) {
2273 
2274 		Properties localConnectionProperties = config.getConnectionProperties();
2275 
2276 		// create synchro context
2277 		ReferentialSynchroContext context = referentialSynchroService.createSynchroContext(localConnectionProperties,
2278 				null, /* no synchronization date */
2279 				true, /* enableDelete */
2280 				true /* enableInsertUpdate */,
2281 				SynchroDirection.EXPORT_LOCAL2FILE,
2282 				userId,
2283 				/* Limit to local referential : */
2284 				Sets.newHashSet(StatusCode.LOCAL_ENABLE.getValue(),
2285 						StatusCode.LOCAL_DISABLE.getValue())
2286 				);
2287 		context.getTarget().putAllProperties(tempDbConnectionProperties);
2288 
2289 		// Set tables to export
2290 		{
2291 			Set<String> tableNames = Sets.newHashSet(context.getTableNames());
2292 
2293 			// Remove programs/strategy (not export here, but with additional referential)
2294 			tableNames.removeAll(ProgramStrategySynchroTables.tableNames());
2295 
2296 			// Remove campaign/occasion
2297 			tableNames.removeAll(CampaignOccasionSynchroTables.tableNames());
2298 
2299 			// Remove rule (just in case importation from central database use, one day, this tables)
2300 			tableNames.removeAll(RuleSynchroTables.tableNames());
2301 
2302 			context.setTableNames(tableNames);
2303 		}
2304 
2305 		// Import local referential into temp DB
2306 		doExportReferential(context,
2307 				t("quadrige2.synchro.progress.export"),
2308 				progressionModel,
2309 				progressionStepNumber,
2310 				progressionStepCount);
2311 
2312 		return context;
2313 	}
2314 
2315 	private ReferentialSynchroContext createContextAndExportAdditionalReferentialToFile(int userId,
2316 			Set<String> programCodes,
2317 			Properties tempDbConnectionProperties,
2318 			ApplicationProgressionModel progressionModel,
2319 			int progressionStepNumber,
2320 			int progressionStepCount) {
2321 
2322 		Properties localConnectionProperties = config.getConnectionProperties();
2323 
2324 		// create synchro context
2325 		ReferentialSynchroContext context = referentialSynchroService.createSynchroContext(localConnectionProperties,
2326 				null, /* no synchronization date */
2327 				true, /* enableDelete */
2328 				true /* enableInsertUpdate */,
2329 				SynchroDirection.EXPORT_LOCAL2FILE,
2330 				userId,
2331 				/* Do NOT filter by status, to includes all tables even if there have no status_cd column */
2332 				null
2333 				);
2334 		context.getTarget().putAllProperties(tempDbConnectionProperties);
2335 
2336 		// Set program codes (if empty: export all programs, local and national)
2337 		if (CollectionUtils.isNotEmpty(programCodes)) {
2338 			context.setProgramCodes(programCodes);
2339 		}
2340 
2341 		// Set additional tables to export
2342 		{
2343 			Set<String> tableNames = Sets.newHashSet();
2344 
2345 			// + Program/Strategy
2346 			tableNames.addAll(ProgramStrategySynchroTables.tableNames());
2347 
2348 			// + Rule
2349 			tableNames.addAll(RuleSynchroTables.tableNames());
2350 
2351 			// + campaign/occasion
2352 			tableNames.addAll(CampaignOccasionSynchroTables.tableNames());
2353 
2354 			context.setTableNames(tableNames);
2355 		}
2356 
2357 		// Export from LOCAL -> TEMP DB
2358 		doExportReferential(context,
2359 				t("quadrige2.synchro.progress.export"),
2360 				progressionModel,
2361 				progressionStepNumber,
2362 				progressionStepCount);
2363 
2364 		return context;
2365 	}
2366 
2367 	private ReferentialSynchroContext createContextAndExportAllReferential(int userId,
2368 			Properties tempDbConnectionProperties,
2369 			ApplicationProgressionModel progressionModel,
2370 			int progressionStepNumber,
2371 			int progressionStepCount) {
2372 
2373 		Properties localConnectionProperties = config.getConnectionProperties();
2374 
2375 		// create synchro context
2376 		ReferentialSynchroContext context = referentialSynchroService.createSynchroContext(localConnectionProperties,
2377 				null, /* no synchronization date */
2378 				true, /* enableDelete */
2379 				true /* enableInsertUpdate */,
2380 				SynchroDirection.EXPORT_LOCAL2FILE,
2381 				userId,
2382 				/* Do NOT filter by status, to includes all tables even if there have no status_cd column */
2383 				null
2384 				);
2385 		context.getTarget().putAllProperties(tempDbConnectionProperties);
2386 
2387 		// To enable integrity constraints in target database
2388 		// => So make is not set as a mirror database
2389 		context.getTarget().setIsMirrorDatabase(false);
2390 
2391 		// Set tables to export
2392 		{
2393 			// All referential tables (with program/strategy, campaign/occasion)
2394 			Set<String> tableNames = Sets.newHashSet(context.getTableNames());
2395 
2396 			// + Rule
2397 			tableNames.addAll(RuleSynchroTables.tableNames());
2398 
2399 			// + Context/filter
2400 			tableNames.addAll(ContextAndFilterSynchroTables.tableNames());
2401 
2402 			// + Technical tables (e.g. SYSTEM_VERSION to avoid error when installing the database)
2403 			tableNames.addAll(TechnicalSynchroTables.tableNames());
2404 
2405 			context.setTableNames(tableNames);
2406 		}
2407 
2408 		// Import local referential into temp DB
2409 		doExportReferential(context,
2410 				t("quadrige2.synchro.progress.export"),
2411 				progressionModel,
2412 				progressionStepNumber,
2413 				progressionStepCount);
2414 
2415 		return context;
2416 	}
2417 
2418 	private Properties getConnectionPropertiesFromDbDirectory(File dbDirectory) {
2419 		Properties dbConnectionProperties = new Properties();
2420 		dbConnectionProperties.putAll(config.getConnectionProperties());
2421 		dbConnectionProperties.setProperty(Environment.URL, Daos.getJdbcUrl(dbDirectory, config.getDbName()));
2422 		return dbConnectionProperties;
2423 	}
2424 
2425 	/**
2426 	 * <p>
2427 	 * getTimestampOrNull.
2428 	 * </p>
2429 	 * 
2430 	 * @param date
2431 	 *            a {@link java.util.Date} object.
2432 	 * @return a {@link java.sql.Timestamp} object.
2433 	 */
2434 	protected Timestamp getTimestampOrNull(Date date) {
2435 		return date == null ? null : new Timestamp(date.getTime());
2436 	}
2437 
2438 	/**
2439 	 * <p>
2440 	 * decorate.
2441 	 * </p>
2442 	 * 
2443 	 * @param object
2444 	 *            a O object.
2445 	 * @param name
2446 	 *            a {@link java.lang.String} object.
2447 	 * @param <O>
2448 	 *            a O object.
2449 	 * @return a {@link java.lang.String} object.
2450 	 */
2451 	protected <O> String decorate(O object, String name) {
2452 		Decorator<O> decorator = decoratorService.getDecorator(object, name);
2453 		Preconditions.checkNotNull(decorator);
2454 		return decorator.toString(object);
2455 	}
2456 
2457 	/**
2458 	 * <p>
2459 	 * decorate.
2460 	 * </p>
2461 	 * 
2462 	 * @param object
2463 	 *            a O object.
2464 	 * @param <O>
2465 	 *            a O object.
2466 	 * @return a {@link java.lang.String} object.
2467 	 */
2468 	protected <O> String decorate(O object) {
2469 		Decorator<O> decorator = decoratorService.getDecorator(object);
2470 		Preconditions.checkNotNull(decorator);
2471 		return decorator.toString(object);
2472 	}
2473 
2474 	/**
2475 	 * <p>
2476 	 * shutdownDatabaseSilently.
2477 	 * </p>
2478 	 * 
2479 	 * @param connectionProperties
2480 	 *            a {@link java.util.Properties} object.
2481 	 */
2482 	protected void shutdownDatabaseSilently(Properties connectionProperties) {
2483 		try {
2484 			Daos.shutdownDatabase(connectionProperties);
2485 		} catch (Exception e1) {
2486 			// Just log, but do nothing
2487 			log.warn(t("quadrige2.error.synchro.import.shutdown"));
2488 		}
2489 	}
2490 
2491 	/**
2492 	 * <p>
2493 	 * deleteSilently.
2494 	 * </p>
2495 	 * 
2496 	 * @param dirToDelete
2497 	 *            a {@link java.io.File} object.
2498 	 */
2499 	protected void deleteSilently(File dirToDelete) {
2500 		try {
2501 			FileUtils.forceDelete(dirToDelete);
2502 		} catch (IOException e1) {
2503 			// Just log, but do nothing
2504 			log.warn(String.format("Could not delete temp directory: %s", dirToDelete.getAbsolutePath()));
2505 		}
2506 	}
2507 
2508 	/**
2509 	 * <p>
2510 	 * getServerCurrentTimestamp.
2511 	 * </p>
2512 	 * 
2513 	 * @param serverConnectionProperties
2514 	 *            a {@link java.util.Properties} object.
2515 	 * @return a {@link java.util.Date} object.
2516 	 */
2517 	protected Date getServerCurrentTimestamp(Properties serverConnectionProperties) {
2518 		Connection connection = null;
2519 
2520 		try {
2521 			connection = Daos.createConnection(serverConnectionProperties);
2522 			Dialect dialect = Dialect.getDialect(serverConnectionProperties);
2523 
2524 			Date result = fr.ifremer.common.synchro.dao.Daos.getCurrentTimestamp(connection, dialect);
2525 
2526 			return result;
2527 		} catch (SQLException e) {
2528 			throw new Quadrige2TechnicalException(e);
2529 		} finally {
2530 			Daos.closeSilently(connection);
2531 		}
2532 	}
2533 
2534 	/**
2535 	 * <p>
2536 	 * resolveRejectsAndFinishImportData.
2537 	 * </p>
2538 	 * 
2539 	 * @param temp2LocalContext
2540 	 *            a {@link fr.ifremer.quadrige2.synchro.service.data.DataSynchroContext} object.
2541 	 * @param resolver
2542 	 *            a {@link fr.ifremer.quadrige2.synchro.service.client.SynchroRejectedRowResolver} object.
2543 	 */
2544 	protected void resolveRejectsAndFinishImportData(
2545 			DataSynchroContext temp2LocalContext,
2546 			SynchroRejectedRowResolver resolver
2547 			) {
2548 		Preconditions.checkNotNull(temp2LocalContext);
2549 
2550 		Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies = Maps.newHashMap();
2551 
2552 		// build rejections map with decorated object
2553 		Map<RejectedRow.Cause, String> rejectedRows = decorateRejectedRows(temp2LocalContext.getResult().getRejectedRows());
2554 
2555 		// show error message if there are some deletes rows
2556 		RejectedRow.ResolveStrategy deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.BAD_UPDATE_DATE, resolver);
2557 		rejectStrategies.put(RejectedRow.Cause.BAD_UPDATE_DATE, deletedRowStrategy);
2558 
2559 		// execute the last step
2560 		temp2LocalContext.setEnableDelete(true);
2561 		temp2LocalContext.setEnableInsertOrUpdate(true);
2562 		doFinishImportData(temp2LocalContext, rejectStrategies);
2563 
2564 		SynchroResult result = temp2LocalContext.getResult();
2565 		if (!result.isSuccess()) {
2566 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.finish", result.getError()));
2567 		}
2568 	}
2569 
2570 	/**
2571 	 * <p>
2572 	 * resolveFileRejectsAndFinishImportFromFile.
2573 	 * </p>
2574 	 * 
2575 	 * @param file2LocalContext
2576 	 *            a {@link fr.ifremer.quadrige2.synchro.service.data.DataSynchroContext} object.
2577 	 * @param resolver
2578 	 *            a {@link fr.ifremer.quadrige2.synchro.service.client.SynchroRejectedRowResolver} object.
2579 	 */
2580 	protected void resolveFileRejectsAndFinishImportFromFile(
2581 			DataSynchroContext file2LocalContext,
2582 			SynchroRejectedRowResolver resolver
2583 			) {
2584 		Preconditions.checkNotNull(file2LocalContext);
2585 
2586 		Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies = Maps.newHashMap();
2587 
2588 		// build rejections map with decorated object
2589 		Map<RejectedRow.Cause, String> rejectedRows = decorateRejectedRows(file2LocalContext.getResult().getRejectedRows());
2590 
2591 		// show error message if there are some deletes rows
2592 		RejectedRow.ResolveStrategy deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.BAD_UPDATE_DATE, resolver);
2593 		rejectStrategies.put(RejectedRow.Cause.BAD_UPDATE_DATE, deletedRowStrategy);
2594 
2595 		// show error message if there are some duplicate rows
2596 		deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.DUPLICATE_KEY, resolver);
2597 		rejectStrategies.put(RejectedRow.Cause.DUPLICATE_KEY, deletedRowStrategy);
2598 
2599 		// execute the last step
2600 		file2LocalContext.setEnableDelete(true);
2601 		file2LocalContext.setEnableInsertOrUpdate(true);
2602 		doFinishImportData(file2LocalContext, rejectStrategies);
2603 
2604 		SynchroResult result = file2LocalContext.getResult();
2605 		if (!result.isSuccess()) {
2606 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.finish"), result.getError());
2607 		}
2608 	}
2609 
2610 	/**
2611 	 * <p>
2612 	 * getReplaceValuesFromResult.
2613 	 * </p>
2614 	 * 
2615 	 * @param synchroResult
2616 	 *            a {@link fr.ifremer.common.synchro.service.SynchroResult} object.
2617 	 * @return a {@link java.util.Map} object.
2618 	 */
2619 	protected Map<String, Map<String, Map<String, String>>> getReplaceValuesFromResult(
2620 			SynchroResult synchroResult
2621 			) {
2622 		Preconditions.checkNotNull(synchroResult);
2623 
2624 		if (MapUtils.isEmpty(synchroResult.getRejectedRows())) {
2625 			return null;
2626 		}
2627 
2628 		Map<String, Map<String, Map<String, String>>> allReplaceValues = Maps.newHashMap();
2629 
2630 		for (String tableName : synchroResult.getRejectedRows().keySet()) {
2631 
2632 			Map<String, Map<String, String>> replaceValuesByTable = Maps.newHashMap();
2633 			allReplaceValues.put(tableName, replaceValuesByTable);
2634 
2635 			for (RejectedRow rejectedRow : RejectedRow.parseFromString(synchroResult.getRejectedRows().get(tableName))) {
2636 
2637 			}
2638 		}
2639 
2640 		return allReplaceValues;
2641 	}
2642 
2643 	/**
2644 	 * <p>
2645 	 * resolveFileReferentialRejectsAndFinishImportFromFile.
2646 	 * </p>
2647 	 * 
2648 	 * @param file2LocalContext
2649 	 *            a {@link fr.ifremer.quadrige2.synchro.service.referential.ReferentialSynchroContext} object.
2650 	 * @param resolver
2651 	 *            a {@link fr.ifremer.quadrige2.synchro.service.client.SynchroRejectedRowResolver} object.
2652 	 */
2653 	protected void resolveFileReferentialRejectsAndFinishImportFromFile(
2654 			ReferentialSynchroContext file2LocalContext,
2655 			SynchroRejectedRowResolver resolver
2656 			) {
2657 		Preconditions.checkNotNull(file2LocalContext);
2658 
2659 		SynchroResult result = file2LocalContext.getResult();
2660 
2661 		Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies = Maps.newHashMap();
2662 
2663 		// WARN : add remap PKs has REJECT ROWS: to be sure there will be added as source missing update
2664 		// (fix Unit Test importFromFileWithRemapPKsToEmptyDb)
2665 		// note: This block has been moved upper to populate result.getRejectedRows() (related to Mantis #28808)
2666 		Map<String, Map<String, String>> remapPks = file2LocalContext.getTarget().getRemapPks();
2667 		if (MapUtils.isNotEmpty(remapPks)) {
2668 			for (String tableName : remapPks.keySet()) {
2669 				Map<String, String> tableRemapPks = remapPks.get(tableName);
2670 				for (String sourcePkStr : tableRemapPks.keySet()) {
2671 					String targetPkStr = tableRemapPks.get(sourcePkStr);
2672 					addRejectForRemappedPkFromFile(result, tableName, sourcePkStr, targetPkStr);
2673 				}
2674 			}
2675 		}
2676 
2677 		// build rejections map with decorated object
2678 		Map<RejectedRow.Cause, String> rejectedRows = decorateRejectedRows(result.getRejectedRows());
2679 
2680 		// show error message if there are some deletes rows
2681 		RejectedRow.ResolveStrategy deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.BAD_UPDATE_DATE, resolver);
2682 		rejectStrategies.put(RejectedRow.Cause.BAD_UPDATE_DATE, deletedRowStrategy);
2683 
2684 		// show error message if there are some duplicate rows
2685 		deletedRowStrategy = resolveRejects(rejectedRows, RejectedRow.Cause.DUPLICATE_KEY, resolver);
2686 		rejectStrategies.put(RejectedRow.Cause.DUPLICATE_KEY, deletedRowStrategy);
2687 
2688 		// execute the last step
2689 		file2LocalContext.setEnableDelete(true);
2690 		file2LocalContext.setEnableInsertOrUpdate(true);
2691 		doFinishImportReferential(file2LocalContext, rejectStrategies);
2692 
2693 		if (!result.isSuccess()) {
2694 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.referential.finish"), result.getError());
2695 		}
2696 	}
2697 
2698 	private void addRejectForRemappedPkFromFile(SynchroResult result, String tableName, String sourcePkStr, String targetPkStr) {
2699 
2700 		// Add reject for remapped Pk
2701 		result.addReject(tableName, sourcePkStr, RejectedRow.Cause.DUPLICATE_KEY.name(), targetPkStr, "REMAPPED_PK");
2702 
2703 		// Add specific reject for TAXON_NAME (see Mantis #29431)
2704 		if (ReferentialSynchroTables.TAXON_NAME.name().equalsIgnoreCase(tableName)) {
2705 			// Add a remap for REFERENCE_TAXON Pk also
2706 			result.addReject(ReferentialSynchroTables.REFERENCE_TAXON.name(), sourcePkStr, RejectedRow.Cause.DUPLICATE_KEY.name(), targetPkStr,
2707 					"REMAPPED_PK");
2708 		}
2709 	}
2710 
2711 	/**
2712 	 * <p>
2713 	 * resolveRejects.
2714 	 * </p>
2715 	 * 
2716 	 * @param rejectedRows
2717 	 *            a {@link java.util.Map} object.
2718 	 * @param rejectedStatus
2719 	 *            a {@link fr.ifremer.common.synchro.service.RejectedRow.Cause} object.
2720 	 * @param resolver
2721 	 *            a {@link fr.ifremer.quadrige2.synchro.service.client.SynchroRejectedRowResolver} object.
2722 	 * @return a {@link fr.ifremer.common.synchro.service.RejectedRow.ResolveStrategy} object.
2723 	 */
2724 	protected RejectedRow.ResolveStrategy resolveRejects(
2725 			Map<RejectedRow.Cause, String> rejectedRows,
2726 			RejectedRow.Cause rejectedStatus,
2727 			SynchroRejectedRowResolver resolver) {
2728 
2729 		RejectedRow.ResolveStrategy result = null;
2730 
2731 		if (rejectedRows.containsKey(rejectedStatus)) {
2732 			String rejectMessage = rejectedRows.get(rejectedStatus);
2733 
2734 			result = resolver.resolveReject(rejectedStatus, null, rejectMessage);
2735 		}
2736 
2737 		return result != null ? result : RejectedRow.ResolveStrategy.DO_NOTHING;
2738 	}
2739 
2740 	/**
2741 	 * This will finish the import, and merge the previous result to the finish result.
2742 	 * 
2743 	 * @param temp2LocalContext
2744 	 *            a {@link fr.ifremer.quadrige2.synchro.service.data.DataSynchroContext} object.
2745 	 * @param rejectStrategies
2746 	 *            a {@link java.util.Map} object.
2747 	 */
2748 	protected void doFinishImportData(
2749 			DataSynchroContext temp2LocalContext,
2750 			Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies) {
2751 		Preconditions.checkNotNull(temp2LocalContext.getResult());
2752 
2753 		// Prepare the result to merge and return at the end
2754 		SynchroResult result = temp2LocalContext.getResult();
2755 
2756 		// Prepare the context, with a new empty result
2757 		SynchroResult tempResult = new SynchroResult();
2758 		temp2LocalContext.setResult(tempResult);
2759 
2760 		try {
2761 			// call finish() to resolve some rejected and apply missing updates
2762 			SynchroDatabaseConfiguration tempDbConfiguration = temp2LocalContext.getSource();
2763 			temp2LocalContext.setSource(null);
2764 			dataSynchroService.finish(temp2LocalContext, result, rejectStrategies);
2765 			if (!tempResult.isSuccess()) {
2766 				// Copy the error into the merged result
2767 				result.setError(tempResult.getError());
2768 				return;
2769 			}
2770 
2771 			// Clean rejected rows (should be obsolete after the finish())
2772 			// then append temp result to the final result
2773 			result.getRejectedRows().clear();
2774 			result.addAll(tempResult);
2775 
2776 			// If some PK need to be re-imported, re-run the importation
2777 			Multimap<String, String> pksToRevert = ArrayListMultimap.create(result.getSourceMissingReverts());
2778 			if (pksToRevert == null || pksToRevert.isEmpty()) {
2779 				// newResult and previousResult will be merged on finally
2780 				return;
2781 			}
2782 
2783 			// Run revert, from TempDB
2784 			// Set the pk to updates
2785 			temp2LocalContext.setPkIncludes(pksToRevert);
2786 			temp2LocalContext.setForceEditedRowOverride(true);
2787 			temp2LocalContext.setSource(tempDbConfiguration);
2788 			tempResult.clear();
2789 
2790 			dataSynchroService.prepare(temp2LocalContext);
2791 
2792 			if (!tempResult.isSuccess()) {
2793 				result.setError(tempResult.getError());
2794 				return;
2795 			}
2796 
2797 			dataSynchroService.synchronize(temp2LocalContext);
2798 
2799 			// clear processed source missing reverts (should have been processed)
2800 			result.getSourceMissingReverts().clear();
2801 			result.addAll(tempResult);
2802 
2803 			if (!tempResult.isSuccess()) {
2804 				result.setError(tempResult.getError());
2805 				return;
2806 			}
2807 
2808 		}
2809 
2810 		// Always restore the original result in context
2811 		finally {
2812 			temp2LocalContext.setResult(result);
2813 		}
2814 	}
2815 
2816 	/**
2817 	 * This will finish the import, and merge the previous result to the finish result.
2818 	 * 
2819 	 * @param temp2LocalContext
2820 	 *            a {@link fr.ifremer.quadrige2.synchro.service.referential.ReferentialSynchroContext} object.
2821 	 * @param rejectStrategies
2822 	 *            a {@link java.util.Map} object.
2823 	 */
2824 	protected void doFinishImportReferential(
2825 			ReferentialSynchroContext temp2LocalContext,
2826 			Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectStrategies) {
2827 		Preconditions.checkNotNull(temp2LocalContext.getResult());
2828 
2829 		// Prepare the result to merge and return at the end
2830 		SynchroResult result = temp2LocalContext.getResult();
2831 
2832 		// Prepare the context, with a new empty result
2833 		SynchroResult tempResult = new SynchroResult();
2834 		temp2LocalContext.setResult(tempResult);
2835 
2836 		SynchroDatabaseConfiguration tempDbConfiguration = temp2LocalContext.getSource();
2837 		try {
2838 			// call finish() to resolve some rejected and apply missing updates
2839 			temp2LocalContext.setSource(null);
2840 			referentialSynchroService.finish(temp2LocalContext, result, rejectStrategies);
2841 			if (!tempResult.isSuccess()) {
2842 				// Copy the error into the merged result
2843 				result.setError(tempResult.getError());
2844 				return;
2845 			}
2846 
2847 			// Clean rejected rows (should be obsolete after the finish())
2848 			// then append temp result to the final result
2849 			result.getRejectedRows().clear();
2850 			result.addAll(tempResult);
2851 		}
2852 
2853 		// Always restore the original context (result and source)
2854 		finally {
2855 			temp2LocalContext.setSource(tempDbConfiguration);
2856 			temp2LocalContext.setResult(result);
2857 		}
2858 	}
2859 
2860 	private Map<RejectedRow.Cause, String> decorateRejectedRows(Map<String, String> rejectedRows) {
2861 
2862 		Map<RejectedRow.Cause, StringBuilder> result = Maps.newHashMap();
2863 
2864 		if (CollectionUtils.isEmpty(rejectedRows.keySet())) {
2865 			return Maps.newHashMap();
2866 		}
2867 
2868 		for (String tableName : rejectedRows.keySet()) {
2869 			for (RejectedRow reject : RejectedRow.parseFromString(rejectedRows.get(tableName))) {
2870 
2871 				String remotePkStr = reject.pkStr;
2872 				String pkStr;
2873 				if (StringUtils.isNotBlank(reject.targetPkStr)) {
2874 					pkStr = reject.targetPkStr;
2875 				}
2876 				else {
2877 					pkStr = remotePkStr; // should never occurred
2878 				}
2879 
2880 				// get the actual string builder or create new one
2881 				StringBuilder sb = result.get(reject.cause);
2882 				if (sb == null) {
2883 					sb = new StringBuilder();
2884 					result.put(reject.cause, sb);
2885 				}
2886 
2887 				// get the object by the table name and id
2888 				try {
2889 					switch (tableName.toUpperCase()) {
2890 					case TABLE_SURVEY: {
2891 						Integer id = Integer.valueOf(pkStr);
2892 						LightSurveyVO survey = surveyDao.getLightSurveyById(id);
2893 						sb.append(decorate(survey));
2894 					}
2895 						break;
2896 					default: {
2897 						sb.append(t("quadrige2.service.synchro.rejection.object", tableName, pkStr));
2898 					}
2899 					}
2900 				} catch (Exception e) {
2901 					log.error("unable to get the object", e);
2902 					sb.append(t("quadrige2.service.synchro.rejection.object", tableName, pkStr));
2903 				}
2904 
2905 				// append date
2906 				if (reject.validUpdateDate != null) {
2907 					sb.append(' ');
2908 					sb.append(t("quadrige2.service.synchro.rejection.BAD_UPDATE_DATE.updateDate", reject.validUpdateDate.toString()));
2909 				}
2910 				sb.append('\n');
2911 
2912 			}
2913 		}
2914 
2915 		return Maps.transformValues(result, new Function<StringBuilder, String>() {
2916 
2917 			@Override
2918 			public String apply(StringBuilder input) {
2919 				return input.toString();
2920 			}
2921 		});
2922 	}
2923 
2924 	/**
2925 	 * Computes a minimum synchronisation date based on BAD_UPDATE_DATE rejected rows.
2926 	 * This is useful to avoid re-synchronizing all data next time.
2927 	 * If there is no BAD_UPDATE_DATE reject, the synchronization date will be returned
2928 	 * 
2929 	 * @param rejectedRows
2930 	 * @param synchroDate
2931 	 *            date of synchronization
2932 	 * @return synchronization date, or minimum update date based on BAD_UPDATE_DATE rejects
2933 	 */
2934 	private Date computeDataSynchronizationDate(Map<String, String> rejectedRows,
2935 			Date synchroDate,
2936 			Date previousSynchroDate) {
2937 
2938 		if (MapUtils.isEmpty(rejectedRows)) {
2939 			return synchroDate;
2940 		} else {
2941 			long minUpdateDate = synchroDate.getTime();
2942 			for (String tableName : rejectedRows.keySet()) {
2943 				for (RejectedRow rejectRow : RejectedRow.parseFromString(rejectedRows.get(tableName))) {
2944 					if (rejectRow.cause == RejectedRow.Cause.BAD_UPDATE_DATE) {
2945 						Timestamp rowUpdateDate = rejectRow.validUpdateDate;
2946 						if (rowUpdateDate != null && rowUpdateDate.getTime() < minUpdateDate) {
2947 							/* 1s before the min, just to make sure the next synchro will retrieve this row */
2948 							minUpdateDate = rowUpdateDate.getTime() - 1000;
2949 						}
2950 					} else {
2951 						// if there are some rejects left (other than UPDATE_DATE), don't update synchro date
2952 						// (keep the previous synchro date)
2953 						log.warn(String.format("Data synchronization date not udpated: rejected rows (other than %s) detected on import: %s",
2954 								RejectedRow.Cause.BAD_UPDATE_DATE,
2955 								rejectRow));
2956 						return previousSynchroDate;
2957 					}
2958 				}
2959 			}
2960 
2961 			return new Date(minUpdateDate);
2962 		}
2963 	}
2964 
2965 	/**
2966 	 * <p>
2967 	 * getSynchroDirectoryByUser.
2968 	 * </p>
2969 	 * 
2970 	 * @param userId
2971 	 *            a int.
2972 	 * @return a {@link java.io.File} object.
2973 	 */
2974 	public File getSynchroDirectoryByUser(int userId) {
2975 		File result = new File(
2976 				config.getSynchronizationDirectory(),
2977 				new StringBuilder()
2978 						.append(userId)
2979 						.toString());
2980 		return result;
2981 	}
2982 
2983 	/**
2984 	 * <p>
2985 	 * checkValidImportFile.
2986 	 * </p>
2987 	 * 
2988 	 * @param file
2989 	 *            a {@link java.io.File} object.
2990 	 */
2991 	protected void checkValidImportFile(File file) {
2992 		if (!file.exists()) {
2993 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.file.notExists", file.getPath()));
2994 		}
2995 
2996 		if (!ZipUtil.isZipFile(file)) {
2997 			throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.import.file.notZipFile", file.getPath()));
2998 		}
2999 	}
3000 
3001 	private DataSynchroContext createContextAndImportFromFile(int userId,
3002 			File dbDirToImport,
3003 			Multimap<String, String> pkToIncludes,
3004 			Map<String, Map<String, Map<String, Object>>> remapValues,
3005 			boolean forceDuplication,
3006 			ApplicationProgressionModel progressionModel,
3007 			int stepNumber,
3008 			int progressionModelStepCount) {
3009 		return createContextAndImportFromFile(userId,
3010 				dbDirToImport,
3011 				null, /* no changes logs */
3012 				false, /* no rollback only */
3013 				pkToIncludes,
3014 				remapValues,
3015 				forceDuplication,
3016 				t("quadrige2.service.synchro.import.data.message"),
3017 				progressionModel,
3018 				stepNumber,
3019 				progressionModelStepCount);
3020 	}
3021 
3022 	private DataSynchroContext createContextAndImportFromFile(int userId,
3023 			File dbDirToImport,
3024 			File changeLogFile,
3025 			boolean rollbackOnly,
3026 			Multimap<String, String> pkToIncludes,
3027 			Map<String, Map<String, Map<String, Object>>> remapValues,
3028 			boolean forceDuplication,
3029 			String progressionBaseMessage,
3030 			ApplicationProgressionModel progressionModel,
3031 			int stepNumber,
3032 			int progressionModelStepCount) {
3033 
3034 		DataSynchroContext synchroContext = dataSynchroService.createSynchroContext(
3035 				dbDirToImport,
3036 				SynchroDirection.IMPORT_FILE2LOCAL,
3037 				userId,
3038 				null /* all data from Temp DB */,
3039 				false /* ignore deletion */,
3040 				true
3041 				);
3042 
3043 		// Set the change log file (could be null = disable)
3044 		synchroContext.setChangeLogFile(changeLogFile);
3045 
3046 		// pk to includes (could be null = all)
3047 		synchroContext.setPkIncludes(pkToIncludes);
3048 
3049 		// Replace column values
3050 		synchroContext.setRemapValues(remapValues);
3051 
3052 		// set force duplication
3053 		synchroContext.setForceDuplication(forceDuplication);
3054 
3055 		// Set if transaction must be mark as rollback only
3056 		synchroContext.getTarget().setRollbackOnly(rollbackOnly);
3057 
3058 		// give the force override option to context
3059 		synchroContext.setForceEditedRowOverride(false);
3060 
3061 		doImportData(synchroContext,
3062 				progressionBaseMessage,
3063 				progressionModel,
3064 				stepNumber,
3065 				progressionModelStepCount);
3066 
3067 		return synchroContext;
3068 	}
3069 
3070 	private void finishExportDataToFile(SynchroClientExportToFileResult exportResult) {
3071 		Preconditions.checkNotNull(exportResult.getDataResult());
3072 
3073 		// execute the last step
3074 		SynchroResult result = exportResult.getDataResult();
3075 		DataSynchroContext dataSynchroContext = exportResult.getDataContext();
3076 		SynchroDatabaseConfiguration target = dataSynchroContext.getTarget();
3077 		dataSynchroContext.setTarget(dataSynchroContext.getSource());
3078 		dataSynchroContext.setSource(null);
3079 
3080 		// finalize (will restore the dataSynchroContext: result, source and target)
3081 		try {
3082 			SynchroResult tempResult = new SynchroResult();
3083 			dataSynchroContext.setResult(tempResult);
3084 
3085 			// Beware, synchro context used for finalization must have no source and its target should be the local
3086 			// database
3087 			Map<RejectedRow.Cause, RejectedRow.ResolveStrategy> rejectedRowStrategy =
3088 					ImmutableMap.<RejectedRow.Cause, RejectedRow.ResolveStrategy> builder()
3089 							.put(RejectedRow.Cause.DUPLICATE_KEY, RejectedRow.ResolveStrategy.DO_NOTHING)
3090 							.put(RejectedRow.Cause.DELETED, RejectedRow.ResolveStrategy.DO_NOTHING)
3091 							.put(RejectedRow.Cause.BAD_UPDATE_DATE, RejectedRow.ResolveStrategy.DO_NOTHING)
3092 							.build();
3093 			dataSynchroService.finish(dataSynchroContext, result, rejectedRowStrategy);
3094 
3095 			if (!tempResult.isSuccess()) {
3096 				// put back the error into the original result
3097 				result.setError(tempResult.getError());
3098 				throw new Quadrige2TechnicalException(t("quadrige2.error.synchro.export.finish"), tempResult.getError());
3099 			}
3100 
3101 			// Append result from finish() into original result
3102 			result.getRejectedRows().clear();
3103 			result.getSourceMissingUpdates().clear();
3104 			result.getSourceMissingDeletes().clear();
3105 			result.getRejectedRows().putAll(tempResult.getRejectedRows());
3106 
3107 		} finally {
3108 			// restore the original context
3109 			dataSynchroContext.setResult(result);
3110 			dataSynchroContext.setSource(dataSynchroContext.getTarget());
3111 			dataSynchroContext.setTarget(target);
3112 		}
3113 	}
3114 
3115 	/**
3116 	 * <p>
3117 	 * Getter for the field <code>decoratorService</code>.
3118 	 * </p>
3119 	 * 
3120 	 * @return a {@link fr.ifremer.quadrige2.core.service.decorator.DecoratorService} object.
3121 	 */
3122 	protected DecoratorService getDecoratorService() {
3123 		return decoratorService;
3124 	}
3125 
3126 	/**
3127 	 * <p>
3128 	 * Getter for the field <code>config</code>.
3129 	 * </p>
3130 	 * 
3131 	 * @return a {@link fr.ifremer.quadrige2.core.config.Quadrige2Configuration} object.
3132 	 */
3133 	protected Quadrige2Configuration getConfig() {
3134 		return config;
3135 	}
3136 
3137 	/**
3138 	 * <p>
3139 	 * newReferentialSynchroRejectedRowResolver.
3140 	 * </p>
3141 	 * 
3142 	 * @return a {@link fr.ifremer.quadrige2.synchro.service.client.SynchroRejectedRowResolver} object.
3143 	 */
3144 	protected SynchroRejectedRowResolver newReferentialSynchroRejectedRowResolver() {
3145 		// A basic resolver, that remap all duplicated entries found in referential
3146 		return new SynchroRejectedRowResolver() {
3147 			@Override
3148 			public void showRejectMessage(Map<RejectedRow.Cause, String> rejectedRowsByCause, RejectedRow.Cause causeFilter, boolean failMessage) {
3149 				// Do nothing
3150 			}
3151 
3152 			@Override
3153 			public RejectedRow.ResolveStrategy resolveReject(RejectedRow.Cause rejectCause, String rejectInfos, String rejectMessage) {
3154 				if (rejectCause == RejectedRow.Cause.DUPLICATE_KEY) {
3155 					return RejectedRow.ResolveStrategy.KEEP_LOCAL;
3156 				}
3157 				return RejectedRow.ResolveStrategy.DO_NOTHING;
3158 			}
3159 		};
3160 	}
3161 
3162 	/**
3163 	 * <p>
3164 	 * getDataRemapValuesFromReferentialResult.
3165 	 * </p>
3166 	 * 
3167 	 * @param referentialResult
3168 	 *            a {@link fr.ifremer.common.synchro.service.SynchroResult} object.
3169 	 * @return a {@link java.util.Map} object.
3170 	 */
3171 	protected Map<String, Map<String, Map<String, Object>>> getDataRemapValuesFromReferentialResult(SynchroResult referentialResult) {
3172 		if (referentialResult == null || MapUtils.isEmpty(referentialResult.getSourceMissingUpdates())) {
3173 			return null;
3174 		}
3175 
3176 		final Set<String> dataTableIncludes = DataSynchroTables.getImportTablesIncludes();
3177 
3178 		// Reuse source missing updates, but limit to data tables
3179 		Map<String, Map<String, Map<String, Object>>> result = Maps.filterKeys(referentialResult.getSourceMissingUpdates(),
3180 				new Predicate<String>() {
3181 					@Override
3182 					public boolean apply(@Nullable String tableName) {
3183 						return dataTableIncludes.contains(tableName);
3184 					}
3185 				});
3186 
3187 		return result;
3188 	}
3189 
3190 	/**
3191 	 * If deletion error, display a special message (mantis #23134)
3192 	 * 
3193 	 * @param deleteException
3194 	 *            the exception
3195 	 * @param target
3196 	 *            the target database configuration
3197 	 */
3198 	private void handleDeleteException(DataIntegrityViolationOnDeleteException deleteException, SynchroDatabaseConfiguration target) {
3199 
3200 		String tableName = deleteException.getTableName();
3201 		String pkStr = deleteException.getPkStr();
3202 		String i18nTableName = decorate(tableName, DecoratorService.TABLE_NAME);
3203 
3204 		String i18nRow = null;
3205 		// Translate the row in error, using table name and PK
3206 		if (StringUtils.isNoneBlank(tableName) && StringUtils.isNoneBlank(pkStr) && SynchroTableMetadata.fromPkStr(pkStr).size() == 1) {
3207 			ReferentialJdbcDao referentialJdbcDao = new ReferentialJdbcDaoImpl(target.getConnectionProperties());
3208 			try {
3209 				Object entity = referentialJdbcDao.getVOByTableNameAndPk(deleteException.getTableName(), pkStr);
3210 				if (entity != null) {
3211 					i18nRow = decorate(entity);
3212 					if (StringUtils.isBlank(tableName)) {
3213 						i18nRow = String.format("%s (pk=%s)", tableName, pkStr);
3214 					}
3215 				}
3216 			} catch (Quadrige2TechnicalException e) {
3217 				// If DAO has no VO for this table name, then reset the row details
3218 				i18nRow = null;
3219 			}
3220 		}
3221 
3222 		// Default message
3223 		if (i18nTableName != null && i18nRow != null) {
3224 			throw new Quadrige2BusinessException(t("quadrige2.error.synchro.import.synchro.delete.details",
3225 					i18nTableName,
3226 					i18nRow),
3227 					deleteException);
3228 		} else {
3229 			throw new Quadrige2BusinessException(t("quadrige2.error.synchro.import.synchro.delete",
3230 					deleteException.getMessage()));
3231 		}
3232 
3233 	}
3234 
3235 	/**
3236 	 * Used to inverse target/source pk, in result. This is need to be used in finish() always as an importation.
3237 	 * See ObsDeb mantis #29874
3238 	 * 
3239 	 * @param result
3240 	 */
3241 	private void inverseRejectsSourceAndTargetPks(SynchroResult result) {
3242 
3243 		Map<String, String> rejectedRows = result.getRejectedRows();
3244 
3245 		Map<String, String> inversedRejectedRows = Maps.newTreeMap();
3246 
3247 		if (CollectionUtils.isNotEmpty(rejectedRows.keySet())) {
3248 
3249 			for (String tableName : rejectedRows.keySet()) {
3250 
3251 				String rejectLines = rejectedRows.get(tableName);
3252 
3253 				// split line
3254 				for (RejectedRow rejectedRow : RejectedRow.parseFromString(rejectLines)) {
3255 					if (rejectedRow.targetPkStr == null) {
3256 						throw new Quadrige2TechnicalException(String.format(
3257 								"Invalid reject data [%s]: missing 'targetPkStr'. Could process this reject.", rejectLines));
3258 					}
3259 
3260 					rejectedRow.inverse();
3261 
3262 					RejectedRow.appendAsString(inversedRejectedRows, tableName, rejectedRow.toString());
3263 				}
3264 			}
3265 		}
3266 
3267 		rejectedRows.clear();
3268 		rejectedRows.putAll(inversedRejectedRows);
3269 	}
3270 
3271 }