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