From 6e03e1af51feae06c2b5020afbfaf3c0bf810c48 Mon Sep 17 00:00:00 2001 From: bugat Date: Tue, 26 Mar 2024 13:30:57 +0100 Subject: [PATCH] =?UTF-8?q?Mont=C3=A9e=20en=20version=202.2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../DigitalLibraryConfiguration.java | 14 +++ .../DescriptionValueRepository.java | 3 + .../DocUnitWorkflowRepositoryCustom.java | 24 ---- .../DocUnitWorkflowRepositoryImpl.java | 114 ++++++++---------- .../pgcn/service/document/DocUnitService.java | 9 -- .../DescriptionValueService.java | 6 + .../csv/CSVToDocUnitConvertService.java | 31 ++++- .../DigitalLibraryDiffusionService.java | 106 +++++++++++----- .../omeka/OmekaRequestHandlerService.java | 2 +- .../service/exchange/omeka/OmekaService.java | 20 +-- .../service/exchange/ssh/SftpService.java | 40 +++++- .../statistics/StatisticsWorkflowService.java | 39 ++++-- .../pgcn/service/user/UserService.java | 17 +-- .../pgcn/service/util/RandomUtil.java | 20 ++- .../service/workflow/WorkflowService.java | 3 +- .../StatisticsWorkflowController.java | 20 ++- .../pgcn/web/rest/train/TrainController.java | 2 +- .../db-changelog-conf-digital-library-03.xml | 17 +++ .../resources/config/liquibase/master.xml | 1 + .../app/numahop/common/validationSrvc.js | 11 +- .../digitalLibraryConfigurationEdit.html | 13 ++ 22 files changed, 335 insertions(+), 179 deletions(-) create mode 100644 src/main/resources/config/liquibase/changelog/db-changelog-conf-digital-library-03.xml diff --git a/pom.xml b/pom.xml index 17e30a7e..dda1e240 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ fr.progilone.numahop numahop - 2.1.1 + 2.2.0 war NumaHOP NumaHOP diff --git a/src/main/java/fr/progilone/pgcn/domain/administration/digitallibrary/DigitalLibraryConfiguration.java b/src/main/java/fr/progilone/pgcn/domain/administration/digitallibrary/DigitalLibraryConfiguration.java index b689b554..dcbb3d2b 100644 --- a/src/main/java/fr/progilone/pgcn/domain/administration/digitallibrary/DigitalLibraryConfiguration.java +++ b/src/main/java/fr/progilone/pgcn/domain/administration/digitallibrary/DigitalLibraryConfiguration.java @@ -75,6 +75,12 @@ public class DigitalLibraryConfiguration extends AbstractDomainObject { @Column(name = "mail") private String mail; + /** + * envoi des fichiers en SFTP + */ + @Column(name = "sftp") + private boolean sftp; + /* Types de fichiers à exporter */ @Column(name = "export_view") private boolean exportView; @@ -239,6 +245,14 @@ public void setDefaultValue(final String defaultValue) { this.defaultValue = defaultValue; } + public boolean isSftp() { + return sftp; + } + + public void setSftp(final boolean sftp) { + this.sftp = sftp; + } + @Override public String toString() { return "DigitalLibraryConfiguration{" + "label='" diff --git a/src/main/java/fr/progilone/pgcn/repository/document/conditionreport/DescriptionValueRepository.java b/src/main/java/fr/progilone/pgcn/repository/document/conditionreport/DescriptionValueRepository.java index 6de526a4..06d837e1 100644 --- a/src/main/java/fr/progilone/pgcn/repository/document/conditionreport/DescriptionValueRepository.java +++ b/src/main/java/fr/progilone/pgcn/repository/document/conditionreport/DescriptionValueRepository.java @@ -1,5 +1,6 @@ package fr.progilone.pgcn.repository.document.conditionreport; +import fr.progilone.pgcn.domain.document.conditionreport.DescriptionProperty; import fr.progilone.pgcn.domain.document.conditionreport.DescriptionValue; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,6 +10,8 @@ public interface DescriptionValueRepository extends JpaRepository findByPropertyIdentifier(String propertyId); + List findByProperty(DescriptionProperty property); + @Modifying void deleteByPropertyIdentifier(String propertyId); } diff --git a/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryCustom.java b/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryCustom.java index fa7a811e..8fe37f73 100644 --- a/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryCustom.java +++ b/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryCustom.java @@ -38,30 +38,6 @@ Page findDocUnitProgressStats(List libraries, LocalDate toDate, Pageable pageable); - /** - * Recherche paginée - * - * @param libraries - * @param projects - * @param pgcnId - * @param states - * @param users - * @param fromDate - * @param toDate - * @return - */ - List findDocUnitProgressStatsPending(List libraries, - List projects, - boolean projetActive, - List lots, - List trains, - String pgcnId, - List states, - final List status, - List users, - LocalDate fromDate, - LocalDate toDate); - /** * Recherche de DocUnitWorkflow * diff --git a/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryImpl.java b/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryImpl.java index eb701552..08b307c6 100644 --- a/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryImpl.java +++ b/src/main/java/fr/progilone/pgcn/repository/workflow/DocUnitWorkflowRepositoryImpl.java @@ -3,7 +3,6 @@ import com.querydsl.core.BooleanBuilder; import com.querydsl.core.Tuple; import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -61,7 +60,8 @@ public Page findDocUnitProgressStats(final List librari final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; - final JPAQuery baseQuery = createQueryToFindDocUnitWorkFlows(libraries, + final JPAQuery baseQuery = createQueryToFindDocUnitWorkFlows(qDocUnitWorkflow, + libraries, projects, projetActive, lots, @@ -80,46 +80,13 @@ public Page findDocUnitProgressStats(final List librari if (pageable != null) { baseQuery.offset(pageable.getOffset()).limit(pageable.getPageSize()); } - // Tri - baseQuery.orderBy(qDocUnitWorkflow.docUnit.pgcnId.asc()); - // Résultats - return new PageImpl<>(baseQuery.fetch(), pageable, total); - } - - @Override - public List findDocUnitProgressStatsPending(final List libraries, - final List projects, - final boolean projetActive, - final List lots, - final List trains, - final String pgcnId, - final List states, - final List status, - final List users, - final LocalDate fromDate, - final LocalDate toDate) { - final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; - final JPAQuery baseQuery = createQueryToFindDocUnitWorkFlows(libraries, - projects, - projetActive, - lots, - trains, - pgcnId, - states, - status, - users, - fromDate, - toDate); - // Tri - baseQuery.orderBy(qDocUnitWorkflow.docUnit.pgcnId.asc()); // Résultats - final List results = baseQuery.fetch(); - - return results; + return new PageImpl<>(baseQuery.fetch(), pageable, total); } - private JPAQuery createQueryToFindDocUnitWorkFlows(final List libraries, + private JPAQuery createQueryToFindDocUnitWorkFlows(final QDocUnitWorkflow qDocUnitWorkflow, + final List libraries, final List projects, final boolean projetActive, final List lots, @@ -130,19 +97,17 @@ private JPAQuery createQueryToFindDocUnitWorkFlows(final List users, final LocalDate fromDate, final LocalDate toDate) { - final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; final QDocUnitState qDocUnitState = QDocUnitState.docUnitState; final QWorkflowModelState qWorkflowModelState = QWorkflowModelState.workflowModelState; final QDocUnit qDocUnit = QDocUnit.docUnit; - final QLibrary qAssociatedLibrary = QLibrary.library; + final QLibrary qAssociatedLibrary = new QLibrary("associatedLibrary"); + final QLibrary qLibrary = QLibrary.library; final QUser qAssociatedUser = QUser.user; final QProject qProject = QProject.project; final QLot qLot = QLot.lot; + final QPhysicalDocument qPhysicalDocument = QPhysicalDocument.physicalDocument; final BooleanBuilder builder = new BooleanBuilder(); - // Droits d'accès - // QueryDSLBuilderUtils.addAccessFilters(builder, qDocUnit.library, qDocUnit.project, libraries, null); - // UD if (StringUtils.isNotBlank(pgcnId)) { builder.and(qDocUnit.pgcnId.like('%' + pgcnId @@ -150,20 +115,18 @@ private JPAQuery createQueryToFindDocUnitWorkFlows(final List createQueryToFindDocUnitWorkFlows(final List createQueryToFindDocUnitWorkFlows(final List createQueryToFindDocUnitWorkFlows(final List findPendingDocUnitWorkflows(final List libr final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; final QDocUnit qDocUnit = QDocUnit.docUnit; + final QLibrary qLibrary = QLibrary.library; + final QProject qProject = QProject.project; final QDocUnitState qDocUnitState = QDocUnitState.docUnitState; final BooleanBuilder builder = new BooleanBuilder(); // Droits d'accès - QueryDSLBuilderUtils.addAccessFilters(builder, qDocUnit.library, qDocUnit.project, libraries, null); + QueryDSLBuilderUtils.addAccessFilters(builder, qLibrary, qProject, libraries, null); // Projets if (CollectionUtils.isNotEmpty(projects)) { - builder.and(qDocUnit.project.identifier.in(projects)); + builder.and(qProject.identifier.in(projects)); } // Lots if (CollectionUtils.isNotEmpty(lots)) { @@ -277,7 +244,14 @@ public List findPendingDocUnitWorkflows(final List libr builder.and(JPAExpressions.select(qDocUnitState).from(qDocUnitState).where(stateBuilder).exists()); // Requête - return queryFactory.selectDistinct(qDocUnitWorkflow).from(qDocUnitWorkflow).innerJoin(qDocUnitWorkflow.docUnit, qDocUnit).fetchJoin().where(builder).fetch(); + return queryFactory.selectDistinct(qDocUnitWorkflow) + .from(qDocUnitWorkflow) + .innerJoin(qDocUnitWorkflow.docUnit, qDocUnit) + .fetchJoin() + .leftJoin(qDocUnit.library, qLibrary) + .leftJoin(qDocUnit.project, qProject) + .where(builder) + .fetch(); } @Override @@ -285,14 +259,16 @@ public List findDocUnitWorkflowsInControl(final List li final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; final QDocUnit qDocUnit = QDocUnit.docUnit; + final QLibrary qLibrary = QLibrary.library; + final QProject qProject = QProject.project; final BooleanBuilder builder = new BooleanBuilder(); // Droits d'accès - QueryDSLBuilderUtils.addAccessFilters(builder, qDocUnit.library, qDocUnit.project, libraries, null); + QueryDSLBuilderUtils.addAccessFilters(builder, qLibrary, qProject, libraries, null); // Projets if (CollectionUtils.isNotEmpty(projects)) { - builder.and(qDocUnit.project.identifier.in(projects)); + builder.and(qProject.identifier.in(projects)); } // Lots if (CollectionUtils.isNotEmpty(lots)) { @@ -308,6 +284,8 @@ public List findDocUnitWorkflowsInControl(final List li final Predicate subQuery = JPAExpressions.select(qDeliveredDocument) .from(qDeliveredDocument) .innerJoin(qDeliveredDocument.digitalDocument, qDigitalDocument) + .leftJoin(qDocUnit.library, qLibrary) + .leftJoin(qDocUnit.project, qProject) .where(subBuilder) .exists(); builder.and(subQuery); @@ -367,14 +345,16 @@ public List getDocUnitsGroupByStatus(final List libraries, fin final QDocUnitWorkflow qDocUnitWorkflow = QDocUnitWorkflow.docUnitWorkflow; final QDocUnitState qDocUnitState = QDocUnitState.docUnitState; final QDocUnit qDocUnit = QDocUnit.docUnit; + final QLibrary qLibrary = QLibrary.library; + final QProject qProject = QProject.project; final BooleanBuilder builder = new BooleanBuilder(); // Droits d'accès - QueryDSLBuilderUtils.addAccessFilters(builder, qDocUnit.library, qDocUnit.project, libraries, null); + QueryDSLBuilderUtils.addAccessFilters(builder, qLibrary, qProject, libraries, null); // Projets if (CollectionUtils.isNotEmpty(projects)) { - builder.and(qDocUnit.project.identifier.in(projects)); + builder.and(qProject.identifier.in(projects)); } // Lots if (CollectionUtils.isNotEmpty(lots)) { @@ -388,6 +368,8 @@ public List getDocUnitsGroupByStatus(final List libraries, fin .from(qDocUnitWorkflow) .innerJoin(qDocUnitWorkflow.docUnit, qDocUnit) .innerJoin(qDocUnitWorkflow.states, qDocUnitState) + .leftJoin(qDocUnit.library, qLibrary) + .leftJoin(qDocUnit.project, qProject) .where(builder) .groupBy(qDocUnitState.discriminator) .stream() @@ -405,6 +387,8 @@ public List findDocUnitWorkflowsForStateControl(final List findDocUnitWorkflowsForStateControl(final List findDocUnitWorkflowsForStateControl(final List getDistinctTypes(final String search, final Integer page, final Integer size) { if (StringUtils.isEmpty(search)) { diff --git a/src/main/java/fr/progilone/pgcn/service/document/conditionreport/DescriptionValueService.java b/src/main/java/fr/progilone/pgcn/service/document/conditionreport/DescriptionValueService.java index d5e1f026..61a06816 100644 --- a/src/main/java/fr/progilone/pgcn/service/document/conditionreport/DescriptionValueService.java +++ b/src/main/java/fr/progilone/pgcn/service/document/conditionreport/DescriptionValueService.java @@ -1,5 +1,6 @@ package fr.progilone.pgcn.service.document.conditionreport; +import fr.progilone.pgcn.domain.document.conditionreport.DescriptionProperty; import fr.progilone.pgcn.domain.document.conditionreport.DescriptionValue; import fr.progilone.pgcn.exception.PgcnValidationException; import fr.progilone.pgcn.exception.message.PgcnError; @@ -30,6 +31,11 @@ public List findAll() { return descriptionValueRepository.findAll(); } + @Transactional(readOnly = true) + public List findByProperty(final DescriptionProperty property) { + return descriptionValueRepository.findByProperty(property); + } + @Transactional(readOnly = true) public List findByPropertyIdentifier(final String propertyId) { return descriptionValueRepository.findByPropertyIdentifier(propertyId); diff --git a/src/main/java/fr/progilone/pgcn/service/exchange/csv/CSVToDocUnitConvertService.java b/src/main/java/fr/progilone/pgcn/service/exchange/csv/CSVToDocUnitConvertService.java index d7f0eb85..2d57706c 100644 --- a/src/main/java/fr/progilone/pgcn/service/exchange/csv/CSVToDocUnitConvertService.java +++ b/src/main/java/fr/progilone/pgcn/service/exchange/csv/CSVToDocUnitConvertService.java @@ -224,7 +224,7 @@ protected List convertCondReport(final CSVRecord record, } else { // get the allowed values DescriptionProperty descProp = descriptionPropertyService.findByIdentifier(property); - List allowedValues = descriptionValueService.findByPropertyIdentifier(descProp.getIdentifier()); + List allowedValues = descriptionValueService.findByProperty(descProp); // if empty list, value is free to this property if (!allowedValues.isEmpty()) { // value have to be in the allowedValues Optional findedVal = allowedValues.stream().filter(val -> StringUtils.equalsIgnoreCase(val.getLabel(), recordValue)).findFirst(); @@ -234,11 +234,11 @@ protected List convertCondReport(final CSVRecord record, desc.setValue(findedVal.get()); condReportValues.add(desc); LOG.warn("Propriété {} mappée", descProp.getLabel()); - } else { + } else if (!checkAndWriteComments(descProp, recordValue, condReportValues)) { LOG.warn("Propriété {} non mappée", descProp.getLabel()); errors.add(builder.reinit().setCode(PgcnErrorCode.CONDREPORT_DETAIL_DESC_NO_VALUE_FOR_PROP).setField("pgcnId").build()); } - } else { + } else if (!checkAndWriteComments(descProp, recordValue, condReportValues)) { LOG.warn("Pas de valeurs associées à la propriété {}", descProp.getLabel()); errors.add(builder.reinit().setCode(PgcnErrorCode.CONDREPORT_DETAIL_DESC_BAD_VALUE).setField("pgcnId").build()); } @@ -253,6 +253,31 @@ protected List convertCondReport(final CSVRecord record, return condReportValues; } + /** + * Check if comments are authorized, if so add it to the description + * + * @param descProp + * @param recordValue + * @param condReportValues + * @return true if comments were added, false otherwise + */ + protected boolean checkAndWriteComments(DescriptionProperty descProp, String recordValue, List condReportValues) { + final Optional confOpt = descProp.getConfigurations() + .stream() + .filter(c -> c.getDescProperty() != null && StringUtils.equals(c.getDescProperty().getIdentifier(), + descProp.getIdentifier())) + .findAny(); + if (confOpt.isPresent() && confOpt.get().isAllowComment() || descProp.isAllowComment()) { + Description desc = new Description(); + desc.setProperty(descProp); + desc.setComment(recordValue); + condReportValues.add(desc); + LOG.warn("Commentaire {} mappé", descProp.getLabel()); + return true; + } + return false; + } + /** * create metadata values on the doc unit * diff --git a/src/main/java/fr/progilone/pgcn/service/exchange/digitallibrary/DigitalLibraryDiffusionService.java b/src/main/java/fr/progilone/pgcn/service/exchange/digitallibrary/DigitalLibraryDiffusionService.java index 1a883618..3d0e2fef 100644 --- a/src/main/java/fr/progilone/pgcn/service/exchange/digitallibrary/DigitalLibraryDiffusionService.java +++ b/src/main/java/fr/progilone/pgcn/service/exchange/digitallibrary/DigitalLibraryDiffusionService.java @@ -1,5 +1,6 @@ package fr.progilone.pgcn.service.exchange.digitallibrary; +import fr.progilone.pgcn.domain.administration.SftpConfiguration; import fr.progilone.pgcn.domain.administration.digitallibrary.DigitalLibraryConfiguration; import fr.progilone.pgcn.domain.administration.viewsformat.ViewsFormatConfiguration; import fr.progilone.pgcn.domain.document.DocPropertyType; @@ -19,11 +20,13 @@ import fr.progilone.pgcn.service.document.ui.UIBibliographicRecordService; import fr.progilone.pgcn.service.exchange.cines.ExportMetsService; import fr.progilone.pgcn.service.exchange.csv.ExportCSVService; +import fr.progilone.pgcn.service.exchange.ssh.SftpService; import fr.progilone.pgcn.service.library.LibraryService; import fr.progilone.pgcn.service.storage.AltoService; import fr.progilone.pgcn.service.storage.BinaryStorageManager; import fr.progilone.pgcn.service.util.CryptoService; import fr.progilone.pgcn.service.util.DateUtils; +import fr.progilone.pgcn.service.util.ImageUtils; import fr.progilone.pgcn.service.workflow.WorkflowService; import jakarta.persistence.EntityNotFoundException; import java.io.File; @@ -44,6 +47,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.slf4j.Logger; @@ -86,6 +90,8 @@ public class DigitalLibraryDiffusionService { private final AltoService altoService; private final LibraryService libraryService; + private final SftpService sftpService; + public DigitalLibraryDiffusionService(final DocUnitService docUnitService, final DigitalLibraryConfigurationService digitalLibraryConfigurationService, final WorkflowService workflowService, @@ -97,7 +103,8 @@ public DigitalLibraryDiffusionService(final DocUnitService docUnitService, final ExportCSVService exportCSVService, final MailService mailService, final AltoService altoService, - final LibraryService libraryService) { + final LibraryService libraryService, + final SftpService sftpService) { this.docUnitService = docUnitService; this.digitalLibraryConfigurationService = digitalLibraryConfigurationService; this.workflowService = workflowService; @@ -110,6 +117,7 @@ public DigitalLibraryDiffusionService(final DocUnitService docUnitService, this.mailService = mailService; this.altoService = altoService; this.libraryService = libraryService; + this.sftpService = sftpService; } @Transactional(readOnly = true) @@ -167,32 +175,19 @@ public boolean exportDocToDigitalLibrary(final DocUnit du, final boolean multipl return exported; } - final File csv; // Génération des fichiers / répertoires MEDIA - csv = exportDocUnit(doc, metaDC, mediasDir, conf, multiple, firstDoc); + final Pair result = exportDocUnit(doc, metaDC, mediasDir, conf, multiple, firstDoc); + final File csv = result.getKey(); + final Path depotPath = result.getValue(); // Tranferts du csv et dossier media if (mediasDir.toFile().exists() && csv.exists()) { - final FTPClient ftpClient = new FTPClient(); - - ftpClient.connect(conf.getAddress(), Integer.parseInt(conf.getPort())); - - final boolean success = ftpClient.login(conf.getLogin(), cryptoService.decrypt(conf.getPassword())); - - if (!success) { - LOG.error("Erreur de connexion ftp lors de l'export sur la Bibliothèque numérique"); - exported = false; + // Envoi des documents en SFTP + if (conf.isSftp() && depotPath.toFile().exists()) { + exported = exportSftp(conf, multiple, lastDoc, csv.toPath(), depotPath); } else { - ftpClient.changeWorkingDirectory(conf.getDeliveryFolder()); - if (!multiple || lastDoc) { - putPathRecursively(csv, ftpClient); - } - LOG.info("Envoi du document {}", mediasDir.getFileName()); - putPathRecursively(mediasDir.toFile(), ftpClient); - exported = true; - ftpClient.logout(); - ftpClient.disconnect(); + exported = exportFtp(conf, multiple, lastDoc, csv, mediasDir); } } @@ -221,12 +216,64 @@ public boolean exportDocToDigitalLibrary(final DocUnit du, final boolean multipl } @Transactional - public File exportDocUnit(final DocUnit docUnit, - final BibliographicRecordDcDTO metaDc, - final Path mediaDir, - final DigitalLibraryConfiguration conf, - final boolean multiple, - final boolean firstDoc) throws IOException, PgcnTechnicalException { + public boolean exportSftp(final DigitalLibraryConfiguration conf, final boolean multiple, final boolean lastDoc, final Path csvPath, final Path mediasDirPath) { + final SftpConfiguration sftpConf = new SftpConfiguration(); + sftpConf.setActive(true); + sftpConf.setHost(conf.getAddress()); + sftpConf.setPort(Integer.valueOf(conf.getPort())); + sftpConf.setUsername(conf.getLogin()); + sftpConf.setPassword(conf.getPassword()); + try { + if (!multiple || lastDoc) { + String rootFolder = conf.getDeliveryFolder(); + sftpConf.setTargetDir(rootFolder); + sftpService.sftpPut(sftpConf, csvPath); + } + // Chaque unité documentaire s'exporte dans un sous dossier "medias" + Path mediaFolderPath = Path.of(conf.getDeliveryFolder(), MEDIA_DIR); + String mediaFolder = mediaFolderPath.toString().replace('\\', '/'); + sftpConf.setTargetDir(mediaFolder); + LOG.info("Envoi du document {}", mediasDirPath.getFileName()); + sftpService.sftpPut(sftpConf, mediasDirPath.toAbsolutePath()); + return true; + } catch (final PgcnTechnicalException e) { + LOG.error("Erreur de connexion sftp lors de l'export sur la Bibliothèque numérique"); + return false; + } + } + + @Transactional + public boolean exportFtp(final DigitalLibraryConfiguration conf, final boolean multiple, final boolean lastDoc, final File csv, final Path mediasDirPath) throws IOException, + PgcnTechnicalException { + final FTPClient ftpClient = new FTPClient(); + + ftpClient.connect(conf.getAddress(), Integer.parseInt(conf.getPort())); + + final boolean success = ftpClient.login(conf.getLogin(), cryptoService.decrypt(conf.getPassword())); + + if (!success) { + LOG.error("Erreur de connexion ftp lors de l'export sur la Bibliothèque numérique"); + return false; + } else { + ftpClient.changeWorkingDirectory(conf.getDeliveryFolder()); + if (!multiple || lastDoc) { + putPathRecursively(csv, ftpClient); + } + LOG.info("Envoi du document {}", mediasDirPath.getFileName()); + putPathRecursively(mediasDirPath.toFile(), ftpClient); + ftpClient.logout(); + ftpClient.disconnect(); + return true; + } + } + + @Transactional + public Pair exportDocUnit(final DocUnit docUnit, + final BibliographicRecordDcDTO metaDc, + final Path mediaDir, + final DigitalLibraryConfiguration conf, + final boolean multiple, + final boolean firstDoc) throws IOException, PgcnTechnicalException { final Path root = mediaDir.resolve(docUnit.getPgcnId()); // Suppression du répertoire s'il existe @@ -252,7 +299,7 @@ public File exportDocUnit(final DocUnit docUnit, } catch (final PgcnUncheckedException e) { throw new PgcnTechnicalException(e); } - return csv; + return Pair.of(csv, depotPath); } private File createDocUnitsDigitalLibraryDiffusionCsv(final DocUnit docUnit, @@ -543,10 +590,11 @@ private List addDepotFiles(final DocUnit docUnit, final P final StoredFile printStoredFile = print.get(); final File sourceFile = bm.getFileForStoredFile(printStoredFile, libraryId); final Path sourcePath = Paths.get(sourceFile.getAbsolutePath()); + final String fileName = printStoredFile.getFilename().substring(0, printStoredFile.getFilename().lastIndexOf(".") + 1) + ImageUtils.FORMAT_JPG; if (conf.isExportPrint() && page.getNumber() != null) { try { - final Path destPath = Files.createFile(depotPrint.resolve(printStoredFile.getFilename())); + final Path destPath = Files.createFile(depotPrint.resolve(fileName)); Files.copy(sourcePath, destPath, StandardCopyOption.REPLACE_EXISTING); // On remplit la map pour optimiser le traitement ultérieur des métadonnées checkSums.add(exportMetsService.getCheckSummedStoredFile(printStoredFile, sourceFile)); diff --git a/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaRequestHandlerService.java b/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaRequestHandlerService.java index a1aed599..6bc6100a 100644 --- a/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaRequestHandlerService.java +++ b/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaRequestHandlerService.java @@ -61,7 +61,7 @@ public void automaticOmekaExport() { TransactionalJobRunner job = new TransactionalJobRunner<>(docsToExport, transactionService); - job.setCommit(1).setReadOnly(true).setElementName("Doc Unit Exporting Omeka").forEach(docId -> { + job.setCommit(1).setReadOnly(false).setElementName("Doc Unit Exporting Omeka").forEach(docId -> { boolean lastDoc = i.incrementAndGet() == docsToExport.size(); DocUnit doc = docUnitRepository.getReferenceById(docId); diff --git a/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaService.java b/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaService.java index 5f0f7efa..56c48c63 100644 --- a/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaService.java +++ b/src/main/java/fr/progilone/pgcn/service/exchange/omeka/OmekaService.java @@ -457,18 +457,18 @@ private File createDocUnitOmekaCsv(final DocUnit docUnit, } } - // custom proerties + // custom properties for (final DocPropertyType enteteCustom : entetesCustom) { if (metaDc.getCustomProperties() != null) { - boolean value = false; - for (final DocPropertyDTO customDC : metaDc.getCustomProperties()) { - if (customDC.getType().getLabel().equals(enteteCustom.getLabel())) { - writer.append(getFormatedValues(Collections.singletonList(customDC.getValue()))).append(CSV_COL_SEP); - value = true; - break; - } - } - if (!value) { + // Values with same type are put in the same group + List customProperties = metaDc.getCustomProperties() + .stream() + .filter(p -> p.getType().getLabel().equals(enteteCustom.getLabel())) + .map(DocPropertyDTO::getValue) + .toList(); + if (!customProperties.isEmpty()) { + writer.append(getFormatedValues(customProperties)).append(CSV_COL_SEP); + } else { writer.append(EMPTY_FIELD_VALUE).append(CSV_COL_SEP); } } diff --git a/src/main/java/fr/progilone/pgcn/service/exchange/ssh/SftpService.java b/src/main/java/fr/progilone/pgcn/service/exchange/ssh/SftpService.java index c4fcdccd..c98baa5e 100644 --- a/src/main/java/fr/progilone/pgcn/service/exchange/ssh/SftpService.java +++ b/src/main/java/fr/progilone/pgcn/service/exchange/ssh/SftpService.java @@ -17,9 +17,11 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.Arrays; import java.util.Optional; import java.util.Properties; import java.util.Vector; +import java.util.regex.Matcher; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -138,7 +140,7 @@ public void sftpPut(final SftpConfiguration conf, final Path localSource) throws conf.getTargetDir()); // Positionnement sur le répertoire cible - channelSftp.cd(conf.getTargetDir()); + goToTargetDir(conf.getTargetDir(), channelSftp); // Si le répertoire existe déjà, on le supprime final boolean pathExists = checkPath(localSource, channelSftp); if (pathExists) { @@ -173,6 +175,38 @@ public void sftpPut(final SftpConfiguration conf, final Path localSource) throws } } + private static void goToTargetDir(final String targetDir, final ChannelSftp channelSftp) throws SftpException { + Path targetDirPath = Path.of(targetDir); + boolean targetExists = checkPath(targetDirPath, channelSftp); + if (!targetExists) { + // Si le chemin 'target' spécifié n'existe pas, le créer dans le serveur + String[] path = targetDirPath.toString().split(Matcher.quoteReplacement(System.getProperty("file.separator"))); + Arrays.stream(path).filter(StringUtils::isNotEmpty).forEach(p -> putTargetDir(p, channelSftp)); + } else { + channelSftp.cd(targetDir); + } + } + + private static void putTargetDir(final String targetName, final ChannelSftp channelSftp) { + try { + // Si le sous-dossier distant existe déjà, on se place dedans + Path targetDirPath = Path.of(targetName); + boolean targetExists = checkPath(targetDirPath, channelSftp); + if (targetExists) { + channelSftp.cd(targetName); + return; + } + LOG.debug("Création du répertoire distant {} ({})", targetName, channelSftp.pwd()); + // Création du sous-dossier distant + channelSftp.mkdir(targetName); + channelSftp.chmod(0770, targetName); // permission en octal + channelSftp.cd(targetName); + } catch (final SftpException e) { + LOG.error("Une erreur s'est produite lors du traitement du répertoire {}: {}", targetName, e.getMessage()); + LOG.error(e.getMessage(), e); + } + } + /** * Vérifie l'existence du fichier / répertoire local dans le répertoire distant courant * @@ -180,7 +214,7 @@ public void sftpPut(final SftpConfiguration conf, final Path localSource) throws * @param channelSftp * @return */ - private boolean checkPath(final Path localSource, final ChannelSftp channelSftp) throws SftpException { + private static boolean checkPath(final Path localSource, final ChannelSftp channelSftp) throws SftpException { final Vector ls = channelSftp.ls("."); return ls.stream().anyMatch(e -> StringUtils.equals(e.getFilename(), localSource.getFileName().toString())); } @@ -193,7 +227,7 @@ private boolean checkPath(final Path localSource, final ChannelSftp channelSftp) * @param channelSftp * connexion SFTP établie */ - private void putPathRecursively(final File localSource, final ChannelSftp channelSftp) { + private static void putPathRecursively(final File localSource, final ChannelSftp channelSftp) { final String targetName = localSource.getName(); // Répertoire: création, ouverture et recopie du contenu diff --git a/src/main/java/fr/progilone/pgcn/service/statistics/StatisticsWorkflowService.java b/src/main/java/fr/progilone/pgcn/service/statistics/StatisticsWorkflowService.java index 686f88e7..f1c555e6 100644 --- a/src/main/java/fr/progilone/pgcn/service/statistics/StatisticsWorkflowService.java +++ b/src/main/java/fr/progilone/pgcn/service/statistics/StatisticsWorkflowService.java @@ -10,7 +10,14 @@ import fr.progilone.pgcn.domain.document.PhysicalDocument; import fr.progilone.pgcn.domain.document.conditionreport.ConditionReport; import fr.progilone.pgcn.domain.document.conditionreport.ConditionReportDetail; -import fr.progilone.pgcn.domain.dto.statistics.*; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDeliveryProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDocUnitInfoDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDocUnitProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDocUnitProgressDTOPending; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowProfileActivityDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowStateProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowUserActivityDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowUserProgressDTO; import fr.progilone.pgcn.domain.library.Library; import fr.progilone.pgcn.domain.lot.Lot; import fr.progilone.pgcn.domain.project.Project; @@ -125,12 +132,25 @@ public List getDocUnitProgressReportPending(f final List status, final List users, final LocalDate fromDate, - final LocalDate toDate) { + final LocalDate toDate, + final int page, + final int size) { final String userLogin = CollectionUtils.isEmpty(users) ? null : users.get(0); - return docUnitWorkflowRepository.findDocUnitProgressStatsPending(libraries, projects, projetActive, lots, trains, pgcnId, states, status, users, fromDate, toDate) + return docUnitWorkflowRepository.findDocUnitProgressStats(libraries, + projects, + projetActive, + lots, + trains, + pgcnId, + states, + status, + users, + fromDate, + toDate, + PageRequest.of(page, size)) .stream() .map(w -> getWorkflowDocUnitProgressDTOLight(w, null, userLogin)) // transform to DTO .collect(Collectors.toList()); @@ -530,8 +550,9 @@ private WorkflowDocUnitProgressDTOPending getWorkflowDocUnitProgressDTOLight(fin final DocUnit docUnit = workflow.getDocUnit(); WorkflowDocUnitProgressDTO dto = new WorkflowDocUnitProgressDTO(); - if (docUnit == null) + if (docUnit == null) { return pendingDTO; + } final Project project = docUnit.getProject(); final Lot lot = docUnit.getLot(); @@ -579,11 +600,11 @@ private WorkflowDocUnitProgressDTOPending getWorkflowDocUnitProgressDTOLight(fin return pendingDTO; } - private WorkflowDocUnitProgressDTO setInfosAndNumberPageToDTO(WorkflowDocUnitProgressDTO dto, - DocUnit docUnit, - Set physicalDocuments, - Lot lot, - Optional record) { + private WorkflowDocUnitProgressDTO setInfosAndNumberPageToDTO(final WorkflowDocUnitProgressDTO dto, + final DocUnit docUnit, + final Set physicalDocuments, + final Lot lot, + final Optional record) { final WorkflowDocUnitInfoDTO infos = new WorkflowDocUnitInfoDTO(); final Optional digitalDoc = docUnit.getDigitalDocuments().stream().filter(digDoc -> !StringUtils.isEmpty(digDoc.getDigitalId())).findFirst(); if (digitalDoc.isPresent()) { diff --git a/src/main/java/fr/progilone/pgcn/service/user/UserService.java b/src/main/java/fr/progilone/pgcn/service/user/UserService.java index 8f2e4798..ebee9f06 100644 --- a/src/main/java/fr/progilone/pgcn/service/user/UserService.java +++ b/src/main/java/fr/progilone/pgcn/service/user/UserService.java @@ -35,7 +35,6 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,22 +113,8 @@ public void changeCurrentUserPassword(final String password) { @Transactional public String changeUserPassword(final String identifier) { - String upperCaseLetters = RandomStringUtils.random(2, 65, 90, true, true); - String lowerCaseLetters = RandomStringUtils.random(2, 97, 122, true, true); - String numbers = RandomStringUtils.randomNumeric(2); - String specialChar = RandomStringUtils.random(2, 33, 47, false, false); - String totalChars = RandomStringUtils.randomAlphanumeric(4); - - String combinedChars = upperCaseLetters.concat(lowerCaseLetters).concat(numbers).concat(specialChar).concat(totalChars); - - List pwdChars = combinedChars.chars().mapToObj(c -> (char) c).collect(Collectors.toList()); - - Collections.shuffle(pwdChars); - - String password = pwdChars.stream().collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString(); - + String password = RandomUtil.generatePassword(); changeUserPassword(identifier, password); - return password; } diff --git a/src/main/java/fr/progilone/pgcn/service/util/RandomUtil.java b/src/main/java/fr/progilone/pgcn/service/util/RandomUtil.java index 10c93e44..c032acea 100644 --- a/src/main/java/fr/progilone/pgcn/service/util/RandomUtil.java +++ b/src/main/java/fr/progilone/pgcn/service/util/RandomUtil.java @@ -2,6 +2,10 @@ import static org.apache.commons.text.CharacterPredicates.*; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.text.RandomStringGenerator; /** @@ -9,7 +13,6 @@ */ public final class RandomUtil { - private static final int DEF_PWD_COUNT = 8; private static final int DEF_KEY_COUNT = 20; private RandomUtil() { @@ -21,7 +24,20 @@ private RandomUtil() { * @return the generated password */ public static String generatePassword() { - return new RandomStringGenerator.Builder().withinRange('0', 'z').filteredBy(LETTERS, DIGITS).build().generate(DEF_PWD_COUNT); + + String upperCaseLetters = RandomStringUtils.random(2, 65, 90, true, true); + String lowerCaseLetters = RandomStringUtils.random(2, 97, 122, true, true); + String numbers = RandomStringUtils.randomNumeric(2); + String specialChar = RandomStringUtils.random(2, 33, 47, false, false); + String totalChars = RandomStringUtils.randomAlphanumeric(4); + + String combinedChars = upperCaseLetters.concat(lowerCaseLetters).concat(numbers).concat(specialChar).concat(totalChars); + + List pwdChars = combinedChars.chars().mapToObj(c -> (char) c).collect(Collectors.toList()); + + Collections.shuffle(pwdChars); + + return pwdChars.stream().collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString(); } /** diff --git a/src/main/java/fr/progilone/pgcn/service/workflow/WorkflowService.java b/src/main/java/fr/progilone/pgcn/service/workflow/WorkflowService.java index 97690942..e3d94c09 100644 --- a/src/main/java/fr/progilone/pgcn/service/workflow/WorkflowService.java +++ b/src/main/java/fr/progilone/pgcn/service/workflow/WorkflowService.java @@ -316,7 +316,7 @@ public void processAutomaticState(final String docUnitId, final WorkflowStateKey if (workflow == null) { LOG.warn("Aucun workflow sur le document {} : impossible de réaliser automatiquement l'étape {}", doc.getPgcnId(), key.name()); } else { - final DocUnitState currentState = workflow.getCurrentStateByKey(key); + DocUnitState currentState = workflow.getCurrentStateByKey(key); if (currentState != null && currentState.isWaiting()) { currentState.process(null); } else { @@ -464,7 +464,6 @@ public boolean canStateBeProcessed(final DocUnit doc, final DocUnitState state) case CONTROLE_QUALITE_EN_COURS: return true; case DIFFUSION_DOCUMENT_OMEKA: - return docUnitService.isDocumentReadyForDiffusionOmeka(doc.getIdentifier()); case DIFFUSION_DOCUMENT_LOCALE: case DIFFUSION_DOCUMENT: case DIFFUSION_DOCUMENT_DIGITAL_LIBRARY: diff --git a/src/main/java/fr/progilone/pgcn/web/rest/statistics/StatisticsWorkflowController.java b/src/main/java/fr/progilone/pgcn/web/rest/statistics/StatisticsWorkflowController.java index 6d17f8ec..5c6c0426 100644 --- a/src/main/java/fr/progilone/pgcn/web/rest/statistics/StatisticsWorkflowController.java +++ b/src/main/java/fr/progilone/pgcn/web/rest/statistics/StatisticsWorkflowController.java @@ -3,7 +3,13 @@ import com.codahale.metrics.annotation.Timed; import fr.progilone.pgcn.domain.AbstractDomainObject; import fr.progilone.pgcn.domain.document.DigitalDocument.DigitalDocumentStatus; -import fr.progilone.pgcn.domain.dto.statistics.*; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDeliveryProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDocUnitProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowDocUnitProgressDTOPending; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowProfileActivityDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowStateProgressDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowUserActivityDTO; +import fr.progilone.pgcn.domain.dto.statistics.WorkflowUserProgressDTO; import fr.progilone.pgcn.domain.util.CustomUserDetails; import fr.progilone.pgcn.domain.workflow.WorkflowStateKey; import fr.progilone.pgcn.domain.workflow.WorkflowStateStatus; @@ -199,7 +205,13 @@ public ResponseEntity> getWorkflowDocUni @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(name = "from", required = false) final LocalDate fromDate, @DateTimeFormat(pattern = "yyyy-MM-dd") @RequestParam(name = "to", - required = false) final LocalDate toDate) { + required = false) final LocalDate toDate, + @RequestParam(value = "page", + defaultValue = "0", + required = false) final int page, + @RequestParam(value = "size", + defaultValue = "10", + required = false) final int size) { // Droits d'accès final List filteredLibraries = libraryAccesssHelper.getLibraryFilter(request, libraries); final List filteredProjects = accessHelper.filterProjects(projects).stream().map(AbstractDomainObject::getIdentifier).collect(Collectors.toList()); @@ -223,7 +235,9 @@ public ResponseEntity> getWorkflowDocUni status, users, fromDate, - toDate), HttpStatus.OK); + toDate, + page, + size), HttpStatus.OK); } @RequestMapping(method = RequestMethod.GET, diff --git a/src/main/java/fr/progilone/pgcn/web/rest/train/TrainController.java b/src/main/java/fr/progilone/pgcn/web/rest/train/TrainController.java index c4818844..a3b7f1cf 100644 --- a/src/main/java/fr/progilone/pgcn/web/rest/train/TrainController.java +++ b/src/main/java/fr/progilone/pgcn/web/rest/train/TrainController.java @@ -115,7 +115,7 @@ public ResponseEntity getById(@PathVariable final String id) { @Timed @RolesAllowed({LOT_HAB3}) public ResponseEntity> findAllIdentifiersForProjects(final HttpServletRequest request, - @RequestParam(value = "projectsIds", required = false) final List projectIds) { + @RequestParam(value = "projectIds", required = false) final List projectIds) { final List trainIds = trainService.findAllByProjectIds(projectIds); return createResponseEntity(trainIds); } diff --git a/src/main/resources/config/liquibase/changelog/db-changelog-conf-digital-library-03.xml b/src/main/resources/config/liquibase/changelog/db-changelog-conf-digital-library-03.xml new file mode 100644 index 00000000..381ea82f --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/db-changelog-conf-digital-library-03.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 1c9d8e93..c5b054bf 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -261,6 +261,7 @@ + diff --git a/src/main/webapp/scripts/app/numahop/common/validationSrvc.js b/src/main/webapp/scripts/app/numahop/common/validationSrvc.js index 2845e378..a181d02f 100644 --- a/src/main/webapp/scripts/app/numahop/common/validationSrvc.js +++ b/src/main/webapp/scripts/app/numahop/common/validationSrvc.js @@ -50,9 +50,16 @@ return gettextCatalog.getString('Ce champ est obligatoire'); } - var regEx = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})'); + // Cette regex teste si le mdp contient au moins 12 caractères, + // au moins 1 majuscule (?=.*?[A-Z]), + // 1 minuscule (?=.*?[a-z]), + // 1 chiffre (?=.*?[0-9]) + // et un caractère spécial (?=.*?[!-/:-@\\[-`{-~£§€µ²°~¨]) + // Elle teste également que TOUS les caractères soient des alphanumériques OU des caractères spéciaux acceptés [!-~£§€µ²°~¨]{12,} + // !-~ contient tous les ASCII entre 33 et 126, y compris les caractères alpahanumériques (48 à 57), (65 à 90), (97 à 122) + const regEx = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!-/:-@\\[-`{-~£§€µ²°~¨])[!-~£§€µ²°~¨]{12,}$/; if (!regEx.test(value)) { - return gettextCatalog.getString("Le mot de passe n'est pas conforme. (12 caractères, au moins 1 majuscule, 1 minuscule, 1 chiffre et un caractère spécial)."); + return gettextCatalog.getString(`Le mot de passe n'est pas conforme. Il nécessite au moins 12 caractères, 1 majuscule, 1 minuscule, 1 chiffre et un caractère spécial.`); } return true; }; diff --git a/src/main/webapp/scripts/app/platformconfiguration/digitallibraryconfiguration/digitalLibraryConfigurationEdit.html b/src/main/webapp/scripts/app/platformconfiguration/digitallibraryconfiguration/digitalLibraryConfigurationEdit.html index dd5fe063..41f7bd92 100644 --- a/src/main/webapp/scripts/app/platformconfiguration/digitallibraryconfiguration/digitalLibraryConfigurationEdit.html +++ b/src/main/webapp/scripts/app/platformconfiguration/digitallibraryconfiguration/digitalLibraryConfigurationEdit.html @@ -89,6 +89,19 @@
Fiche
model="ctrl.configuration.password" numa-readonly="formRo" > +