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.gson.reflect.TypeToken;
27  import fr.ifremer.common.synchro.service.SynchroResult;
28  import fr.ifremer.quadrige3.core.ProgressionCoreModel;
29  import fr.ifremer.quadrige3.core.config.QuadrigeConfiguration;
30  import fr.ifremer.quadrige3.core.dao.technical.Assert;
31  import fr.ifremer.quadrige3.core.dao.technical.Files;
32  import fr.ifremer.quadrige3.core.dao.technical.gson.Gsons;
33  import fr.ifremer.quadrige3.core.exception.*;
34  import fr.ifremer.quadrige3.core.security.AuthenticationInfo;
35  import fr.ifremer.quadrige3.core.service.http.HttpService;
36  import fr.ifremer.quadrige3.core.vo.administration.program.ProgramVO;
37  import fr.ifremer.quadrige3.core.vo.administration.user.QuserVO;
38  import fr.ifremer.quadrige3.core.vo.data.survey.CampaignVO;
39  import fr.ifremer.quadrige3.core.vo.system.generalCondition.GeneralConditionVO;
40  import fr.ifremer.quadrige3.core.vo.system.rule.RuleListVO;
41  import fr.ifremer.quadrige3.synchro.vo.SynchroExportContextVO;
42  import fr.ifremer.quadrige3.synchro.vo.SynchroImportContextVO;
43  import fr.ifremer.quadrige3.synchro.vo.SynchroProgressionStatus;
44  import fr.ifremer.quadrige3.synchro.vo.SynchroProgressionVO;
45  import org.apache.commons.collections4.CollectionUtils;
46  import org.apache.commons.io.FileUtils;
47  import org.apache.commons.io.FilenameUtils;
48  import org.apache.commons.lang3.StringUtils;
49  import org.apache.commons.lang3.time.DateUtils;
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  import org.apache.http.client.methods.HttpGet;
53  import org.apache.http.client.methods.HttpPost;
54  import org.apache.http.entity.ContentType;
55  import org.apache.http.entity.StringEntity;
56  import org.nuiton.i18n.I18n;
57  import org.nuiton.version.Version;
58  import org.nuiton.version.Versions;
59  import org.springframework.context.annotation.Lazy;
60  import org.springframework.stereotype.Service;
61  
62  import javax.annotation.Resource;
63  import java.io.File;
64  import java.io.IOException;
65  import java.lang.reflect.Type;
66  import java.nio.file.Path;
67  import java.util.*;
68  
69  import static org.nuiton.i18n.I18n.t;
70  
71  /**
72   * <p>
73   * SynchroRestClientServiceImpl class.
74   * </p>
75   */
76  @Service("synchroRestClientService")
77  @Lazy
78  public class SynchroRestClientServiceImpl implements SynchroRestClientService {
79  
80      private static final Log log = LogFactory.getLog(SynchroRestClientServiceImpl.class);
81  
82      private static final String URL_USER_CURRENT = "/service/user/current";
83      private static final String URL_USER_READABLE_PROGRAMS = "/service/user/readablePrograms";
84      private static final String URL_USER_WRITABLE_PROGRAMS = "/service/user/writablePrograms";
85      private static final String URL_PROGRAM_GET = "/service/program/get/%s";
86      private static final String URL_PROGRAM_COMPATIBLE_CODES = "/service/program/compatibleProgramCodes";
87      private final static String URL_CHECK_VERSION = "/service/version/check/%s";
88      private final static String URL_GET_VERSION = "/service/version/get";
89      private final static String URL_IMPORT_CHECK_ANONYMOUSLY = "/service/import/check/anonymous";
90      private final static String URL_IMPORT_CHECK = "/service/import/check";
91      private final static String URL_IMPORT_CHECK_DATA = "/service/import/check/data";
92      private final static String URL_IMPORT_STATUS = "/service/import/status/%s";
93      private final static String URL_IMPORT_START = "/service/import/start";
94      private final static String URL_IMPORT_START_ANONYMOUSLY = "/service/import/start/anonymous";
95      private final static String URL_IMPORT_STOP = "/service/import/stop/%s";
96      private final static String URL_IMPORT_ACKNOWLEDGE = "/service/import/acknowledge";
97      private static final String URL_IMPORT_REFERENTIAL_UPDATE_DATE = "/service/import/referential/updateDate";
98      private static final String URL_IMPORT_REFERENTIAL_UPDATE_DATE_CLEAR_CACHE = "/service/import/referential/updateDate/clear";
99      private static final String URL_IMPORT_PHOTO = "/service/import/photo/%s";
100     private static final String URL_EXPORT_UPLOAD = "/service/export/upload";
101     private final static String URL_EXPORT_STATUS = "/service/export/status/%s";
102     private final static String URL_EXPORT_START = "/service/export/start";
103     private final static String URL_EXPORT_STOP = "/service/export/stop/%s";
104     private final static String URL_EXPORT_FILES = "/service/export/files/%s";
105     private static final String URL_EXPORT_ACKNOWLEDGE = "/service/export/acknowledge";
106     private static final String URL_PROGRAM_SAVE_LIST = "/service/program/save/list/";
107     private static final String URL_CAMPAIGN_SAVE_LIST = "/service/campaign/save/list/";
108     private static final String URL_CAMPAIGN_DELETE_LIST = "/service/campaign/delete/list/";
109     private static final String URL_RULE_LIST_SAVE_LIST = "/service/ruleList/save/list/";
110     private static final String URL_RULE_LIST_DELETE_LIST = "/service/ruleList/delete/list/";
111     private static final String URL_RULE_LIST_GET = "/service/ruleList/get/%s";
112     private static final String URL_GENERAL_CONDITION_GET = "/service/generalCondition/lastNonAccepted";
113     private static final String URL_GENERAL_CONDITION_ACCEPT = "/service/generalCondition/accept";
114 
115     @Resource
116     private QuadrigeConfiguration configuration;
117 
118     @Resource(name = "httpService")
119     private HttpService httpService;
120 
121     /**
122      * {@inheritDoc}
123      */
124     @Override
125     public QuserVO getUser(AuthenticationInfo authenticationInfo) {
126 
127         QuserVO result;
128 
129         if (log.isDebugEnabled()) {
130             log.debug(String.format("Try to retrieve authenticated user data, from the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
131         }
132 
133         try {
134             HttpGet request = new HttpGet(httpService.getPathUri(URL_USER_CURRENT));
135             result = httpService.executeRequest(authenticationInfo, request, QuserVO.class);
136         } catch (QuadrigeTechnicalException e) {
137             log.error(I18n.t("quadrige3.error.remote.currentPerson.failed", e.getMessage()), e);
138             throw e;
139         }
140 
141         return result;
142     }
143 
144     @Override
145     public List<ProgramVO> getReadableProgramsForUser(AuthenticationInfo authenticationInfo) throws QuadrigeTechnicalException {
146         List<ProgramVO> result;
147 
148         if (log.isDebugEnabled()) {
149             log.debug(String.format("Try to retrieve readable programs of authenticated user, from the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
150         }
151 
152         try {
153             HttpGet request = new HttpGet(httpService.getPathUri(URL_USER_READABLE_PROGRAMS));
154             Type listType = new TypeToken<ArrayList<ProgramVO>>() {
155             }.getType();
156             result = httpService.executeRequest(authenticationInfo, request, listType);
157         } catch (QuadrigeTechnicalException e) {
158             log.error(I18n.t("quadrige3.error.remote.programs.get.failed", e.getMessage()), e);
159             throw e;
160         }
161         return result;
162     }
163 
164     /**
165      * {@inheritDoc}
166      */
167     @Override
168     public List<ProgramVO> getWritableProgramsForUser(AuthenticationInfo authenticationInfo) {
169 
170         List<ProgramVO> result;
171 
172         if (log.isDebugEnabled()) {
173             log.debug(String.format("Try to retrieve writable programs of authenticated user, from the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
174         }
175 
176         try {
177             HttpGet request = new HttpGet(httpService.getPathUri(URL_USER_WRITABLE_PROGRAMS));
178             Type listType = new TypeToken<ArrayList<ProgramVO>>() {
179             }.getType();
180             result = httpService.executeRequest(authenticationInfo, request, listType);
181         } catch (QuadrigeTechnicalException e) {
182             log.error(I18n.t("quadrige3.error.remote.programs.get.failed", e.getMessage()), e);
183             throw e;
184         }
185         return result;
186     }
187 
188     @Override
189     public Set<String> getCompatibleProgramCodes(AuthenticationInfo authenticationInfo) throws QuadrigeTechnicalException {
190         Set<String> result;
191 
192         if (log.isDebugEnabled()) {
193             log.debug(String.format("Try to retrieve compatible programs from the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
194         }
195 
196         try {
197             HttpGet request = new HttpGet(httpService.getPathUri(URL_PROGRAM_COMPATIBLE_CODES));
198             Type listType = new TypeToken<LinkedHashSet<String>>() {
199             }.getType();
200             result = httpService.executeRequest(authenticationInfo, request, listType);
201         } catch (QuadrigeTechnicalException e) {
202             log.error(I18n.t("quadrige3.error.remote.programs.compatible.get.failed", e.getMessage()), e);
203             throw e;
204         }
205         return result;
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     @Override
212     public ProgramVO getProgramByCode(AuthenticationInfo authenticationInfo, String progCd) {
213         Assert.notNull(progCd);
214 
215         if (log.isDebugEnabled()) {
216             log.debug(String.format("Try to retrieve program [%s], from the synchronization server [%s]", progCd, configuration.getSynchronizationSiteUrl()));
217         }
218 
219         try {
220             HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_PROGRAM_GET, progCd)));
221             return httpService.executeRequest(authenticationInfo, request, ProgramVO.class);
222         } catch (QuadrigeTechnicalException e) {
223             log.error(I18n.t("quadrige3.error.remote.program.get.failed", progCd, e.getMessage()), e);
224             throw e;
225         }
226     }
227 
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     public SynchroImportContextVO checkImportContext(AuthenticationInfo authenticationInfo, SynchroImportContextVO importContextVO) {
233         if (log.isDebugEnabled()) {
234             log.debug(String.format("Check if exists referential updates on the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
235         }
236 
237         if (CollectionUtils.isEmpty(importContextVO.getReferentialProgramCodes())) {
238             // Limit programs, if set in config file (if not, server will use his configuration)
239             Set<String> programCodes = configuration.getSynchroProgramCodeIncludes();
240             if (CollectionUtils.isNotEmpty(programCodes)) {
241                 importContextVO.setReferentialProgramCodes(programCodes);
242             }
243         }
244 
245         // Make sure at least one type (referential or data) is set to true
246         // This is need to avoid a server error (something like "invalid context - nothing to synchronize")
247         if (!importContextVO.isWithData() && !importContextVO.isWithReferential()) {
248             importContextVO.setWithReferential(true);
249         }
250 
251         boolean isAnonymous = authenticationInfo == null;
252 
253         try {
254             // Check compatibility with server
255             checkVersion(authenticationInfo);
256 
257             // prepare the request
258             HttpPost request = createHttpPostWithStringEntity(isAnonymous ? URL_IMPORT_CHECK_ANONYMOUSLY : URL_IMPORT_CHECK, importContextVO);
259 
260             // execute request
261             return httpService.executeRequest(authenticationInfo, request, SynchroImportContextVO.class);
262 
263         } catch (QuadrigeTechnicalException e) {
264             log.error(I18n.t("quadrige3.error.remote.checkImportContext.failed", e.getMessage()), e);
265             throw e;
266         }
267     }
268 
269     @Override
270     public SynchroImportContextVO checkImportDataContext(AuthenticationInfo authenticationInfo, SynchroImportContextVO importContextVO) throws QuadrigeTechnicalException {
271         if (log.isDebugEnabled()) {
272             log.debug(String.format("Check if exists data updates on the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
273         }
274 
275         // Make sure data is set to true
276         importContextVO.setWithData(true);
277 
278         try {
279             // Check compatibility with server
280             checkVersion(authenticationInfo);
281 
282             // prepare the request
283             HttpPost request = createHttpPostWithStringEntity(URL_IMPORT_CHECK_DATA, importContextVO);
284 
285             // execute request
286             return httpService.executeRequest(authenticationInfo, request, SynchroImportContextVO.class);
287         } catch (QuadrigeTechnicalException e) {
288             log.error(I18n.t("quadrige3.error.remote.checkImportContext.failed", e.getMessage()), e);
289             throw e;
290         }
291     }
292 
293     /**
294      * {@inheritDoc}
295      *
296      * @param authenticationInfo
297      */
298     @Override
299     public void checkVersion(AuthenticationInfo authenticationInfo) {
300 
301         // Read local version (from quadrige3, not the schemaVersionIfUpdate - see mantis #25340)
302         Version localVersionToCheck = QuadrigeConfiguration.getInstance().getVersion();
303 
304         if (log.isDebugEnabled()) {
305             log.debug(String.format("Checking if version [%s] is compatible with the synchronization server [%s]", localVersionToCheck.getVersion(),
306                 configuration.getSynchronizationSiteUrl()));
307         }
308 
309         // get the version of the server
310         HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_CHECK_VERSION, localVersionToCheck.getVersion())));
311         String isVersionAllowedStr = httpService.executeRequest(authenticationInfo, request, String.class);
312         boolean isVersionAllowed = Boolean.valueOf(isVersionAllowedStr);
313 
314         // If version not allowed by server
315         if (!isVersionAllowed) {
316 
317             // get the version of the server
318             request = new HttpGet(httpService.getPathUri(URL_GET_VERSION));
319             String serverVersionStr = httpService.executeRequest(authenticationInfo, request, String.class);
320             Version serverVersion = null;
321             if (StringUtils.isNotBlank(serverVersionStr)) {
322                 serverVersion = Versions.valueOf(serverVersionStr.replaceAll("\"", ""));
323             }
324 
325             // Fail with an error message
326             throw new QuadrigeBusinessException(I18n.t("quadrige3.error.synchro.version", localVersionToCheck, serverVersion));
327         }
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     @Override
334     public SynchroProgressionVO startImport(AuthenticationInfo authenticationInfo,
335                                             SynchroImportContextVO importContextVO,
336                                             ProgressionCoreModel progressionModel) {
337         Assert.notNull(importContextVO);
338 
339         String jobId = importContextVO.getJobId();
340         boolean previouslyLaunched = jobId != null;
341 
342         boolean isAnonymous = authenticationInfo == null;
343 
344         try {
345             progressionModel.setMessage(t("quadrige3.synchro.progress.start"));
346 
347             // check version compatibility
348             checkVersion(authenticationInfo);
349 
350             if (!previouslyLaunched) {
351                 // prepare the request
352                 HttpPost request = createHttpPostWithStringEntity(isAnonymous ? URL_IMPORT_START_ANONYMOUSLY : URL_IMPORT_START, importContextVO);
353 
354                 // execute request
355                 SynchroImportContextVO resultImportContextVO = httpService.executeRequest(authenticationInfo, request, SynchroImportContextVO.class);
356                 jobId = resultImportContextVO.getJobId();
357 
358                 // refresh source import context
359                 importContextVO.setJobId(jobId);
360             }
361 
362             // wait a while (very short wait to avoid exception if job is already finished before timeout, Mantis #44513)
363             Thread.sleep(50);
364 
365             return getImportLastStatus(authenticationInfo, jobId, progressionModel);
366 
367         } catch (QuadrigeTechnicalException e) {
368             log.error(I18n.t("quadrige3.error.remote.startImport.failed", e.getMessage()), e);
369             throw e;
370         } catch (Exception e) {
371             log.error(I18n.t("quadrige3.error.remote.startImport.failed", e.getMessage()), e);
372             throw new QuadrigeTechnicalException(e);
373         }
374     }
375 
376     /**
377      * {@inheritDoc}
378      */
379     @Override
380     public void stopImport(AuthenticationInfo authenticationInfo, String importJobId) {
381         if (log.isDebugEnabled()) {
382             log.debug(String.format("try to stop synchronization job [%s] on servers", importJobId));
383         }
384 
385         // prepare the request
386         HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_IMPORT_STOP, importJobId)));
387 
388         // execute stop request
389         httpService.executeRequest(authenticationInfo, request);
390 
391     }
392 
393     /**
394      * {@inheritDoc}
395      */
396     @Override
397     public void acknowledgeImport(AuthenticationInfo authenticationInfo, SynchroImportContextVO importContextVO) throws QuadrigeTechnicalException {
398 
399         if (log.isDebugEnabled()) {
400             log.debug(String.format("Sending import acknowledge to the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
401         }
402 
403         // Remove the extra offset from referential date (added from SynchroClientService)
404         if (importContextVO.getReferentialUpdateDate() != null) {
405             importContextVO = (SynchroImportContextVO) importContextVO.clone();
406             importContextVO.setReferentialUpdateDate(DateUtils.addSeconds(importContextVO.getReferentialUpdateDate(), configuration.getImportReferentialUpdateDateOffsetInSecond()));
407         }
408 
409         try {
410 
411             // prepare the request
412             HttpPost request = createHttpPostWithStringEntity(URL_IMPORT_ACKNOWLEDGE, importContextVO);
413 
414             // execute request
415             httpService.executeRequest(authenticationInfo, request);
416 
417         } catch (QuadrigeTechnicalException e) {
418             log.error(I18n.t("quadrige3.error.remote.acknowledgeImport.failed", e.getMessage()), e);
419             // If error, be quiet ! (don't care if server could not delete its files)
420         }
421     }
422 
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public void uploadExportFile(AuthenticationInfo authenticationInfo, File fileToUpload, ProgressionCoreModel progressionModel)
428         throws QuadrigeTechnicalException {
429 
430         if (!fileToUpload.exists()) {
431             throw new QuadrigeBusinessException(I18n.t("quadrige3.error.file.not.exists", fileToUpload.getAbsolutePath()));
432         }
433 
434         // Get file size
435         long fileSize = fileToUpload.length();
436         if (fileSize == 0L) {
437             throw new QuadrigeBusinessException(I18n.t("quadrige3.error.file.size.invalid", fileToUpload.getAbsolutePath()));
438         }
439         long maxUploadSize = configuration.getExportDataFileMaxUploadSize();
440 
441         if (log.isDebugEnabled()) {
442             log.debug(String.format("file to transfer to server: %s", fileToUpload.getAbsolutePath()));
443             if (fileSize > maxUploadSize)
444                 log.debug(String.format("file will be split : file size is %s and configured max upload size is %s", fileSize, maxUploadSize));
445         }
446 
447         List<Path> filesToUpload;
448         String progressionMessage = progressionModel.getMessage();
449         try {
450             // Split the file into pieces of maxUploadSize - 1000
451             // note: remove 1000 bytes by precaution because request total size is taken in account in multipart resolver size limitation)
452             filesToUpload = Files.splitFile(fileToUpload.toPath(), maxUploadSize - 1000);
453         } catch (IOException e) {
454             log.error(I18n.t("quadrige3.error.file.split.failed", e.getMessage()), e);
455             throw new QuadrigeTechnicalException(e);
456         }
457         boolean multipleFiles = filesToUpload.size() > 1;
458 
459         try {
460             int nFile = 0;
461             // send split files
462             for (Path file : filesToUpload) {
463 
464                 if (multipleFiles)
465                     progressionModel.setMessage(String.format("%s (%s/%s)", progressionMessage, ++nFile, filesToUpload.size()));
466 
467                 // prepare the request
468                 HttpPost uploadHttpPost = new HttpPost(httpService.getPathUri(URL_EXPORT_UPLOAD));
469 
470                 // execute request
471                 httpService.executeUploadFileRequest(authenticationInfo, uploadHttpPost, progressionModel, file.toFile(), null);
472 
473             }
474         } catch (QuadrigeTechnicalException e) {
475             log.error(I18n.t("quadrige3.error.remote.upload.failed", e.getMessage()), e);
476             throw e;
477         }
478 
479         if (multipleFiles) {
480             Files.deleteQuietly(filesToUpload);
481         }
482     }
483 
484     /**
485      * {@inheritDoc}
486      */
487     @Override
488     public SynchroProgressionVO startExport(AuthenticationInfo authenticationInfo,
489                                             SynchroExportContextVO exportContextVO,
490                                             ProgressionCoreModel progressionModel) {
491         Assert.notNull(exportContextVO);
492 
493         try {
494             progressionModel.setMessage(t("quadrige3.synchro.progress.start"));
495 
496             // check version compatibility
497             checkVersion(authenticationInfo);
498 
499             // prepare the start request
500             HttpPost request = createHttpPostWithStringEntity(URL_EXPORT_START, exportContextVO);
501 
502             // execute request
503             SynchroProgressionVO result = httpService.executeRequest(authenticationInfo, request, SynchroProgressionVO.class);
504 
505             progressionModel.setMessage(result.getMessage());
506             progressionModel.setCurrent(result.getIncrement());
507 
508             return result;
509         } catch (QuadrigeTechnicalException e) {
510             log.error(I18n.t("quadrige3.error.remote.startExport.failed", e.getMessage()), e);
511             throw e;
512         }
513     }
514 
515     /**
516      * {@inheritDoc}
517      */
518     @Override
519     public void stopExport(AuthenticationInfo authenticationInfo, String exportJobId) {
520 
521         HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_EXPORT_STOP, exportJobId)));
522 
523         // execute stop request
524         httpService.executeRequest(authenticationInfo, request);
525 
526     }
527 
528     /**
529      * {@inheritDoc}
530      */
531     @Override
532     public SynchroResult downloadExportResult(AuthenticationInfo authenticationInfo,
533                                               SynchroExportContextVO context,
534                                               ProgressionCoreModel progressionModel) throws Exception {
535 
536         File exportDir = configuration.getTempDirectory();
537         FileUtils.forceMkdir(exportDir);
538         String fileName = FilenameUtils.getBaseName(context.getFileName()) + ".json";
539         File downloadedFile = new File(exportDir, fileName);
540 
541         progressionModel.setMessage(t("quadrige3.synchro.progress.download"));
542 
543         // download the json file
544         HttpGet downloadHttpGet = new HttpGet(httpService.getPathUri(String.format(URL_EXPORT_FILES, fileName)));
545         httpService.executeDownloadFileRequest(authenticationInfo, downloadHttpGet, progressionModel, downloadedFile);
546 
547         SynchroResult result = null;
548 
549         if (downloadedFile.exists()) {
550             result = Gsons.deserializeFile(downloadedFile, SynchroResult.class);
551             FileUtils.deleteQuietly(downloadedFile);
552 
553             if (result == null) {
554                 throw new QuadrigeTechnicalException(t("quadrige3.error.synchro.export.resultFile.read", downloadedFile.getAbsolutePath()));
555             }
556         }
557 
558         return result;
559     }
560 
561     @Override
562     public boolean downloadPhoto(AuthenticationInfo authenticationInfo, int photoRemoteId, String targetFile, ProgressionCoreModel progressionModel) throws Exception {
563 
564         File downloadedPhoto = new File(targetFile);
565         Assert.isTrue(downloadedPhoto.isAbsolute(), "targetFile parameter must be an absolute pathname");
566         if (downloadedPhoto.exists()) {
567             log.warn(String.format("the photo %s already exists, it will be overridden", targetFile));
568         }
569 
570         // download the photo file
571         HttpGet downloadHttpGet = new HttpGet(httpService.getPathUri(String.format(URL_IMPORT_PHOTO, photoRemoteId)));
572         httpService.executeDownloadFileRequest(authenticationInfo, downloadHttpGet, progressionModel, downloadedPhoto);
573 
574         return downloadedPhoto.exists();
575     }
576 
577     /**
578      * {@inheritDoc}
579      */
580     @Override
581     public void acknowledgeExport(AuthenticationInfo authenticationInfo, SynchroExportContextVO context) {
582         try {
583 
584             // check version compatibility
585             checkVersion(authenticationInfo);
586 
587             // send the acknowledge request
588             HttpPost startHttpPost = createHttpPostWithStringEntity(URL_EXPORT_ACKNOWLEDGE, context);
589 
590             httpService.executeRequest(authenticationInfo, startHttpPost);
591 
592         } catch (QuadrigeTechnicalException e) {
593             log.error(I18n.t("quadrige3.error.remote.acknowledgeExport.failed", e.getMessage()), e);
594             // If error, be quiet ! (don't care if server could not delete its files)
595         }
596     }
597 
598     /**
599      * {@inheritDoc}
600      */
601     @Override
602     public List<ProgramVO> savePrograms(AuthenticationInfo authenticationInfo, List<ProgramVO> programs) {
603         return executeRemoteUpdateTyped(
604             URL_PROGRAM_SAVE_LIST,
605             I18n.t("quadrige3.error.remote.programs.save"),
606             authenticationInfo,
607             programs,
608             new TypeToken<ArrayList<ProgramVO>>() {
609             },
610             true/*failed if empty response*/);
611     }
612 
613     /**
614      * {@inheritDoc}
615      */
616     @Override
617     public void clearReferentialUpdateDateCache(AuthenticationInfo authenticationInfo) {
618         // build http client
619         try {
620 
621             // create GET request
622             HttpGet request = new HttpGet(httpService.getPathUri(URL_IMPORT_REFERENTIAL_UPDATE_DATE_CLEAR_CACHE));
623 
624             // Execute request
625             httpService.executeRequest(authenticationInfo, request);
626 
627         } catch (QuadrigeTechnicalException e) {
628             log.error(I18n.t("quadrige3.error.remote.referential.clearUpdateDateCache.failed", e.getMessage()), e);
629             throw e;
630         }
631 
632     }
633 
634     @Override
635     public Date getReferentialUpdateDate(AuthenticationInfo authenticationInfo) {
636         // build http client
637         try {
638 
639             // create GET request
640             HttpGet request = new HttpGet(httpService.getPathUri(URL_IMPORT_REFERENTIAL_UPDATE_DATE));
641 
642             // Execute request
643             return httpService.executeRequest(authenticationInfo, request, Date.class);
644 
645         } catch (QuadrigeTechnicalException e) {
646             log.error(I18n.t("quadrige3.error.remote.referential.updateDate.failed", e.getMessage()), e);
647             throw e;
648         }
649     }
650 
651     /**
652      * {@inheritDoc}
653      */
654     @Override
655     public List<CampaignVO> saveCampaigns(AuthenticationInfo authenticationInfo, Collection<CampaignVO> campaigns) {
656         return executeRemoteUpdateTyped(
657             URL_CAMPAIGN_SAVE_LIST,
658             I18n.t("quadrige3.error.remote.campaigns.save"),
659             authenticationInfo,
660             campaigns,
661             new TypeToken<ArrayList<CampaignVO>>() {
662             },
663             true/*failed if empty response*/);
664     }
665 
666     @Override
667     public void deleteCampaigns(AuthenticationInfo authenticationInfo, Collection<Integer> campaignIds) {
668         executeRemoteUpdate(URL_CAMPAIGN_DELETE_LIST,
669             I18n.t("quadrige3.error.remote.campaigns.delete"),
670             authenticationInfo,
671             campaignIds);
672     }
673 
674     @Override
675     public List<RuleListVO> saveRuleLists(AuthenticationInfo authenticationInfo, Collection<RuleListVO> ruleLists) {
676         return executeRemoteUpdateTyped(
677             URL_RULE_LIST_SAVE_LIST,
678             I18n.t("quadrige3.error.remote.ruleLists.save"),
679             authenticationInfo,
680             ruleLists,
681             new TypeToken<ArrayList<RuleListVO>>() {
682             },
683             true/*failed if empty response*/);
684     }
685 
686     @Override
687     public void deleteRuleLists(AuthenticationInfo authenticationInfo, Collection<String> ruleListCds) {
688         executeRemoteUpdate(URL_RULE_LIST_DELETE_LIST,
689             I18n.t("quadrige3.error.remote.ruleLists.delete"),
690             authenticationInfo,
691             ruleListCds);
692     }
693 
694     @Override
695     public RuleListVO getRuleListByCode(AuthenticationInfo authenticationInfo, String ruleListCode) {
696         Assert.notNull(ruleListCode);
697 
698         if (log.isDebugEnabled()) {
699             log.debug(String.format("Try to retrieve ruleList [%s], from the synchronization server [%s]", ruleListCode, configuration.getSynchronizationSiteUrl()));
700         }
701 
702         try {
703 
704             HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_RULE_LIST_GET, ruleListCode)));
705             return httpService.executeRequest(authenticationInfo, request, RuleListVO.class, true);
706 
707         } catch (QuadrigeTechnicalException e) {
708             log.error(I18n.t("quadrige3.error.remote.ruleList.get.failed", ruleListCode, e.getMessage()), e);
709             throw e;
710         }
711 
712     }
713 
714     @Override
715     public GeneralConditionVO getLastNonAcceptedGeneralCondition(AuthenticationInfo authenticationInfo) {
716         if (log.isDebugEnabled()) {
717             log.debug(String.format("Try to retrieve last non-accepted general condition, from the synchronization server [%s]", configuration.getSynchronizationSiteUrl()));
718         }
719 
720         HttpGet request = new HttpGet(httpService.getPathUri(URL_GENERAL_CONDITION_GET));
721         return httpService.executeRequest(authenticationInfo, request, GeneralConditionVO.class, true);
722     }
723 
724     @Override
725     public void acceptGeneralCondition(AuthenticationInfo authenticationInfo, int id) {
726         if (log.isDebugEnabled()) {
727             log.debug(String.format("Try to accept general condition [%s], from the synchronization server [%s]", id, configuration.getSynchronizationSiteUrl()));
728         }
729 
730         HttpPost request = createHttpPostWithStringEntity(URL_GENERAL_CONDITION_ACCEPT, id);
731         httpService.executeRequest(authenticationInfo, request);
732     }
733 
734     @Override
735     public SynchroProgressionVO getExportLastStatus(AuthenticationInfo authenticationInfo,
736                                                     String exportJobId,
737                                                     ProgressionCoreModel progressionModel) throws Exception {
738 
739         SynchroProgressionVO result = null;
740         int refreshTimeout = configuration.getSynchronizationRefreshTimeout();
741 
742         try {
743             HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_EXPORT_STATUS, exportJobId)));
744 
745             progressionModel.setTotal(100);
746 
747             SynchroProgressionStatus status = null;
748             while (status == null || isRunningStatus(status)) {
749 
750                 // execute status request
751                 result = httpService.executeRequest(authenticationInfo, request, SynchroProgressionVO.class);
752 
753                 status = SynchroProgressionStatus.valueOf(result.getStatus());
754 
755                 // update the progress model
756                 progressionModel.setMessage(result.getMessage());
757                 progressionModel.setCurrent(result.getIncrement());
758 
759                 // wait a while
760                 Thread.sleep(refreshTimeout);
761             }
762 
763             return result;
764         } catch (QuadrigeTechnicalException e) {
765             log.error(I18n.t("quadrige3.error.remote.export.status.failed", e.getMessage()), e);
766             throw e;
767         }
768     }
769 
770     /* -- Internal methods -- */
771 
772     /**
773      * <p>
774      * isRunningStatus.
775      * </p>
776      *
777      * @param status a {@link fr.ifremer.quadrige3.synchro.vo.SynchroProgressionStatus} object.
778      * @return a boolean.
779      */
780     private boolean isRunningStatus(SynchroProgressionStatus status) {
781         return status == SynchroProgressionStatus.RUNNING
782                || status == SynchroProgressionStatus.WAITING_EXECUTION;
783     }
784 
785     /**
786      * <p>
787      * getImportLastStatus.
788      * </p>
789      *
790      * @param authenticationInfo a {@link AuthenticationInfo} object.
791      * @param importJobId        a {@link java.lang.String} object.
792      * @param progressionModel   a {@link ProgressionCoreModel} object.
793      * @return a {@link fr.ifremer.quadrige3.synchro.vo.SynchroProgressionVO} object.
794      * @throws java.lang.Exception if any.
795      */
796     private SynchroProgressionVO getImportLastStatus(AuthenticationInfo authenticationInfo,
797                                                      String importJobId,
798                                                      ProgressionCoreModel progressionModel) throws Exception {
799         SynchroProgressionVO result = null;
800         int refreshTimeout = configuration.getSynchronizationRefreshTimeout();
801 
802         try {
803             HttpGet request = new HttpGet(httpService.getPathUri(String.format(URL_IMPORT_STATUS, importJobId)));
804 
805             progressionModel.setTotal(100);
806 
807             SynchroProgressionStatus status = null;
808             String filename = null;
809             while (status == null || isRunningStatus(status)) {
810 
811                 // execute status request
812                 result = httpService.executeRequest(authenticationInfo, request, SynchroProgressionVO.class);
813 
814                 // Save the filename (once)
815                 if (filename == null && StringUtils.isNotBlank(result.getFileName())) {
816                     filename = result.getFileName();
817                 }
818 
819                 status = SynchroProgressionStatus.valueOf(result.getStatus());
820 
821                 // update the progress model
822                 progressionModel.setMessage(result.getMessage());
823                 progressionModel.setCurrent(result.getIncrement());
824 
825                 // wait a while
826                 Thread.sleep(refreshTimeout);
827             }
828 
829             // Set the filename (could have disappeared in the last VO received)
830             result.setFileName(filename);
831 
832             return result;
833         } catch (QuadrigeTechnicalException e) {
834             log.error(I18n.t("quadrige3.error.remote.import.status.failed", e.getMessage()), e);
835             throw e;
836         }
837     }
838 
839     private <R> List<R> executeRemoteUpdateTyped(
840         final String postPath,
841         final String errorMessagePrefix,
842         AuthenticationInfo authenticationInfo,
843         Object data,
844         TypeToken<? extends List<R>> typeToken,
845         boolean checkNotEmptyResult) {
846 
847         List<R> result = executeRemoteUpdate(
848             postPath,
849             errorMessagePrefix,
850             authenticationInfo,
851             data,
852             typeToken.getType()
853         );
854 
855         if (checkNotEmptyResult && CollectionUtils.isEmpty(result)) {
856             log.error(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.emptyResponse"));
857             throw new QuadrigeTechnicalException(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.emptyResponse"));
858         }
859 
860         return result;
861 
862     }
863 
864     private void executeRemoteUpdate(
865         final String postPath,
866         final String logMessagePrefix,
867         AuthenticationInfo authenticationInfo,
868         Object data) {
869 
870         executeRemoteUpdate(postPath, logMessagePrefix, authenticationInfo, data, null/*no result*/);
871     }
872 
873     private <T> T executeRemoteUpdate(
874         final String postPath,
875         final String errorMessagePrefix,
876         AuthenticationInfo authenticationInfo,
877         Object data,
878         Type resultType
879     ) {
880 
881         try {
882 
883             // check version compatibility
884             checkVersion(authenticationInfo);
885 
886             // create POST request
887             HttpPost request = createHttpPostWithStringEntity(postPath, data);
888 
889             // Execute request
890             return httpService.executeRequest(authenticationInfo, request, resultType);
891 
892         } catch (BadUpdateDtException e) {
893             throw new QuadrigeBusinessException(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.badUpdateDate", e.getMessage()), e);
894         } catch (DataLockedException e) {
895             throw new QuadrigeBusinessException(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.dataLocked"), e);
896         } catch (SaveForbiddenException | DeleteForbiddenException e) {
897             // this exception can be handled by client
898             throw e;
899         } catch (Exception e) {
900             log.error(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.failed", e.getMessage()), e);
901             throw new QuadrigeTechnicalException(errorMessagePrefix + " " + I18n.t("quadrige3.error.remote.update.failed", e.getMessage()), e);
902         }
903     }
904 
905     private HttpPost createHttpPostWithStringEntity(
906         final String postPath,
907         Object data) throws DataLockedException {
908 
909         HttpPost request = new HttpPost(httpService.getPathUri(postPath));
910         StringEntity entity = new StringEntity(httpService.getGson().toJson(data), ContentType.APPLICATION_JSON);
911         request.setEntity(entity);
912 
913         return request;
914 
915     }
916 }