--- /dev/null
+package org.lilie.services.eliot.temps.importcommun
+
+import org.lilie.services.eliot.scolarite.Etablissement
+import org.lilie.services.eliot.scolarite.etablissement.EtablissementService
+import org.lilie.services.eliot.scolarite.AnneeScolaire
+import org.lilie.services.eliot.scolarite.anneescolaire.AnneeScolaireService
+import org.lilie.services.eliot.temps.importsts.StsImportUtil
+import java.text.SimpleDateFormat
+
+/**
+ * @author jtra
+ */
+abstract class AbstractImportDonneesManager {
+
+ EtablissementService etablissementService
+ AnneeScolaireService anneeScolaireService
+
+ // Préfixe du nom des dossiers des établissements
+ protected final static String PREFIXE_NOM_DOSSIER_ETABLISSEMENT = "ETAB_"
+
+ // Nom du dossier contenant les données en attente de synchronisation
+ private final static String NOM_DOSSIER_EN_ATTENTE = 'EnAttente'
+
+ // Nom du dossier contenant les données d'un import en cours d'exécution
+ private final static String NOM_DOSSIER_EN_COURS = 'EnCours'
+
+ // Nom du dossier contenant les données des imports traités
+ private final static String NOM_DOSSIER_TRAITES = 'Traites'
+
+ // Nom du fichier de verrou (pour gérer la synchronisation des imports)
+ private final static String NOM_FICHIER_LOCK = 'lock'
+
+ // Préfixe du nom des dossiers contenant les données d'un import finalisé
+ final static String PREFIXE_IMPORT_TRAITE = "IMPORT_"
+
+ // Nom des fichiers rapports qui seront enregistrés
+ private final static String NOM_FICHIER_RAPPORT = "rapport.html"
+
+ /**
+ * Teste pour un établissent s'il existe des données importer
+ * @param etablissement un établissement
+ * @return true s'il y a des données STS à importer pour l'établissement,
+ * false sinon
+ */
+ abstract boolean hasDonneesAImporter(Etablissement etablissement)
+
+ /**
+ * Teste pour un établissement si un import STS est en cours
+ * @param etablissement un établissement
+ * @return true si un import STS est cours pour cet établissement, false sinon
+ */
+ abstract boolean hasImportEnCours(Etablissement etablissement)
+
+ /**
+ * @return le code du traitement (utilisé pour construire l'arborescence des dossiers)
+ */
+ abstract String getTraitementCode()
+
+ /**
+ * @return le chemin du dossier racine de l'import
+ */
+ abstract String getUriRacineTraitement()
+
+ /**
+ * Retourne le dossier des données pour un établissement et un état d'import
+ * Note: l'arborescence des dossiers sera créée si nécessaire
+ * @param etablissement un établissement
+ * @param etat un état d'import (EN_ATTENTE, EN_COURS, ou TRAITE)
+ * @return le dossier pour l'établissement et l'état passé en paramètre
+ */
+ File getFileDossierForEtablissementAndEtat(Etablissement etablissement,
+ ImportEtat etat) {
+ File fileDossierEtablissement = getFileDossierForEtablissement(etablissement)
+ String strNomSousDossier
+ switch (etat) {
+ case ImportEtat.EN_ATTENTE: strNomSousDossier = NOM_DOSSIER_EN_ATTENTE
+ break
+ case ImportEtat.EN_COURS: strNomSousDossier = NOM_DOSSIER_EN_COURS
+ break
+ case ImportEtat.TRAITE: strNomSousDossier = NOM_DOSSIER_TRAITES
+ break
+ default: throw new IllegalStateException(
+ "L'état $etat est inconnu"
+ )
+ }
+
+ File file = new File(fileDossierEtablissement, strNomSousDossier)
+
+ // Crée l'arborescence des dossiers si nécessaires
+ file.mkdirs()
+
+ return file
+ }
+
+ /**
+ * Retourne le dossier racine des imports pour un établissement (le
+ * dossier qui contient les sous-dossiers EnAttente, EnCours, Traites)
+ * @param etablissement un établissement
+ * @return le dossier racine des imports STS pour cet établissement
+ */
+ protected File getFileDossierForEtablissement(Etablissement etablissement) {
+ AnneeScolaire anneeScolaire = anneeScolaireService.anneeScolaireEnCours()
+ String result = uriRacineTraitement
+ result += "/$traitementCode"
+ result += "/${PREFIXE_NOM_DOSSIER_ETABLISSEMENT}${etablissement.uai}"
+ result += "/${anneeScolaire.code}"
+
+ File file = new File(result)
+ // Crée l'arborescence si nécessaire
+ file.mkdirs()
+
+ return file
+ }
+
+ /**
+ * Retourne l'établissement associé à un dossier d'import STS d'un
+ * établissement
+ * @param nomDossier le nom du dossier
+ * @return l'établissement associé à ce dossier, ou null si ce dossier ne
+ * correspond pas à un dossier d'import STS d'un établissement
+ */
+ protected Etablissement parseEtablissementFromNomDossier(String nomDossier) {
+ if (!nomDossier.startsWith(PREFIXE_NOM_DOSSIER_ETABLISSEMENT)) {
+ return null
+ }
+
+ String uai = nomDossier.substring(
+ PREFIXE_NOM_DOSSIER_ETABLISSEMENT.size()
+ )
+
+ return etablissementService.findEtablissementByUai(
+ uai
+ )
+ }
+
+ /**
+ * @param etablissement
+ * @return retourne la date (sous forme de Long) à laquelle les fichiers STS
+ * ont été déposés pour cet établissement, ou null s'il n'y a pas de fichiers
+ * STS en attente pour
+ * cet établissement
+ */
+ abstract protected Long getTimeDepot(Etablissement etablissement)
+
+ /**
+ * Méthode utilitaire pour supprimer un fichier (s'il existe)
+ * Les exceptions éventuelles sont catchées et loggées
+ * @param file le fichier à supprimer
+ */
+ protected void safeDelete(File file) {
+ if (file.exists()) {
+ try {
+ file.delete()
+ }
+ catch (Exception e) {
+ log.error("Impossible de supprimer le fichier $file", e)
+ }
+ }
+ }
+
+ /**
+ * Retourne un pointeur sur un fichier pour un
+ * établissement et un état (en attente, en cours, traité)
+ * @param etablissement un établissement
+ * @param nomFichier NOM_FICHIER_EMP_STS ou NOM_FICHIER_STS_EMP
+ * @param etat un état d'import Sts (en attente, en cours, traité)
+ * @param dateTraitement ce paramètre doit être fournit si
+ * etat == StsImportEtat.TRAITE, il correspond à la date du traitement
+ * @return le fichier EmpSts correspondant
+ * @throws IllegalArgumentException si nomFichier n'est pas une des 2 valeurs
+ * permises
+ * @throws IllegalArgumentException si etat == StsImportEtat.TRAITE et
+ * dateTraitement == null
+ */
+ protected File getFile(Etablissement etablissement,
+ String nomFichier,
+ ImportEtat etat,
+ Date dateTraiment = null)
+ throws IllegalArgumentException {
+ // Vérifie l'argument 'dateTraitement' si 'etat' == TRAITE
+ if (etat == ImportEtat.TRAITE && !dateTraiment) {
+ throw new IllegalArgumentException(
+ "Le paramètre 'dateTraitement' doit être renseigné pour " +
+ "etat == 'StsImportEtat.TRAITE'"
+ )
+ }
+
+ if (etat != ImportEtat.TRAITE) {
+ File fileDossierEtablissementEtat = getFileDossierForEtablissementAndEtat(
+ etablissement,
+ etat
+ )
+
+ return new File(fileDossierEtablissementEtat, nomFichier)
+ }
+ else {
+ File fileDossierImportTraite = getFileDossierImportTraites(
+ etablissement,
+ dateTraiment
+ )
+
+ return new File(fileDossierImportTraite, nomFichier)
+ }
+ }
+
+ /**
+ * Retourne un pointeur sur le dossier d'un import traité pour un
+ * établissement et une date de traitement
+ * l'établissement fournit en argument
+ * Note : l'arborescence des dossier sera créée si elle n'existe pas
+ * @param etablissement un établissement
+ * @param dateTraitement la date de traitement de l'import
+ * @return le pointeur sur le dossier
+ */
+ protected File getFileDossierImportTraites(Etablissement etablissement,
+ Date dateTraitement) {
+
+ // Récupère le dossier racine des imports terminés
+ File fileDossierRacineImportsTraites = getFileDossierForEtablissementAndEtat(
+ etablissement,
+ ImportEtat.TRAITE
+ )
+
+ // Dossier import traite (daté)
+ File fileDossierImportTraite = new File(
+ fileDossierRacineImportsTraites,
+ PREFIXE_IMPORT_TRAITE + dateTraitement.format(
+ StsImportUtil.FORMAT_DATE_HEURE_DOSSIER
+ )
+ )
+
+ // Crée l'arborescence de dossiers si nécessaires
+ fileDossierImportTraite.mkdirs()
+
+ return fileDossierImportTraite
+ }
+
+ /**
+ * Retourne un pointeur sur le fichier du rapport d'un import pour un
+ * établissement et une date de traitement
+ * @param etablissement un établissement
+ * @param dateTraitement une date de traitement
+ * @return
+ */
+ public File getFileRapport(Etablissement etablissement, Date dateTraitement) {
+ File fileDossierImportTraite = getFileDossierImportTraites(
+ etablissement,
+ dateTraitement
+ )
+
+ return new File(
+ fileDossierImportTraite,
+ AbstractImportDonneesManager.NOM_FICHIER_RAPPORT
+ )
+ }
+
+ /**
+ * Essaie de verrouiller les données d'un établissement
+ * @param etablissement un établissement
+ * @return true si les données de l'établissement ont pu être vérouillées,
+ * false sinon
+ */
+ boolean verrouilleEtablissement(Etablissement etablissement) {
+ File fileVerrou = getFileVerrouForEtablissement(etablissement)
+
+ if (fileVerrou.exists()) {
+ log.error(
+ "Impossible de poser un verrou pour l'établissement ${etablissement?.uai}. " +
+ "Un verrou pour cet établissement existe déjà."
+ )
+ return false
+ }
+
+ try {
+ fileVerrou.createNewFile()
+ log.info("Pose du verrou pour l'établissement ${etablissement?.uai}")
+ return true // Le verrou a été posé
+ }
+ catch (Exception e) {
+ log.error("Impossible de poser un verrou pour l'établissement ${etablissement?.uai}", e)
+ return false // Le verrou n'a pas pu être posé
+ }
+ }
+
+ /**
+ * Déverouille les données d'un établissement
+ * @param etablissement
+ */
+ void deverrouilleEtablissement(Etablissement etablissement) {
+ File fileVerrou = getFileVerrouForEtablissement(etablissement)
+ if (fileVerrou.exists()) {
+ try {
+ fileVerrou.delete()
+ log.info("Suppression du verrou pour l'établissement ${etablissement.uai}")
+ }
+ catch (Exception e) {
+ log.error "Echec de l'opération de dévérouillage : ${e.message}"
+ }
+ }
+ }
+
+ /**
+ * Teste si un établissement est verrouillé
+ * @param etablissement
+ * @return true si l'établissement est verrouillé, false sinon
+ */
+ boolean isVerrouille(Etablissement etablissement) {
+ File fileVerrou = getFileVerrouForEtablissement(etablissement)
+ return fileVerrou.exists()
+ }
+
+ /**
+ * Retourne un pointeur sur le fichier de verrou pour un établissement
+ * @param etablissement un établissement
+ * @return Retourne un pointeur sur le fichier de verrou pour un établissement
+ */
+ protected File getFileVerrouForEtablissement(Etablissement etablissement) {
+ File fileDossier = getFileDossierForEtablissement(etablissement)
+ return new File(fileDossier, NOM_FICHIER_LOCK)
+ }
+
+ /**
+ * @return l'établissement en attente d'un import depuis le plus longtemps,
+ * ou null si aucun établissement n'est en attente d'un import STS
+ */
+ public Etablissement piocheEtablissementEnAttenteImport(String identifiantJob) {
+ File fileRacineImportSts = new File("$uriRacineTraitement/$traitementCode")
+
+ if (!fileRacineImportSts.exists()) {
+ return null
+ }
+
+ Etablissement etablissementSelectionne = null
+ Long timeDepotLePlusAncien = Long.MAX_VALUE
+
+ Iterator it = fileRacineImportSts.list().iterator()
+ while (it.hasNext()) {
+ String nomDossier = it.next()
+ Etablissement etablissement = null
+ try {
+ etablissement = parseEtablissementFromNomDossier(nomDossier)
+ }
+ catch (Exception e) {
+ log.error(
+ "Erreur lors du traitement du dossier $nomDossier : " +
+ "(Job : $identifiantJob).",
+ e
+ )
+ }
+
+ if (etablissement) {
+ Long timeDepot = getTimeDepot(etablissement)
+
+ // Recherche l'établissement dont le dépôt est le plus ancien
+ if (hasDonneesAImporter(etablissement) &&
+ timeDepot < timeDepotLePlusAncien &&
+ !isVerrouille(etablissement)) {
+ timeDepotLePlusAncien = timeDepot
+ etablissementSelectionne = etablissement
+ }
+ }
+ }
+
+ if (etablissementSelectionne) {
+ log.info(
+ "Prise en charge de l'établissement ${etablissementSelectionne.uai} " +
+ "(par le job $identifiantJob.) " +
+ "- en attente depuis " +
+ new Date(timeDepotLePlusAncien)
+ )
+ return etablissementSelectionne
+ }
+
+ // Aucun établissement en attente d'un import STS
+ return null
+ }
+
+ /**
+ * Retourne tous les rapports d'import pour un établissement
+ * @param etablissement
+ * @return
+ */
+ public List<ImportRapportInfo> getListeRapportFichierInfo(Etablissement etablissement) {
+ List<ImportRapportInfo> listeRapportFichierInfo = (List) []
+
+ File fileDossierImportsTraites = getFileDossierForEtablissementAndEtat(
+ etablissement,
+ ImportEtat.TRAITE
+ )
+
+ File[] listeDossiersImportsTraites =
+ fileDossierImportsTraites.listFiles(
+ {
+ File dir, String name ->
+ return name.startsWith(PREFIXE_IMPORT_TRAITE)
+ } as FilenameFilter
+ )
+
+ listeDossiersImportsTraites.each { File fileDossier ->
+ File fileRapport = new File(
+ fileDossier,
+ AbstractImportDonneesManager.NOM_FICHIER_RAPPORT
+ )
+
+ if (fileRapport.exists()) {
+
+ try {
+ // Récupère la date de l'import
+ String strDate = fileRapport.parentFile.name.substring(
+ PREFIXE_IMPORT_TRAITE.size()
+ )
+
+ Date date =
+ new SimpleDateFormat(
+ StsImportUtil.FORMAT_DATE_HEURE_DOSSIER,
+ Locale.FRANCE
+ ).parse(strDate)
+
+ listeRapportFichierInfo << new ImportRapportInfo(
+ etablissementNom: etablissement.nomAffichage,
+ etablissementUai: etablissement.uai,
+ dateRapport: date,
+ fileRapport: fileRapport
+ )
+ }
+ catch (Exception e) {
+ log.error "Erreur durant la récupération des informations dans le " +
+ "nom du dossier : ${e.message}"
+ }
+ }
+ }
+
+ return listeRapportFichierInfo
+ }
+
+ /**
+ * @return Retourne la liste des établissements qui sont en attente d'un import
+ */
+ public List<Etablissement> getEtablissementsEnAttenteImport() {
+ File fileRacineImportSts = new File("$uriRacineTraitement/$traitementCode")
+
+ if (!fileRacineImportSts.exists()) {
+ return []
+ }
+
+ List<Etablissement> etablissementsEnAttente = []
+
+ // Note (jtra) : L'extraction des établissements à partir des dossiers physiques
+ // devrait être dans une méthode dédiée
+ fileRacineImportSts.list().each { String name ->
+ Etablissement etablissement = parseEtablissementFromNomDossier(name)
+
+ if (etablissement && hasDonneesAImporter(etablissement)) {
+ etablissementsEnAttente << etablissement
+ }
+ }
+
+ return etablissementsEnAttente
+ }
+}