/*
 * Version 1.1
 * CeCILL Copyright (c) 2006-2007, AtolCD, ADULLACT-projet
 * Initiated by AtolCD S.A. & ADULLACT-projet S.A.
 * Developped by AtolCD
 * 
 * contact@atolcd.com
 * contact@adullact-projet.coop
 * 
 * Ce logiciel est un programme informatique servant à faire circuler des 
 * documents au travers d'un circuit de validation, où chaque acteur vise 
 * le dossier, jusqu'à l'étape finale de signature.
 * 
 * Ce logiciel est régi par la licence CeCILL soumise au droit français et
 * respectant les principes de diffusion des logiciels libres. Vous pouvez
 * utiliser, modifier et/ou redistribuer ce programme sous les conditions
 * de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
 * sur le site "http://www.cecill.info".
 * 
 * En contrepartie de l'accessibilité au code source et des droits de copie,
 * de modification et de redistribution accordés par cette licence, il n'est
 * offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
 * seule une responsabilité restreinte pèse sur l'auteur du programme,  le
 * titulaire des droits patrimoniaux et les concédants successifs.
 * 
 * A cet égard  l'attention de l'utilisateur est attirée sur les risques
 * associés au chargement,  à l'utilisation,  à la modification et/ou au
 * développement et à la reproduction du logiciel par l'utilisateur étant 
 * donné sa spécificité de logiciel libre, qui peut le rendre complexe à 
 * manipuler et qui le réserve donc à des développeurs et des professionnels
 * avertis possédant  des  connaissances  informatiques approfondies.  Les
 * utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
 * logiciel à leurs besoins dans des conditions permettant d'assurer la
 * sécurité de leurs systèmes et ou de leurs données et, plus généralement, 
 * à l'utiliser et l'exploiter dans les mêmes conditions de sécurité. 
 * 
 * Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
 * pris connaissance de la licence CeCILL, et que vous en avez accepté les
 * termes.
 *  
 */
package com.atolcd.parapheur.repo;

import com.atolcd.parapheur.model.ParapheurModel;
import com.atolcd.parapheur.repo.impl.EtapeCircuitImpl;
import com.atolcd.parapheur.repo.impl.SavedWorkflowImpl;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ICC_Profile;
import com.itextpdf.text.pdf.PdfArray;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfCopyFields;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfICCBased;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.collection.PdfCollection;
//import com.lowagie.text.PageSize;
//import com.lowagie.text.pdf.BaseFont;
//import com.lowagie.text.pdf.PdfContentByte;
//import com.lowagie.text.pdf.PdfCopyFields;
//import com.lowagie.text.pdf.PdfReader;
//import com.lowagie.text.pdf.PdfStamper;
import com.itextpdf.text.xml.xmp.DublinCoreSchema;
import com.itextpdf.text.xml.xmp.PdfSchema;
import com.itextpdf.text.xml.xmp.XmpArray;
import com.itextpdf.text.xml.xmp.XmpSchema;
import com.itextpdf.text.xml.xmp.XmpWriter;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;
import java.security.MessageDigest;
import java.security.Security;
import java.security.cert.CertStore;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.transaction.UserTransaction;
import org.adullact.iparapheur.comparator.NameNodeRefComparator;
import org.adullact.iparapheur.util.CollectionsUtils;
import org.adullact.iparapheur.util.X509Util;
import org.adullact.libersign.util.signature.PKCS7VerUtil;
import org.adullact.iparapheur.repo.util.signature.xades;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.audit.AuditService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateService;
import org.alfresco.service.cmr.rule.RuleService;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.Base64;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.TempFileProvider;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xpath.XPathAPI;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERUTCTime;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPUtil;
import org.bouncycastle.tsp.TimeStampToken;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

public final class ParapheurServiceImpl implements ParapheurService, InitializingBean {

    public static final String ARCHIVES_CONFIGURATION_PATH = "/app:company_home/app:dictionary/ph:archives_configuration";

    private static Logger logger = Logger.getLogger(ParapheurService.class);

    private static Runtime rt = Runtime.getRuntime();

    private NodeService nodeService;

    private CopyService copyService;

    private SearchService searchService;

    private DictionaryService dictionaryService;

    private FileFolderService fileFolderService;

    private NamespaceService namespaceService;

    private ContentService contentService;

    private PolicyComponent policyComponent;

    private TemplateService templateService;

    private PersonService personService;

    private RuleService ruleService;

    private ActionService actionService;

    private PermissionService permissionService;

    private AuthenticationService authenticationService;

    private AuthenticationComponent authenticationComponent;

    private S2lowService s2lowService;

    private AuthorityService authorityService;

    private AuditService auditService;

    private TransactionListener transactionListener;

    private ThreadPoolExecutor threadExecuter;

    private TransactionService transactionService;

    private BehaviourFilter policyFilter;

    private Properties configuration;

    private WorkflowService workflowService;

    private static final String KEY_PENDING_READ_NODES = "parapheurService.pendingReadNodes";

    private static final String KEY_PENDING_UPDATE_NODES = "parapheurService.pendingUpdateNodes";

    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setCopyService(CopyService copyService) {
        this.copyService = copyService;
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setDictionaryService(DictionaryService dictionaryService) {
        this.dictionaryService = dictionaryService;
    }

    public void setFileFolderService(FileFolderService fileFolderService) {
        this.fileFolderService = fileFolderService;
    }

    public void setWorkflowService(WorkflowService workflowService) {
        this.workflowService = workflowService;
    }

    /**
     * @param namespaceService
     *                The namespaceService to set.
     */
    public void setNamespaceService(NamespaceService namespaceService) {
        this.namespaceService = namespaceService;
    }

    /**
     * @param contentService
     *                The contentService to set.
     */
    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    public void setPolicyComponent(PolicyComponent policyComponent) {
        this.policyComponent = policyComponent;
    }

    /**
     * @param templateService
     *                The templateService to set.
     */
    public void setTemplateService(TemplateService templateService) {
        this.templateService = templateService;
    }

    /**
     * @param personService
     *                The personService to set.
     */
    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    /**
     * @param ruleService
     *                The ruleService to set.
     */
    public void setRuleService(RuleService ruleService) {
        this.ruleService = ruleService;
    }

    /**
     * @param actionService
     *                The actionService to set.
     */
    public void setActionService(ActionService actionService) {
        this.actionService = actionService;
    }

    /**
     * @param configuration
     *                The configuration to set.
     */
    public void setConfiguration(Properties configuration) {
        this.configuration = configuration;
    }

    /**
     * @param permissionService
     *                The permissionService to set.
     */
    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    /**
     * @param authenticationService
     *                The authenticationService to set.
     */
    public void setAuthenticationService(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    public void setAuthenticationComponent(AuthenticationComponent authenticationComponent) {
        this.authenticationComponent = authenticationComponent;
    }

    /**
     * @param service
     *                the s2lowService to set
     */
    public void setS2lowService(S2lowService service) {
        s2lowService = service;
    }

    /**
     * @param authorityService
     *                the authorityService to set
     */
    public void setAuthorityService(AuthorityService authorityService) {
        this.authorityService = authorityService;
    }

    /**
     * @param auditService
     *                the auditService to set
     */
    public void setAuditService(AuditService auditService) {
        this.auditService = auditService;
    }

    public void setThreadExecuter(ThreadPoolExecutor threadExecuter) {
        this.threadExecuter = threadExecuter;
    }

    public void setTransactionService(TransactionService transactionService) {
        this.transactionService = transactionService;
    }

    public void setPolicyFilter(BehaviourFilter policyFilter) {
        this.policyFilter = policyFilter;
    }

    public Properties getConfiguration() {
        return configuration;
    }

    /**
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() {
        Assert.notNull(nodeService, "There must be a node service");
        Assert.notNull(searchService, "There must be a search service");
        Assert.notNull(dictionaryService, "There must be a dictionary service");
        Assert.notNull(fileFolderService, "There must be a fileFolder service");
        Assert.notNull(templateService, "There must be a template service");
        Assert.notNull(permissionService, "There must be a permission service");
        Assert.notNull(actionService, "There must be a action service");
        Assert.notNull(personService, "There must be a person service");
        Assert.notNull(ruleService, "There must be a rule service");
        Assert.notNull(contentService, "There must be a content service");
        Assert.notNull(namespaceService, "There must be a namespace service");
        Assert.notNull(authenticationService, "There must be an authentication service");
        Assert.notNull(s2lowService, "There must be a S2low service");
        Assert.notNull(authorityService, "There must be an authority service");
        Assert.notNull(policyComponent, "There must be a policy component");
        Assert.notNull(configuration, "There must be a configuration element");
        Assert.notNull(threadExecuter, "There must be a thread executer");
        Assert.notNull(transactionService, "There must be a transaction service");
        Assert.notNull(auditService, "There must be a audit service");
        Assert.notNull(policyFilter, "There must be a behaviour filter");
    }

    public void init() {
        this.policyComponent.bindClassBehaviour(ContentServicePolicies.ON_CONTENT_READ, ContentModel.TYPE_CONTENT,
                new JavaBehaviour(this, "onContentRead", NotificationFrequency.TRANSACTION_COMMIT));

        this.policyComponent.bindClassBehaviour(ContentServicePolicies.ON_CONTENT_UPDATE, ContentModel.TYPE_CONTENT,
                new JavaBehaviour(this, "onContentUpdate", NotificationFrequency.TRANSACTION_COMMIT));

        this.transactionListener = new DocumentTransactionListener();
    }

    @SuppressWarnings("unchecked")
    private void queueNode(String mode, NodeRef nodeRef) {
        logger.debug("QUEUE NODE : " + mode);
        AlfrescoTransactionSupport.bindListener(transactionListener);
        if (!this.authenticationService.isCurrentUserTheSystemUser() && this.authenticationService.getCurrentUserName() != null) {
            // logger.debug("AUTH OK");
            Set<NodeUpdater> pendingNodeUpdates = (Set<NodeUpdater>) AlfrescoTransactionSupport.getResource(mode);
            if (pendingNodeUpdates == null) {
                pendingNodeUpdates = new HashSet<NodeUpdater>();
                AlfrescoTransactionSupport.bindResource(mode, pendingNodeUpdates);
            }
            logger.debug("AJOUT DU NOEUD: " + nodeRef.toString() + " POUR: " + this.authenticationService.getCurrentUserName());
            pendingNodeUpdates.add(new NodeUpdater(nodeRef, this.authenticationService.getCurrentUserName()));
        }
    }

    public void onContentRead(NodeRef noderef) {
        // logger.debug("LECTURE DE CONTENU");
        logger.debug("OnContentRead: " + noderef);
        queueNode(KEY_PENDING_READ_NODES, noderef);
    }

    public void onContentUpdate(NodeRef noderef, boolean newContent) {
        // logger.debug("MAJ DE CONTENU");
        logger.debug("OnContentUpdate: " + noderef);
        queueNode(KEY_PENDING_UPDATE_NODES, noderef);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isParapheur(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isParapheur(NodeRef nodeRef) {
        return isOfType(nodeRef, ParapheurModel.TYPE_PARAPHEUR);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isCorbeille(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isCorbeille(NodeRef nodeRef) {
        return isOfType(nodeRef, ParapheurModel.TYPE_CORBEILLE);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isCorbeilleVirtuelle(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isCorbeilleVirtuelle(NodeRef nodeRef) {
        return isOfType(nodeRef, ParapheurModel.TYPE_CORBEILLE_VIRTUELLE);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isDossier(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isDossier(NodeRef nodeRef) {
        return isOfType(nodeRef, ParapheurModel.TYPE_DOSSIER);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParapheurs()
     */
    public List<NodeRef> getParapheurs() {
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.parapheurs.childname");

        List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException e) {
            logger.error("Impossible d'accéder au répertoire de stockage des parapheurs.");
            return null;
        }

        if (results != null && results.size() == 1) {
            List<NodeRef> nodes = new ArrayList<NodeRef>();
            List<ChildAssociationRef> lstEnfants = nodeService.getChildAssocs(results.get(0),
                    ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
            for (ChildAssociationRef tmpChildAssoc : lstEnfants) {
                nodes.add(tmpChildAssoc.getChildRef());
            }

            // Sort node ref alphabeticaly
            NameNodeRefComparator comparator = new NameNodeRefComparator(nodeService);
            Collections.sort(nodes, comparator);

            return nodes;
        } else {
            logger.error("Erreur lors de la récupération des parapheurs.");
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParapheursFromName(String)
     */
    public List<NodeRef> getParapheursFromName(String nomParapheur) {
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.parapheurs.childname")
                + "/*[@cm:name='" + nomParapheur + "']";

        List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException e) {
            logger.error("Impossible d'accéder au répertoire de stockage des parapheurs.");
            return null;
        }

        if (results != null && results.size() > 0) {
            List<NodeRef> nodes = new ArrayList<NodeRef>();
            nodes.addAll(results);

            // Sort node ref alphabeticaly
            NameNodeRefComparator comparator = new NameNodeRefComparator(nodeService);
            Collections.sort(nodes, comparator);

            return nodes;
        } else {
            logger.error("Erreur lors de la récupération des parapheurs.");
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getCorbeilles(org.alfresco.service.cmr.repository.NodeRef)
     */
    public List<NodeRef> getCorbeilles(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(parapheur, ContentModel.ASSOC_CONTAINS,
                RegexQNamePattern.MATCH_ALL);
        List<NodeRef> res = null;

        if (childAssocs != null && childAssocs.size() > 0) {
            res = new ArrayList<NodeRef>(childAssocs.size());
            for (ChildAssociationRef ref : childAssocs) {
                if (isCorbeille(ref.getChildRef())) {
                    res.add(ref.getChildRef());
                }
            }
        }

        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDossiers(org.alfresco.service.cmr.repository.NodeRef)
     */
    public List<NodeRef> getDossiers(NodeRef corbeille) {
        Assert.isTrue(isCorbeille(corbeille), "NodeRef doit être un ph:corbeille");

        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(corbeille, ContentModel.ASSOC_CONTAINS,
                RegexQNamePattern.MATCH_ALL);
        List<NodeRef> res = null;

        if (childAssocs != null && childAssocs.size() > 0) {
            res = new ArrayList<NodeRef>(childAssocs.size());
            for (ChildAssociationRef ref : childAssocs) {
                if (isDossier(ref.getChildRef())) {
                    res.add(ref.getChildRef());
                }
            }
        }

        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDocuments(org.alfresco.service.cmr.repository.NodeRef)
     */
    public List<NodeRef> getDocuments(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier [" + dossier.getId() + "]");

        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(dossier, ContentModel.ASSOC_CONTAINS,
                RegexQNamePattern.MATCH_ALL);
        List<NodeRef> res = null;
        if (childAssocs != null && childAssocs.size() > 0) {
            res = new ArrayList<NodeRef>(childAssocs.size());
            for (ChildAssociationRef ref : childAssocs) {
                res.add(ref.getChildRef());
            }
        }

        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getCorbeille(org.alfresco.service.cmr.repository.NodeRef,
     *      org.alfresco.service.namespace.QName)
     */
    public NodeRef getCorbeille(NodeRef parapheur, QName name) {
        Assert.isTrue(isParapheur(parapheur), "Parapheur doit être un ph:parapheur");

        List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(parapheur, ContentModel.ASSOC_CONTAINS, name);
        if (childAssocs.size() != 1) {
            // Par design: Une seule bannette d'un nom donné par parapheur
            return null;
        }

        return childAssocs.get(0).getChildRef();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParentDossier(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getParentDossier(NodeRef nodeRef) {
        return getFirstParentOfType(nodeRef, ParapheurModel.TYPE_DOSSIER);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParentCorbeille(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getParentCorbeille(NodeRef nodeRef) {
        return getFirstParentOfType(nodeRef, ParapheurModel.TYPE_CORBEILLE);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParentParapheur(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getParentParapheur(NodeRef nodeRef) {
        return getFirstParentOfType(nodeRef, ParapheurModel.TYPE_PARAPHEUR);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParapheurOwner(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getParapheurOwner(NodeRef nodeRef) {
        nodeRef = getParentParapheur(nodeRef);
        if (nodeRef == null) {
            return null;
        }
        return (String) this.nodeService.getProperty(nodeRef, ParapheurModel.PROP_PROPRIETAIRE_PARAPHEUR);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isParapheurOwner(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    public boolean isParapheurOwner(NodeRef nodeRef, String userName) {
        Assert.notNull(nodeRef, "Node Ref is mandatory");
        Assert.isTrue(nodeService.exists(nodeRef), "Node Ref must be a valid node reference");
        Assert.isTrue(isParapheur(nodeRef), "Node Ref must represent a parapheur");
        Assert.notNull(userName, "User Name is mandatory");
        Assert.isTrue(userName.length() > 0, "User Name cannot be an empty string");

        String proprio = (String) nodeService.getProperty(nodeRef, ParapheurModel.PROP_PROPRIETAIRE_PARAPHEUR);
        return EqualsHelper.nullSafeEquals(proprio, userName);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getOwnedParapheur(java.lang.String)
     */
    public NodeRef getOwnedParapheur(String userName) {
        /* STV  code d'origine:  */
        for (NodeRef parapheur : getParapheurs()) {
            if (isParapheurOwner(parapheur, userName)) {
                // Par design: un seul parapheur par utilisateur.
                return parapheur;
            }
        }
        return null;
        /*
         * STV: ce code optimisé ne fonctionne que avec alfresco 3.3 ???
        NodeRef nodeRef = getParapheursHome();
        List<NodeRef> candidates = searchService.selectNodes(nodeRef,
                "./*[@ph:proprietaire='" + userName + "']",
                null,
                namespaceService,
                false);

        if (candidates.size() == 0) {
            logger.debug("pas de parapheur pour: " + userName);
            return null;
        }

        return candidates.get(0);
        */
    }

    /**
     * EN OPTION POUR PLUS TARD
     * 
     * @see com.atolcd.parapheur.repo.ParapheurService#getSharedParapheurs(java.lang.String)
     */
    public List<NodeRef> getSharedParapheurs(String userName) {
        throw new UnsupportedOperationException();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isParapheurSecretaire(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    @SuppressWarnings("unchecked")
    public boolean isParapheurSecretaire(NodeRef nodeRef, String userName) {
        Assert.notNull(nodeRef, "Node Ref is mandatory");
        Assert.isTrue(isParapheur(nodeRef), "Node Ref doit être un ph:parapheur");
        Assert.notNull(userName, "User Name is mandatory");

        List<String> lstSecretaires = (List<String>) nodeService.getProperty(nodeRef, ParapheurModel.PROP_SECRETAIRES);
        if (lstSecretaires != null && lstSecretaires.contains(userName)) {
            return true;
        }
        
        return false;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSecretariatParapheurs(java.lang.String)
     */
    public List<NodeRef> getSecretariatParapheurs(String userName) {
        List<NodeRef> parapheurs = new ArrayList<NodeRef>();

        for (NodeRef parapheur : getParapheurs()) {
            if (isParapheurSecretaire(parapheur, userName)) {
                parapheurs.add(parapheur);
            }
        }

        return parapheurs;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getNomParapheur(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getNomParapheur(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "Node Ref doit être un ph:parapheur");
        return this.nodeService.getProperty(parapheur, ContentModel.PROP_TITLE).toString();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getNomProprietaire(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getNomProprietaire(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "Node Ref doit être un ph:parapheur");
        String parapheurOwner = getParapheurOwner(parapheur);
        if (parapheurOwner != null && !parapheurOwner.trim().equals("")) {
            NodeRef proprietaireRef = personService.getPerson(parapheurOwner);
            String firstName = (String) nodeService.getProperty(proprietaireRef, ContentModel.PROP_FIRSTNAME);
            String lastName = (String) nodeService.getProperty(proprietaireRef, ContentModel.PROP_LASTNAME);
            return firstName + (lastName != null ? (" " + lastName) : "");
        } else {
            return "Non-attribué";
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getPrenomNomFromLogin(String)
     */
    public String getPrenomNomFromLogin(String userName) {
        Assert.hasText(userName, "userName ne doit pas être vide");
        NodeRef personneRef = personService.getPerson(userName);
        Assert.notNull(personneRef, "userName ne correspond à aucun utilisateur déclaré");
        String firstName = (String) nodeService.getProperty(personneRef, ContentModel.PROP_FIRSTNAME);
        String lastName = (String) nodeService.getProperty(personneRef, ContentModel.PROP_LASTNAME);
        return firstName + (lastName != null ? (" " + lastName) : "");
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#createDossier(org.alfresco.service.cmr.repository.NodeRef,
     *      java.util.Map)
     */
    public NodeRef createDossier(NodeRef parapheur, Map<QName, Serializable> properties) {
        Assert.isTrue(properties.containsKey(ContentModel.PROP_NAME), "La propriété \"cm:name\" n'a pas été passée en paramètre.");
        Assert.isTrue(isParapheur(parapheur), "Node Ref doit être un ph:parapheur");

        // On récupère la corbeille dans laquelle créer le dossier
        NodeRef corbeille = getCorbeille(parapheur, ParapheurModel.NAME_EN_PREPARATION);

        // On récupère le nom du dossier dans la Map
        String nom = (String) properties.get(ContentModel.PROP_NAME);

        FileInfo fileInfo = fileFolderService.create(corbeille, nom, ParapheurModel.TYPE_DOSSIER);
        NodeRef dossier = fileInfo.getNodeRef();

        // ajout des propriétés
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();
        // propriétés par défaut
        props.put(ContentModel.PROP_CREATOR, this.authenticationService.getCurrentUserName());
        props.put(ContentModel.PROP_CREATED, new Date());
        props.put(ParapheurModel.PROP_DATE_LIMITE, null);
        props.put(ParapheurModel.PROP_CONFIDENTIEL, false);
        props.put(ParapheurModel.PROP_SIGNATURE_PAPIER, false);
        props.put(ParapheurModel.PROP_TERMINE, false);
        props.put(ParapheurModel.PROP_RECUPERABLE, false);
        // FIXME : propriété de Folder mais non présente dans ContModel.java, donc rajouté à ParapheurModel.java;
        // éventuellement à modifier si ca change dans les prochaines versions d'alfresco
        props.put(ParapheurModel.PROP_ORDERED_CHILDREN, true);
        // merge avec les propriétés du dossier (properties override props)
        props.putAll(properties);
        this.nodeService.setProperties(dossier, props);

        Map<QName, Serializable> pptes = new HashMap<QName, Serializable>();
        pptes.put(ParapheurModel.PROP_EFFECTUEE, false);
        pptes.put(ParapheurModel.PROP_PASSE_PAR, parapheur);
        pptes.put(ParapheurModel.PROP_ACTION_DEMANDEE, "VISA");
        this.nodeService.createNode(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE,
                ParapheurModel.ASSOC_DOSSIER_ETAPE, ParapheurModel.TYPE_ETAPE_CIRCUIT, pptes);

        permissionService.setInheritParentPermissions(dossier, false);

        List<Object> list = new ArrayList<Object>();
        list.add("NonLu");
        nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
        auditService.audit("ParapheurService", "Creation de dossier", dossier, list);

        return dossier;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setCircuit(org.alfresco.service.cmr.repository.NodeRef,
     *      java.util.List)
     */
    public void setCircuit(NodeRef dossier, List<EtapeCircuit> circuit) {
        Assert.isTrue(!isEmis(dossier), "Node Ref doit être un ph:dossier non-émis");
        if (circuit != null && !circuit.isEmpty()) {
            // On retire l'émetteur s'il est présent comme premier élément de la liste
            // ET que l'étape en question est un VISA !!!
            NodeRef emetteur = getEmetteur(dossier);
            if (emetteur.equals(circuit.get(0).getParapheur()) &&
                    circuit.get(0).getActionDemandee().trim().equalsIgnoreCase("VISA")) {   // On ne modifie pas directement la liste...
                circuit = circuit.subList(1, circuit.size());
            }
//	    // On vérifie qu'il n'y a pas de doublons
//	    HashSet<NodeRef> hashTest = new HashSet<NodeRef>(circuit);
//	    hashTest.add(emetteur);
//	    Assert.isTrue(hashTest.size() == circuit.size() + 1, "Il y a des doublons dans le circuit");
        }

        NodeRef premiereEtape = getFirstChild(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE);
        // On vide le circuit précédent au cas où, à part la première étape
        NodeRef etapeSupprim = getFirstChild(premiereEtape, ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE);
        if (etapeSupprim != null) {
            this.nodeService.deleteNode(etapeSupprim);
        }

        if (circuit != null && !circuit.isEmpty()) {
            // Ajout des étapes correspondant aux parapheurs de la liste
            Map<QName, Serializable> proprietes = new HashMap<QName, Serializable>();
            proprietes.put(ParapheurModel.PROP_EFFECTUEE, false);
            ChildAssociationRef assocPrecedent;
            NodeRef etapePrecedenteRef = premiereEtape;
            String actionArchivage = "";
            for (EtapeCircuit etape : circuit) {
                if (EtapeCircuit.TRANSITION_PARAPHEUR.equals(etape.getTransition())) {
                    proprietes.put(ParapheurModel.PROP_PASSE_PAR, etape.getParapheur());
                } else if (EtapeCircuit.TRANSITION_CHEF_DE.equals(etape.getTransition())) {
                    // Si N+1 n'a pas de responsable, N est assigné à l'étape.
                    NodeRef acteurPrecedent = (NodeRef) nodeService.getProperty(etapePrecedenteRef, ParapheurModel.PROP_PASSE_PAR);
                    NodeRef responsable = getParapheurResponsable(acteurPrecedent);
                    NodeRef acteur = (responsable != null ? responsable : acteurPrecedent);
                    proprietes.put(ParapheurModel.PROP_PASSE_PAR, acteur);
                } else if (EtapeCircuit.TRANSITION_EMETTEUR.equals(etape.getTransition())) {
                    proprietes.put(ParapheurModel.PROP_PASSE_PAR, getEmetteur(dossier));
                } else {
                    throw new IllegalArgumentException("Unknown workflow transition: " + etape.getTransition());
                }
                actionArchivage = etape.getActionDemandee().trim();
                proprietes.put(ParapheurModel.PROP_ACTION_DEMANDEE, actionArchivage);
                assocPrecedent = this.nodeService.createNode(etapePrecedenteRef,
                        ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE, ParapheurModel.ASSOC_DOSSIER_ETAPE,
                        ParapheurModel.TYPE_ETAPE_CIRCUIT, proprietes);
                etapePrecedenteRef = assocPrecedent.getChildRef();
                this.nodeService.setProperty(etapePrecedenteRef, ParapheurModel.PROP_LISTE_NOTIFICATION, (Serializable) etape.getListeNotification());
            }
            if (!actionArchivage.equalsIgnoreCase(EtapeCircuit.ETAPE_ARCHIVAGE)) {   // si pas d'étape ARCHIVAGE explicite , on en ajoute une
                proprietes.put(ParapheurModel.PROP_PASSE_PAR, getEmetteur(dossier));
                proprietes.put(ParapheurModel.PROP_ACTION_DEMANDEE, EtapeCircuit.ETAPE_ARCHIVAGE);
                this.nodeService.createNode(etapePrecedenteRef,
                        ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE, ParapheurModel.ASSOC_DOSSIER_ETAPE,
                        ParapheurModel.TYPE_ETAPE_CIRCUIT, proprietes);
            }
        }
    }

    public void appendCircuit(NodeRef dossier, List<EtapeCircuit> circuit) {
        Assert.notNull(dossier, "'dossier' is a mandatory parameter");

        NodeRef lastEtape = getFirstChild(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE);
        while (getFirstChild(lastEtape, ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE) != null) {
            lastEtape = getFirstChild(lastEtape, ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE);
        }

        if ("ARCHIVAGE".equals(nodeService.getProperty(lastEtape, ParapheurModel.PROP_ACTION_DEMANDEE))) {
            nodeService.setProperty(lastEtape, ParapheurModel.PROP_ACTION_DEMANDEE, "VISA");
        }
        
        for (EtapeCircuit etape : circuit) {
            Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
            properties.put(ParapheurModel.PROP_EFFECTUEE, false);
            if (EtapeCircuit.TRANSITION_PARAPHEUR.equals(etape.getTransition())) {
                properties.put(ParapheurModel.PROP_PASSE_PAR, etape.getParapheur());
            } else if (EtapeCircuit.TRANSITION_CHEF_DE.equals(etape.getTransition())) {
                // Si N+1 n'a pas de responsable, N est assigné à l'étape.
                NodeRef acteurPrecedent = (NodeRef) nodeService.getProperty(lastEtape, ParapheurModel.PROP_PASSE_PAR);
                NodeRef responsable = getParapheurResponsable(acteurPrecedent);
                NodeRef acteur = (responsable != null ? responsable : acteurPrecedent);
                properties.put(ParapheurModel.PROP_PASSE_PAR, acteur);
            } else if (EtapeCircuit.TRANSITION_EMETTEUR.equals(etape.getTransition())) {
                properties.put(ParapheurModel.PROP_PASSE_PAR, getEmetteur(dossier));
            } else {
                throw new IllegalArgumentException("Unknown workflow transition: " + etape.getTransition());
            }
            properties.put(ParapheurModel.PROP_ACTION_DEMANDEE, etape.getActionDemandee().trim());
            properties.put(ParapheurModel.PROP_LISTE_NOTIFICATION, (Serializable) etape.getListeNotification());
            lastEtape = this.nodeService.createNode(lastEtape,
                    ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE, ParapheurModel.ASSOC_DOSSIER_ETAPE,
                    ParapheurModel.TYPE_ETAPE_CIRCUIT, properties).getChildRef();
        }
        if (!EtapeCircuit.ETAPE_ARCHIVAGE.equalsIgnoreCase((String) nodeService.getProperty(lastEtape, ParapheurModel.PROP_ACTION_DEMANDEE))) {
            Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
            properties.put(ParapheurModel.PROP_PASSE_PAR, getEmetteur(dossier));
            properties.put(ParapheurModel.PROP_EFFECTUEE, false);
            properties.put(ParapheurModel.PROP_ACTION_DEMANDEE, EtapeCircuit.ETAPE_ARCHIVAGE);
            this.nodeService.createNode(lastEtape,
                    ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE, ParapheurModel.ASSOC_DOSSIER_ETAPE,
                    ParapheurModel.TYPE_ETAPE_CIRCUIT, properties);
        }
    }

    public void restartCircuit(NodeRef dossier, String type, String subtype, String workflow) {
        nodeService.setProperty(dossier, ParapheurModel.PROP_TYPE_METIER, type);
        nodeService.setProperty(dossier, ParapheurModel.PROP_SOUSTYPE_METIER, subtype);
        nodeService.setProperty(dossier, ParapheurModel.PROP_WORKFLOW, workflow);
        nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, false);

        nodeService.moveNode(dossier,
                getCorbeille(getParentParapheur(dossier), ParapheurModel.NAME_EN_PREPARATION),
                ContentModel.ASSOC_CONTAINS,
                nodeService.getPrimaryParent(dossier).getQName());
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setDiffusion(org.alfresco.service.cmr.repository.NodeRef,
     *      java.util.Set)
    public void setDiffusion(NodeRef dossier, Set<NodeRef> diffusion)
    {
    Assert.isTrue(isDossier(dossier));

    this.nodeService.setProperty(dossier, ParapheurModel.PROP_LISTE_DIFFUSION, (Serializable) diffusion);
    }
     */
    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#createParapheur(java.util.Map)
     */
    public NodeRef createParapheur(Map<QName, Serializable> properties) {
        Assert.isTrue(properties.containsKey(ContentModel.PROP_NAME),
                "La propriété \"cm:name\" n'a pas été passée en paramètre.");

        // On récupère le nom du parpaheur dans la Map
        String nom = (String) properties.get(ContentModel.PROP_NAME);

        // On récupère le répertoire de stockage des parapheurs
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.parapheurs.childname");

        List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException e) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Erreur lors de l'accès au répertoire de stockage des parapheurs");
            }
            return null;
        }

        if (results == null || results.size() != 1) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Erreur lors de la récupération du répertoire de stockage des parapheurs");
            }
            return null;
        }

        NodeRef parapheursRep = results.get(0);

        // Création du parapheur
        FileInfo fileInfo = fileFolderService.create(parapheursRep, nom, ParapheurModel.TYPE_PARAPHEUR);
        NodeRef parapheur = fileInfo.getNodeRef();

        // Ajout des propriétés
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();
        // propriétés par défaut
        props.put(ContentModel.PROP_CREATOR, this.authenticationService.getCurrentUserName());
        props.put(ContentModel.PROP_CREATED, new Date());
        props.putAll(properties);
        this.nodeService.setProperties(parapheur, props);

        // Création des actions de mail, utilisées par les règles
	/*
         * Action mailDiffusionEmission = actionService.createAction("parapheur-mail");
         * mailDiffusionEmission.setParameterValue("dest","diff");
         * mailDiffusionEmission.setParameterValue("template","diffusion-emission.ftl");
         * mailDiffusionEmission.setExecuteAsynchronously(false); Action mailCurrentReception =
         * actionService.createAction("parapheur-mail"); mailCurrentReception.setParameterValue("dest","current");
         * mailCurrentReception.setParameterValue("template","current-reception.ftl");
         * mailCurrentReception.setExecuteAsynchronously(false); Action mailOwnerReception =
         * actionService.createAction("parapheur-mail"); mailOwnerReception.setParameterValue("dest","owner");
         * mailOwnerReception.setParameterValue("template","owner-reception.ftl");
         * mailOwnerReception.setExecuteAsynchronously(false); Action mailDiffusionVisa =
         * actionService.createAction("parapheur-mail"); mailDiffusionVisa.setParameterValue("dest","diff");
         * mailDiffusionVisa.setParameterValue("template","diffusion-visa.ftl");
         * mailDiffusionVisa.setExecuteAsynchronously(false); Action mailOwnerArchivage =
         * actionService.createAction("parapheur-mail"); mailOwnerArchivage.setParameterValue("dest","owner");
         * mailOwnerArchivage.setParameterValue("template","owner-archivage.ftl");
         * mailOwnerArchivage.setExecuteAsynchronously(false); Action mailOwnerRetour =
         * actionService.createAction("parapheur-mail"); mailOwnerRetour.setParameterValue("dest","owner");
         * mailOwnerRetour.setParameterValue("template","owner-retour.ftl");
         * mailOwnerRetour.setExecuteAsynchronously(false);
         */

        // Création des corbeilles & corbeilles virtuelles
        Map<QName, Serializable> proprietesC = new HashMap<QName, Serializable>();
        proprietesC.put(ContentModel.PROP_NAME, "Dossiers à transmettre");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers à transmettre");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers en cours de préparation ou prêts à être émis.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_EN_PREPARATION,
                ParapheurModel.TYPE_CORBEILLE, proprietesC);
        /*
         * Rule rule = ruleService.createRule("outbound"); rule.setTitle("ParapheurMail");
         * rule.addAction(mailDiffusionEmission); ruleService.saveRule(childRefCorbeille.getChildRef(),rule);
         */

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers à traiter");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers à traiter");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers à viser ou à signer.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_A_TRAITER,
                ParapheurModel.TYPE_CORBEILLE, proprietesC);
        /*
         * rule = ruleService.createRule("inbound"); rule.setTitle("ParapheurMail"); rule.addAction(mailOwnerReception);
         * rule.addAction(mailCurrentReception); ruleService.saveRule(childRefCorbeille.getChildRef(),rule); rule =
         * ruleService.createRule("outbound"); rule.setTitle("ParapheurMail2"); rule.addAction(mailDiffusionVisa);
         * ruleService.saveRule(childRefCorbeille.getChildRef(),rule);
         */

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers en fin de circuit");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers en fin de circuit");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers ayant terminé leur circuit de validation.");
        proprietesC.put(ApplicationModel.PROP_ICON, "space-icon-default");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_A_ARCHIVER,
                ParapheurModel.TYPE_CORBEILLE, proprietesC);
        /*
         * rule = ruleService.createRule("inbound"); rule.setTitle("ParapheurMail"); rule.addAction(mailOwnerArchivage);
         * ruleService.saveRule(childRefCorbeille.getChildRef(),rule);
         */

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers retournés");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers retournés");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers rejetés lors du circuit de signature/visa.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_RETOURNES,
                ParapheurModel.TYPE_CORBEILLE, proprietesC);
        /*
         * rule = ruleService.createRule("inbound"); rule.setTitle("ParapheurMail"); rule.addAction(mailOwnerRetour);
         * ruleService.saveRule(childRefCorbeille.getChildRef(),rule);
         */

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers en cours");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers en cours");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers qui ont été émis.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_EN_COURS,
                ParapheurModel.TYPE_CORBEILLE_VIRTUELLE, proprietesC);

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers à venir");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers à venir");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers à venir");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_A_VENIR,
                ParapheurModel.TYPE_CORBEILLE_VIRTUELLE, proprietesC);

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers récupérables");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers récupérables");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers sur lesquels on peut exercer un droit de remords.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_RECUPERABLES,
                ParapheurModel.TYPE_CORBEILLE_VIRTUELLE, proprietesC);

        proprietesC.put(ContentModel.PROP_NAME, "Dossiers à relire - annoter");
        proprietesC.put(ContentModel.PROP_TITLE, "Dossiers à relire - annoter");
        proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers envoyés au secrétariat pour relecture - annotation.");
        nodeService.createNode(parapheur, ContentModel.ASSOC_CONTAINS, ParapheurModel.NAME_SECRETARIAT,
                ParapheurModel.TYPE_CORBEILLE, proprietesC);

        authorityService.createAuthority(AuthorityType.ROLE, "PHOWNER_" + nom);

        return parapheur;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getEmetteur(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getEmetteur(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        NodeRef emetteur = getFirstChild(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE);
        if (emetteur != null) {
            return (NodeRef) this.nodeService.getProperty(emetteur, ParapheurModel.PROP_PASSE_PAR);
        } else {
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isEmis(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isEmis(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        NodeRef emetteur = getFirstChild(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE);
        if (emetteur != null) {
            return (Boolean) this.nodeService.getProperty(emetteur, ParapheurModel.PROP_EFFECTUEE);
        } else {
            return false;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isTermine(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isTermine(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        return (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_TERMINE);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getCircuit(org.alfresco.service.cmr.repository.NodeRef)
     */
    public List<EtapeCircuit> getCircuit(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        List<EtapeCircuit> circuit = new ArrayList<EtapeCircuit>();

        NodeRef emetteur = getFirstChild(dossier, ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE);
        if (emetteur == null || !this.nodeService.exists(emetteur)) {
            return null;
        }
        circuit.add(new EtapeCircuitImpl(nodeService, emetteur));

        for (NodeRef etape = getFirstChild(emetteur, ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE);
                etape != null && this.nodeService.exists(etape);
                etape = getFirstChild(etape, ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE)) {
            circuit.add(new EtapeCircuitImpl(nodeService, etape));
        }

        return circuit;
    }

    @SuppressWarnings("unchecked")
    public Set<NodeRef> getDiffusion(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        return new HashSet<NodeRef>((List<NodeRef>) this.nodeService.getProperty(dossier,
                ParapheurModel.PROP_LISTE_DIFFUSION));
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDiffusionToutesEtapes(org.alfresco.service.cmr.repository.NodeRef)
     */
    @SuppressWarnings("unchecked")
    public Set<NodeRef> getDiffusionToutesEtapes(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        Set<NodeRef> res = new HashSet<NodeRef>();
        List<EtapeCircuit> circuit = this.getCircuit(dossier);
        if (circuit.get(0).getActionDemandee() == null) {
            return new HashSet<NodeRef>((List<NodeRef>) this.nodeService.getProperty(dossier, ParapheurModel.PROP_LISTE_DIFFUSION));
        } else {
            for (EtapeCircuit etape : circuit) {
                Set<NodeRef> liste = etape.getListeNotification();
                if (liste != null) {
                    res.addAll(liste);
                }
            }
            return res;
        }
    }

    public void removeFromDiffusionToutesEtapes(NodeRef dossier, NodeRef parapheurRef) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");
        Assert.isTrue(isParapheur(parapheurRef), "Node Ref doit être un ph:parapheur");
        List<EtapeCircuit> circuit = this.getCircuit(dossier);
        if (circuit.get(0).getActionDemandee() == null) {   // compatibilité ascendante
            Set<NodeRef> setDiffusion = this.getDiffusion(dossier);
            if (setDiffusion.contains(parapheurRef)) {
                setDiffusion.remove(parapheurRef);
                this.nodeService.setProperty(dossier, ParapheurModel.PROP_LISTE_DIFFUSION, (Serializable) setDiffusion);
            }
        } else {
            for (EtapeCircuit etape : circuit) {
                Set<NodeRef> setDiffusion = etape.getListeNotification();
                if (setDiffusion.contains(parapheurRef)) {
                    setDiffusion.remove(parapheurRef);
                    etape.setAndRecordListeDiffusion(nodeService, setDiffusion);
                }
            }
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getActeurCourant(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getActeurCourant(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        if (etapeCourante == null) {
            if (logger.isEnabledFor(Level.DEBUG)) {
                logger.debug("Le dossier n'a pas d'étape courante : id = " + dossier);
            }

            return null;
        }
        NodeRef parapheur = etapeCourante.getParapheur();

        return getParapheurOwner(parapheur);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isActeurCourant(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    public boolean isActeurCourant(NodeRef dossier, String userName) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        if (etapeCourante == null) {
            if (logger.isEnabledFor(Level.DEBUG)) {
                logger.debug("Le dossier n'a pas d'étape courante : id = " + dossier);
            }

            return false;
        }
        NodeRef parapheur = etapeCourante.getParapheur();

        return isParapheurOwner(parapheur, userName);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getAnnotationPublique(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getAnnotationPublique(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante (il est terminé ou dans un état invalide)");

        return etapeCourante.getAnnotation();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setAnnotationPublique(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    public void setAnnotationPublique(NodeRef dossier, String annotation) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante (il est terminé ou dans un état invalide)");

        this.nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_ANNOTATION, annotation);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getAnnotationPrivee(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getAnnotationPrivee(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante (il est terminé ou dans un état invalide)");

        return etapeCourante.getAnnotationPrivee();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setAnnotationPrivee(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    public void setAnnotationPrivee(NodeRef dossier, String annotation) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante (il est terminé ou dans un état invalide)");

        this.nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_ANNOTATION_PRIVEE, annotation);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getAnnotationPriveePrecedente(org.alfresco.service.cmr.repository.NodeRef)
     */
    public String getAnnotationPriveePrecedente(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        List<EtapeCircuit> circuit = this.getCircuit(dossier);
        EtapeCircuit etapePrecedente = null;
        if (circuit != null) {
            for (EtapeCircuit etape : circuit) {
                if (etape.isApproved()) {
                    etapePrecedente = etape;
                } else {
                    break;
                }
            }
        }

        if (etapePrecedente != null) {
            return etapePrecedente.getAnnotationPrivee();
        } else {
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setSignataire(org.alfresco.service.cmr.repository.NodeRef,
     *      java.lang.String)
     */
    public void setSignataire(NodeRef dossier, String signataire) {
        setSignataire(dossier, signataire, null);
    }

    public void setSignataire(NodeRef dossier, String signataire, X509Certificate[] cert) {
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante (il est terminé ou dans un état invalide)");

        NodeRef etapeRef = etapeCourante.getNodeRef();
        this.nodeService.setProperty(etapeRef, ParapheurModel.PROP_SIGNATAIRE, signataire);
        Boolean signaturePapier = (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_PAPIER);
        if (!signaturePapier && cert != null && cert.length > 0) {
            String signature = new StringBuilder("Certificat émis par \"").append(extractCN(cert[0].getIssuerX500Principal().getName())).append("\" pour le compte de ").append(extractCN(cert[0].getSubjectX500Principal().getName())).append(".").toString();
            this.nodeService.setProperty(etapeRef, ParapheurModel.PROP_SIGNATURE, signature);
        }
    }

    private static String extractCN(String dn) {
        if (dn == null || dn.length() < 4) {
            return "<inconnu>";
        }
        int i = dn.indexOf("CN=");
        if (i < 0) {
            return "<inconnu>";
        }
        i += 3;
        int j = dn.indexOf(",", i);
        if (j - 1 < 0) {
            return "<inconnu>";
        }
        return dn.substring(i, j).trim();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#approve(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void approve(NodeRef dossier) {
        logger.debug("APPROVE - Entree dans la methode");
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        String username = this.authenticationService.getCurrentUserName();
        NodeRef parapheurRef = getParentParapheur(dossier);
        Boolean signaturePapier = (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_PAPIER);
        List<EtapeCircuit> circuit = getCircuit(dossier);

        Assert.notEmpty(circuit, "Le dossier n'a pas de circuit de validation: id = " + dossier);
        Assert.isTrue(circuit.size() > 1, "Impossible d'émettre le dossier, aucun circuit n'a été défini.");
        // Récupération de la prochaine étape
        EtapeCircuitImpl etapeCourante = null;
        EtapeCircuit etapeSuivante = null;
        for (EtapeCircuit etape : circuit) {
            if (!etape.isApproved()) {
                if (etapeCourante == null) {
                    etapeCourante = (EtapeCircuitImpl) etape;
                } else if (etapeSuivante == null) {
                    etapeSuivante = etape;
                    break;
                }
            }
        }
        Assert.notNull(etapeCourante, "(APPROVE) Le dossier n'a pas d'étape courante: id = " + dossier);
        String actionDemandee = etapeCourante.getActionDemandee();
        if (actionDemandee == null) {
            logger.debug("Approve sur un vieux circuit... COMPATIBILITE ASCENDANTE !");
            approveBeforeV3(dossier);
        } else {
            actionDemandee = actionDemandee.trim();
            // Vérification du "droit" à approuver
            if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_VISA)) {   // VISA: Durant le circuit, seul l'acteur courant peut viser; pas d'obligation de lecture
                Assert.isTrue(isActeurCourant(dossier, username), "Vous ("+username+") n'êtes pas acteur courant de ce dossier!");
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {   // SIGNATURE:  vérification de la lecture des documents
                // Si la signature est papier, le secrétariat a la possibilité de confirmer l'approbation
                if (signaturePapier) {
                    Assert.isTrue(isActeurCourant(dossier, username) || isParapheurSecretaire(parapheurRef, username),
                            "Vous ("+username+") n'êtes pas acteur courant de ce dossier, ni membre du secrétariat!");
                } else {
                    Assert.isTrue(isActeurCourant(dossier, username), "Vous ("+username+") n'êtes pas acteur courant de ce dossier!");
                }
                // La lecture du premier document est nécessaire et suffisante
                if (isReadingMandatory(dossier)) {
                    Assert.isTrue(nodeService.hasAspect(getDocuments(dossier).get(0), ParapheurModel.ASPECT_LU), "Le document à signer n'a pas été lu.");
                }
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT)) {
                Assert.isTrue(isActeurCourant(dossier, username) || isParapheurSecretaire(parapheurRef, username),
                        "Vous ("+username+") n'êtes pas acteur courant de ce dossier, ni membre du secrétariat!");
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                // TODO
                throw new RuntimeException("L'action demandée " + actionDemandee +
                        " est impossible dans ce contexte. Veuillez informer votre administrateur de ce problème, ou retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
            } else { //  (actionDemandee=ARCHIVAGE ou autre n'a pas sa place ici)
                throw new RuntimeException("L'action demandée " + actionDemandee +
                        " est inconnue. Veuillez informer votre administrateur de ce problème, ou retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
            }
            // DETERMINER LA CORBEILLE DE DESTINATION
            NodeRef corbeille = null;
            if (etapeSuivante != null) {
                NodeRef parapheur = etapeSuivante.getParapheur();
                if (!this.nodeService.exists(parapheur)) {
                    throw new RuntimeException("Le parapheur suivant n'a pu être déterminé. Veuillez retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
                }

                NodeRef titulaire = new NodeRef(parapheur.toString());
                // on suit l'arbre de délégation (lève une exception s'il y a une boucle)
                parapheur = followDelegation(parapheur);
                if (this.getParapheurOwner(parapheur) == null || this.getParapheurOwner(parapheur).equals("")) {
                    throw new RuntimeException("Le parapheur suivant est actuellement sans propriétaire. Veuillez informer votre administrateur de ce problème, ou retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
                }

                if (!titulaire.equals(parapheur)) {   // Traiter la trace sur delegation: ASPECT "DELEGATION" sur étape suivante
                    logger.debug("APPROVE - Aspect DELEGATION set");
                    Map<QName, Serializable> delegProps = new HashMap<QName, Serializable>();
                    delegProps.put(ParapheurModel.PROP_DELEGATEUR, titulaire);
                    this.nodeService.addAspect(((EtapeCircuitImpl) etapeSuivante).getNodeRef(), ParapheurModel.ASPECT_ETAPE_DELEGATION, delegProps);
                } else {
                    logger.debug("APPROVE -   pas de délégation");
                }
                logger.debug("APPROVE - Propriete PASSE_PAR set");
                nodeService.setProperty(((EtapeCircuitImpl) etapeSuivante).getNodeRef(), ParapheurModel.PROP_PASSE_PAR, parapheur);

                if (etapeSuivante.getActionDemandee().trim().equalsIgnoreCase(EtapeCircuit.ETAPE_ARCHIVAGE)) {
                    corbeille = getCorbeille(parapheur, ParapheurModel.NAME_A_ARCHIVER);
                } else {
                    corbeille = getCorbeille(parapheur, ParapheurModel.NAME_A_TRAITER);
                }
                // AUDIT SUR ETAPE COURANTE
                String annotation = etapeCourante.getAnnotation();
                if (annotation != null) {
                    annotation = annotation.trim();
                }
                String strStatus = "";
                String strMessage = "";
                if (!isEmis(dossier)) {
                    strStatus = "NonLu";
                    strMessage = "Emission du dossier";
                } // VISA, SIGNATURE, TDT, DIFF_EMAIL, (pas ARCHIVAGE)
                else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_VISA)) {
                    strStatus = "Vise";
                    strMessage = "Visa sur dossier";
                } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                    strStatus = "Signe";
                    strMessage = "Signature sur dossier";
                } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT) ||
                        actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                    strStatus = "TransmissionOK";
                    strMessage = "Dossier télétransmis";
                }
                List<Object> list = new ArrayList<Object>();
                list.add(strStatus);
                if (annotation != null && !annotation.trim().isEmpty()) {
                    auditService.audit("ParapheurService", annotation, dossier, list);
                } else {
                    auditService.audit("ParapheurService", strMessage, dossier, list);
                }

            } else {   // ERREUR !!!!! : on ne doit pas arriver ici!!
                logger.error("APPROVE - Etape suivante=null !?! Renvoi à Archiver chez l'émetteur...");
                NodeRef emetteur = getEmetteur(dossier);
                if (!this.nodeService.exists(emetteur)) {
                    throw new RuntimeException("Le dossier n'a pas d'émetteur: id = " + dossier);
                }
                if (this.getParapheurOwner(emetteur) == null || this.getParapheurOwner(emetteur).equals("")) {
                    throw new RuntimeException("Le parapheur émetteur est actuellement sans propriétaire. Veuillez informer votre administrateur de ce problème.");
                }
                corbeille = getCorbeille(emetteur, ParapheurModel.NAME_A_ARCHIVER);

                List<Object> list = new ArrayList<Object>();
                list.add("Signe");
                nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "Signe");
                String annotation = etapeCourante.getAnnotation();
                if (annotation == null || annotation.trim().isEmpty()) {
                    auditService.audit("ParapheurService", "Signature du dossier", dossier, list);
                } else {
                    auditService.audit("ParapheurService", annotation, dossier, list);
                }
            }

            // DEPLACEMENT du dossier vers la corbeille de destination
            ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
            Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);
            if (logger.isDebugEnabled()) {
                logger.debug("APPROVE - Deplacement du noeud");
            }
            try {
                nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
                if (logger.isInfoEnabled()) {
                    logger.info("APPROVE : Dossier confié au parapheur ["+
                            this.getNomParapheur(this.getParentParapheur(corbeille))+"]");
                }
            } catch (DuplicateChildNodeNameException ex) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans le parapheur de destination");
                }
                throw new RuntimeException("Un dossier de même nom existe dans le parapheur de destination", ex);
            } catch (org.hibernate.exception.ConstraintViolationException ex2) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans le parapheur de destination");
                }
                throw new RuntimeException("Un dossier de même nom existe dans le parapheur de destination", ex2);
            }

            // On a besoin de savoir si le dossier vient d'être émis pour l'envoi de mail
            boolean emis = isEmis(dossier);

            // Mise à jour des informations d'étape
            logger.debug("APPROVE - Propriete EFFECTUEE set");
            nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_EFFECTUEE, Boolean.TRUE);
            logger.debug("APPROVE - Propriete DATE_VALIDATION set");
            nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_DATE_VALIDATION, new Date());
            // Tant que le document principal n'a pas été lu, le dossier est "récupérable", hors signature!
            if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("APPROVE - Propriete RECUPERABLE set = FALSE car signature.");
                }
                nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.FALSE);
            } else {
                if (logger.isDebugEnabled())
                    logger.debug("APPROVE - Propriete RECUPERABLE set = TRUE");
                nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.TRUE);
            }
            if (etapeSuivante == null) {
                logger.error("etapeSuivante NULL?!! Malgré tout: APPROVE - Propriete TERMINE set");
                nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);
                if (signaturePapier) {
                    nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE, "Signature papier");
                }
            } else {
                logger.debug("APPROVE - Propriete STATUS_METIER set");
                String actDmdee = etapeSuivante.getActionDemandee().trim();
                String strStatus = "";
                String strMessage = "";
                if (actDmdee.equalsIgnoreCase(EtapeCircuit.ETAPE_VISA)) {
                    strStatus = "EnCoursVisa";
                    strMessage = "Dossier déposé chez " + this.getNomProprietaire(this.getParentParapheur(corbeille)) +" pour Visa";
                } else if (actDmdee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                    strStatus = "NonLu";
                    strMessage = "Dossier déposé chez " + this.getNomProprietaire(this.getParentParapheur(corbeille)) +" pour signature";
                    if (signaturePapier) {	    // La signature n'est pas électronique
                        logger.debug("APPROVE - Propriete SIGNATURE set");
                        nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE, "Signature papier");
                    }
                } else if (actDmdee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT) ||
                        actDmdee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                    strStatus = "PretTdT";
                    strMessage = "Dossier diffusable";
                } else if (actDmdee.equalsIgnoreCase(EtapeCircuit.ETAPE_ARCHIVAGE)) {
                    logger.debug("APPROVE - Propriete TERMINE set");
                    nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);
                    strStatus = "Archive";
                    strMessage = "Circuit terminé, dossier archivable";
                }
                nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, strStatus);
                List<Object> list = new ArrayList<Object>();
                list.add(strStatus);
                auditService.audit("ParapheurService", strMessage, dossier, list);
            }

            try // NOTIFICATIONS: Envoi de mails
            {
                if (emis) {   // Progression d'un dossier connu
                    if (EtapeCircuit.ETAPE_TDT.equalsIgnoreCase(actionDemandee)) {
                        mail("diff", "parapheur-diffusion-tdt-ok.ftl", dossier);
                    } else {
                        mail("diff", "parapheur-diffusion-visa.ftl", dossier);
                    }
                    if (etapeSuivante == null || etapeSuivante.getActionDemandee().trim().equalsIgnoreCase(EtapeCircuit.ETAPE_ARCHIVAGE)) {
                        // Le dossier vient de finir son circuit
                        if (EtapeCircuit.ETAPE_TDT.equalsIgnoreCase(actionDemandee)) {
                            mail("current", "parapheur-current-tdt-ok-archivage.ftl", dossier);
                            mail("tiers", "parapheur-tiers-tdt-ok-archivage.ftl", dossier);
                        } else { // VISA ou SIGNATURE avant ARCHIVAGE
                            mail("current", "parapheur-owner-archivage.ftl", dossier);
                            mail("tiers", "parapheur-tiers.ftl", dossier);
                        }
                    } else {   // Suppression du mail à l'émetteur webClient pour chaque étape
                        // mail("owner", "parapheur-owner-reception.ftl", dossier);
                        mail("current", "parapheur-current-reception.ftl", dossier);
                        // email à l'émetteur "WebService"
                        mail("tiers", "parapheur-tiers-visa.ftl", dossier);
                    }
                } else {   // C'est un nouveau dossier
                    mail("diff", "parapheur-diffusion-emission.ftl", dossier);
                    mail("current", "parapheur-current-reception.ftl", dossier);
                }
                if (signaturePapier && etapeSuivante != null && etapeSuivante.getActionDemandee().trim().equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {   // le dossier est à signer physiquement et le prochain acteur est signataire: on prévient son secrétariat
                    NodeRef etapeRef = ((EtapeCircuitImpl) etapeSuivante).getNodeRef();
                    List<ChildAssociationRef> childEtapes = nodeService.getChildAssocs(etapeRef);
                    if (childEtapes.isEmpty()) {
                        mail("secretariat", "parapheur-secretariat-signature.ftl", dossier);
                    }
                }
            } catch (Exception e) {
                logger.warn("Une erreur est survenue lors d'un envoi de mail : " + e.getMessage());
            }
        } // fin de vérif initiale sur la version d'implémentation de circuits

        logger.debug("APPROVE - Suppression de l'aspect LU pour tous les documents du dossier");
        setDossierNonLu(dossier);
        logger.debug("APPROVE - Sortie de la methode");
    }

    /**
     * Cette méthode ne sert que pour la traiter de vieux dossiers initiés avant
     * le passage en v3, donc avec le vieux système de circuits.
     * @param dossier
     */
    private void approveBeforeV3(NodeRef dossier) {
        logger.debug("approveBeforeV3 - Entree dans la methode");
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");
        String username = this.authenticationService.getCurrentUserName();
        NodeRef parapheurRef = getParentParapheur(dossier);
        Boolean signaturePapier = (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_PAPIER);
        List<EtapeCircuit> circuit = getCircuit(dossier);
        Assert.notEmpty(circuit, "Le dossier n'a pas de circuit de validation: id = " + dossier);
        Assert.isTrue(circuit.size() > 1, "Impossible d'émettre le dossier, aucun circuit n'a été défini.");

        // Récupération de la prochaine étape
        EtapeCircuitImpl etapeCourante = null;
        EtapeCircuit etapeSuivante = null;
        for (EtapeCircuit etape : circuit) {
            if (!etape.isApproved()) {
                if (etapeCourante == null) {
                    etapeCourante = (EtapeCircuitImpl) etape;
                } else if (etapeSuivante == null) {
                    etapeSuivante = etape;
                    break;
                }
            }
        }
        Assert.notNull(etapeCourante, "(approveBeforeV3) Le dossier n'a pas d'étape courante: id = " + dossier);

        // Vérification du "droit" à approuver (visa - signature)
        if (etapeSuivante == null) {   // SIGNATURE:  vérification de la lecture des documents
            // Si la signature est papier, le secrétariat a la possibilité de confirmer l'approbation
            if (signaturePapier) {
                Assert.isTrue(isActeurCourant(dossier, username) || isParapheurSecretaire(parapheurRef, username),
                        "Vous n'êtes pas acteur courant de ce dossier, ni membre du secrétariat!");
            } else {
                Assert.isTrue(isActeurCourant(dossier, username), "Vous n'êtes pas acteur courant de ce dossier!");
            }

            List<NodeRef> lstDocs = getDocuments(dossier);
            // La lecture du premier document est suffisante
            /* for (NodeRef doc : lstDocs) Assert.isTrue(nodeService.hasAspect(doc, ParapheurModel.ASPECT_LU),
             * "Un ou plusieurs documents n'ont pas été lus");	     */
            Assert.isTrue(nodeService.hasAspect(lstDocs.get(0), ParapheurModel.ASPECT_LU), "Le document à signer n'a pas été lu.");
        } else {   // VISA: Durant le circuit, seul l'acteur courant peut viser
            Assert.isTrue(isActeurCourant(dossier, username), "Vous n'êtes pas acteur courant de ce dossier!");
        }

        // DETERMINER LA CORBEILLE DE DESTINATION
        NodeRef corbeille = null;
        if (etapeSuivante != null) {
            NodeRef parapheur = etapeSuivante.getParapheur();
            if (!this.nodeService.exists(parapheur)) {
                throw new RuntimeException("Le parapheur suivant n'a pu être déterminé. Veuillez retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
            }

            // on suit l'arbre de délégation (lève une exception s'il y a une boucle)
            parapheur = followDelegation(parapheur);
            if (this.getParapheurOwner(parapheur) == null || this.getParapheurOwner(parapheur).equals("")) {
                throw new RuntimeException("Le parapheur suivant est actuellement sans propriétaire. Veuillez informer votre administrateur de ce problème, ou retourner ce dossier à son émetteur afin que celui-ci modifie le circuit de validation.");
            }

            logger.debug("approveBeforeV3 - Propriete PASSE_PAR set");
            nodeService.setProperty(((EtapeCircuitImpl) etapeSuivante).getNodeRef(), ParapheurModel.PROP_PASSE_PAR, parapheur);
            corbeille = getCorbeille(parapheur, ParapheurModel.NAME_A_TRAITER);

            List<Object> list = new ArrayList<Object>();
            list.add("NonLu");
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
            String annotation = etapeCourante.getAnnotation();
            if (annotation != null && !annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", annotation, dossier, list);
            } else {
                if (isEmis(dossier)) {
                    auditService.audit("ParapheurService", "Visa sur dossier", dossier, list);
                } else {
                    auditService.audit("ParapheurService", "Emission du dossier", dossier, list);
                }
            }
        } else {
            NodeRef emetteur = getEmetteur(dossier);
            if (!this.nodeService.exists(emetteur)) {
                throw new RuntimeException("Le dossier n'a pas d'émetteur: id = " + dossier);
            }
            if (this.getParapheurOwner(emetteur) == null || this.getParapheurOwner(emetteur).equals("")) {
                throw new RuntimeException("Le parapheur émetteur est actuellement sans propriétaire. Veuillez informer votre administrateur de ce problème.");
            }
            corbeille = getCorbeille(emetteur, ParapheurModel.NAME_A_ARCHIVER);

            List<Object> list = new ArrayList<Object>();
            list.add("Signe");
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "Signe");
            String annotation = etapeCourante.getAnnotation();
            if (annotation == null || annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", "Signature du dossier", dossier, list);
            } else {
                auditService.audit("ParapheurService", annotation, dossier, list);
            }
        }

        // DEPLACEMENT du dossier vers la corbeille de destination
        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);
        logger.debug("approveBeforeV3 - Deplacement du noeud");
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans le parapheur de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe dans le parapheur de destination", ex);
        }

        // On a besoin de savoir si le dossier vient d'être émis pour l'envoi de mail
        boolean emis = isEmis(dossier);

        // Mise à jour des informations d'étape
        logger.debug("approveBeforeV3 - Propriete EFFECTUEE set");
        nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_EFFECTUEE, Boolean.TRUE);
        logger.debug("approveBeforeV3 - Propriete DATE_VALIDATION set");
        nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_DATE_VALIDATION, new Date());

        // Tant que le document principal n'a pas été lu, le dossier est "récupérable"
        logger.debug("approveBeforeV3 - Propriete RECUPERABLE set");
        nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.TRUE);

        if (etapeSuivante == null) {   // Il s'agissait d'une étape de signature
            logger.debug("approveBeforeV3 - Propriete TERMINE set");
            nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);
            if (signaturePapier) {	    // La signature n'est pas électronique
                logger.debug("approveBeforeV3 - Propriete SIGNATURE set");
                nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE, "Signature papier");
            }
        }

        // NOTIFICATIONS: Envoi de mails
        try {
            if (emis) {   // Progression d'un dossier connu
                mail("diff", "parapheur-diffusion-visa.ftl", dossier);
                if (etapeSuivante == null) {   // Le dossier vient de finir son circuit
                    mail("owner", "parapheur-owner-archivage.ftl", dossier);
                    mail("tiers", "parapheur-tiers.ftl", dossier);
                // } else {
                    // Suppression du mail à l'émetteur pour chaque étape
                    // mail("owner", "parapheur-owner-reception.ftl", dossier);
                }
            } else {   // C'est un nouveau dossier
                mail("diff", "parapheur-diffusion-emission.ftl", dossier);
            }

            // Dans tous les cas, on prévient l'acteur courant
            mail("current", "parapheur-current-reception.ftl", dossier);

            if (signaturePapier && etapeSuivante != null) {   // le dossier est à signer physiquement et le prochain acteur est signataire: on prévient son secrétariat
                NodeRef etapeRef = ((EtapeCircuitImpl) etapeSuivante).getNodeRef();
                List<ChildAssociationRef> childEtapes = nodeService.getChildAssocs(etapeRef);
                if (childEtapes.isEmpty()) {
                    mail("secretariat", "parapheur-secretariat-signature.ftl", dossier);
                }
            }
        } catch (Exception e) {
            logger.warn("Une erreur est survenue lors d'un envoi de mail : " + e.getMessage());
        }
        logger.debug("approveBeforeV3 - Sortie de la methode");
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#auditTransmissionEnCours(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void auditTransmissionTDT(NodeRef dossier, String status, String message)
    {
        if (logger.isDebugEnabled()) {
            logger.debug("auditTransmissionTDT: "+
                status+ ", "+message);
        }
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        // TODO  auditer le fait qu'il y a une transmission en cours!
        EtapeCircuit etape = this.getCurrentEtapeCircuit(dossier);
        if (isTermine(dossier)) {
            // ça se complique... on n'est pas censé arriver là
            logger.warn("TRANSMISSION EN COURS sur Dossier Terminé???");
        }
        else if (!isEmis(dossier)) {
            // ça se complique... on n'est pas censé arriver là
            logger.warn("TRANSMISSION EN COURS sur Dossier pas émis ????");
        } 
        else {
            // AUDIT SUR ETAPE COURANTE: TDT, DIFF_EMAIL, (voire ARCHIVAGE)
            String annotation = etape.getAnnotation();
            String actionDemandee = etape.getActionDemandee();
            if (annotation != null) {
                annotation = annotation.trim();
            }
            String strStatus = "";
            String strMessage = "";
            if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT) ||
                    actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                strStatus = status;
                strMessage = message;
            }
            if (!strStatus.isEmpty()) {
                nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, strStatus);
            }

            List<Object> list = new ArrayList<Object>();
            list.add(strStatus);
            if (annotation != null && !annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", annotation, dossier, list);
            } else {
                auditService.audit("ParapheurService", strMessage, dossier, list);
            }
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#reject(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void reject(NodeRef dossier) {
        logger.debug("REJECT - Entree dans la methode");
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);

        String username = this.authenticationService.getCurrentUserName();
        Assert.isTrue(isActeurCourant(dossier, username), "Vous n'êtes pas acteur courant de ce dossier!");

        // On récupère la corbeille "dossiers retournés" de la première étape
        NodeRef emetteur = getEmetteur(dossier);
        Assert.notNull(emetteur, "Le dossier n'a pas d'émetteur: id = " + dossier);
        Assert.hasText(this.getParapheurOwner(emetteur),
                "Le parapheur émetteur est actuellement sans propriétaire. Veuillez informer votre administrateur de ce problème.");

        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) getCurrentEtapeCircuit(dossier);
        EtapeCircuit premiereEtape = getCircuit(dossier).get(0);
        Assert.isTrue(premiereEtape.isApproved(), "Impossible de rejeter le dossier, il n'a pas été émis.");

        nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_EFFECTUEE, Boolean.TRUE);
        nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_DATE_VALIDATION, new Date());

        NodeRef corbeille = getCorbeille(emetteur, ParapheurModel.NAME_RETOURNES);

        // AUDIT
        String actionDemandee = etapeCourante.getActionDemandee();
        String annotation = null;
        if (actionDemandee == null) {   // compatibilité ascendante
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "RejetSignataire");
            List<Object> list = new ArrayList<Object>();
            list.add("RejetSignataire");
            annotation = etapeCourante.getAnnotation();
            if (annotation == null || annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", "Rejet du dossier", dossier, list);
            } else {
                auditService.audit("ParapheurService", annotation, dossier, list);
            }
        } else {
            actionDemandee = actionDemandee.trim();
            annotation = etapeCourante.getAnnotation();
            if (annotation != null) {
                annotation = annotation.trim();
            }
            String strStatus = "";
            String strMessage = "";
            if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_VISA)) {
                strStatus = "RejetVisa";
                strMessage = "Dossier rejeté";
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                strStatus = "RejetSignataire";
                strMessage = "Dossier rejeté";
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT) ||
                    actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                strStatus = "RejetTransmission";
                strMessage = "Dossier rejeté";
                String nackReason = getNackMotif(dossier);
                if (nackReason != null && !nackReason.trim().isEmpty()) {
                    strMessage = nackReason;
                }
            }
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, strStatus);
            List<Object> list = new ArrayList<Object>();
            list.add(strStatus);
            if (annotation != null && !annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", annotation, dossier, list);
            } else {
                auditService.audit("ParapheurService", strMessage, dossier, list);
            }
        }

        // On déplace le dossier vers la corbeille
        logger.debug("REJECT - Deplacement du noeud");
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans le parapheur de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe dans le parapheur de destination", ex);
        }
        logger.debug("REJECT - Propriete TERMINE set");
        nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);

        mail("owner", "parapheur-owner-retour.ftl", dossier, annotation);
        mail("diff", "parapheur-diffusion-retour.ftl", dossier, annotation);
        mail("tiers", "parapheur-tiers-retour.ftl", dossier, annotation);

        // On retire l'aspect "lu" de tous les documents du dossier
        logger.debug("REJECT - Suppression de l'aspect LU pour tous les documents");
        setDossierNonLu(dossier);

        logger.debug("REJECT - Sortie de la methode");
    }

    private String getNackMotif(NodeRef dossier) {
        try {
            ContentReader reader = contentService.getReader(dossier, ParapheurModel.PROP_NACKHELIOS_XML);

            if (reader == null) {
                return null;
            }

            InputSource nack = new InputSource(reader.getContentInputStream());

            DOMParser parser = new DOMParser();
            parser.parse(nack);
            org.w3c.dom.Document doc = parser.getDocument();
            Node node = XPathAPI.selectSingleNode(doc, "/n:PES_NonAcquit/NonAcquit/Motif/@V", doc);

            return node.getTextContent();
        } catch (Exception ex) {
            logger.warn(ex);
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#reprendreDossier(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef reprendreDossier(NodeRef dossier) {
        logger.debug("REPRENDRE - Entree dans la methode");
        Assert.isTrue(isDossier(dossier), "Node Ref doit être un ph:dossier");

        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);

        // On récupère la corbeille "dossiers à transmettre" dans le parapheur de la première étape
        NodeRef emetteur = getEmetteur(dossier);
        Assert.notNull(emetteur, "Le dossier n'a pas d'émetteur: id = " + dossier);

        // On déplace le dossier vers la corbeille
        NodeRef corbeille = getCorbeille(emetteur, ParapheurModel.NAME_EN_PREPARATION);
        Assert.state(corbeille != null, "Le parapheur d'origine du dossier ne contient pas de corbeille \"à transmettre\" : id = " + dossier);
        logger.debug("REPRENDRE - Deplacement du noeud");
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
            List<Object> list = new ArrayList<Object>();
            list.add("NonLu");
            auditService.audit("ParapheurService", "Reprise du dossier", dossier, list);
        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de reprendre le dossier : un dossier de même nom existe dans la corbeille de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe parmi les dossiers en préparation", ex);
        }
        logger.debug("REPRENDRE - Propriete TERMINE set");
        nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.FALSE);
        for (EtapeCircuit etape : getCircuit(dossier)) {
            logger.debug("REPRENDRE - Modification des proprietes des etapes");
            logger.debug("REPRENDRE - Propriete EFFECTUEE set");
            nodeService.setProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_EFFECTUEE, Boolean.FALSE);
            logger.debug("REPRENDRE - Propriete ANNOTATION set");
            nodeService.setProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_ANNOTATION, "");
            logger.debug("REPRENDRE - Propriete ANNOTATION_PRIVEE set");
            nodeService.setProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_ANNOTATION_PRIVEE, "");
            logger.debug("REPRENDRE - Propriete DATE_VALIDATION set");
            nodeService.setProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_DATE_VALIDATION, null);
            logger.debug("REPRENDRE - Aspect DELEGATION remove");
            if (nodeService.hasAspect(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.ASPECT_ETAPE_DELEGATION)) {
                nodeService.setProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_PASSE_PAR, ((EtapeCircuitImpl) etape).getDelegateur());
                nodeService.removeAspect(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.ASPECT_ETAPE_DELEGATION);
            }
        }

        logger.debug("REPRENDRE - Ajout de l'aspect LU a tous les documents");
        setDossierLu(dossier);

        logger.debug("REPRENDRE - Sortie la methode");
        return dossier;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#recupererDossier(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void recupererDossier(NodeRef dossier) {
        logger.debug("RECUPERER - Entree dans la methode");
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");
        Assert.isTrue(!isTermine(dossier), "Le dossier est \"terminé\": id = " + dossier);
        Assert.isTrue(isEmis(dossier), "Le dossier n'a pas été émis: id = " + dossier);
        Assert.isTrue(isRecuperable(dossier), "Le dossier n'est pas récupérable: id = " + dossier);

        List<EtapeCircuit> circuit = getCircuit(dossier);
        EtapeCircuitImpl etapeCourante = null;
        EtapeCircuitImpl etapePrecedente = null;
        for (EtapeCircuit etape : circuit) {
            EtapeCircuitImpl etapeI = (EtapeCircuitImpl) etape;
            if (etapeI.isApproved()) {
                etapePrecedente = etapeI;
            } else {
                etapeCourante = etapeI;
                break;
            }
        }
        Assert.notNull(etapeCourante, "Le dossier n'a pas d'étape courante: id = " + dossier);
        Assert.notNull(etapePrecedente, "Le dossier n'a pas d'étape précédente: id = " + dossier);

        Assert.isTrue(getParapheurOwner(etapePrecedente.getParapheur()).equals(authenticationService.getCurrentUserName()));

        // On renvoie le dossier vers la corbeille de l'étape précédente
        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);

        NodeRef corbeille = null;
        // Si il s'agissait d'une émission, on renvoie dans la corbeille "à transmettre", sinon "à traiter"
        if (etapePrecedente.getNodeRef().equals(((EtapeCircuitImpl) circuit.get(0)).getNodeRef())) {
            corbeille = getCorbeille(etapePrecedente.getParapheur(), ParapheurModel.NAME_EN_PREPARATION);
        } else {
            corbeille = getCorbeille(etapePrecedente.getParapheur(), ParapheurModel.NAME_A_TRAITER);
        }

        logger.debug("RECUPERER - Deplacement du noeud");
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans la corbeille de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe dans la corbeille de destination", ex);
        }

        logger.debug("RECUPERER - Aspect DELEGATION remove");
        if (nodeService.hasAspect(((EtapeCircuitImpl) etapeCourante).getNodeRef(), ParapheurModel.ASPECT_ETAPE_DELEGATION)) {
            nodeService.setProperty(((EtapeCircuitImpl) etapeCourante).getNodeRef(), ParapheurModel.PROP_PASSE_PAR, ((EtapeCircuitImpl) etapeCourante).getDelegateur());
            nodeService.removeAspect(((EtapeCircuitImpl) etapeCourante).getNodeRef(), ParapheurModel.ASPECT_ETAPE_DELEGATION);
        }
        // AUDIT
        String actionDemandee = etapePrecedente.getActionDemandee();
        if (actionDemandee == null) {   // compatibilité ascendante
            nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
            List<Object> list = new ArrayList<Object>();
            list.add("NonLu");
            auditService.audit("ParapheurService", "Recuperation du dossier", dossier, list);
        } else {
            actionDemandee = actionDemandee.trim();
            String annotation = etapeCourante.getAnnotation();
            if (annotation != null) {
                annotation = annotation.trim();
            }
            String strStatus = "";
            String strMessage = "";
            if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_VISA)) {
                strStatus = "EnCoursVisa";
                strMessage = "Recuperation du dossier";
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                strStatus = "NonLu";
                strMessage = "Recuperation du dossier";
            } else if (actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_TDT) ||
                    actionDemandee.equalsIgnoreCase(EtapeCircuit.ETAPE_DIFF_EMAIL)) {
                strStatus = "PretTdT";
                strMessage = "Recuperation du dossier";
            }
            List<Object> list = new ArrayList<Object>();
            list.add(strStatus);
            if (annotation != null && !annotation.trim().isEmpty()) {
                auditService.audit("ParapheurService", annotation, dossier, list);
            } else {
                auditService.audit("ParapheurService", strMessage, dossier, list);
            }
        }

        // On met à jour les informations d'étape
        logger.debug("RECUPERER - Propriete EFFECTUEE set");
        nodeService.setProperty(etapePrecedente.getNodeRef(), ParapheurModel.PROP_EFFECTUEE, Boolean.FALSE);

        // En revanche, le dossier n'est plus récupérable
        logger.debug("RECUPERER - Propriete RECUPERABLE set");
        nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.FALSE);

        // Le dossier ayant déjà été lu une fois, on ajoute l'aspect aux documents
        logger.debug("RECUPERER - Ajout de l'aspect LU a tous les documents");
        setDossierLu(dossier);

        logger.debug("RECUPERER - Sortie de la methode");
    }

    private File genererPageSignatairesPDF(NodeRef dossier, String statutTdt, String ackDate) {
        // Récupération du template de la page de garde
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.templates.content.childname") + "/" + this.configuration.getProperty("templates.signataires.childname");
        List<NodeRef> results = null;
        results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        if (results != null && results.size() == 1) {  // Génération de la page de garde
            NodeRef templateRef = results.get(0);
            Map<String, Object> model = new HashMap<String, Object>();
            List<EtapeCircuit> circuit = this.getCircuit(dossier);

            model.put("etapes", circuit);
            model.put("dossier", dossier);
            model.put("space", dossier);
            model.put("statut", statutTdt);
            if (ackDate != null) {
                model.put("ackMIAT", ackDate);
            }

            // Création de la page de garde
            File tmpHtml = TempFileProvider.createTempFile("tmpconv", ".html");
            ContentWriter tmpWriterHtml = new FileContentWriter(tmpHtml);

            tmpWriterHtml.setMimetype(MimetypeMap.MIMETYPE_HTML);
            tmpWriterHtml.setEncoding("utf-8");
            BufferedWriter writer;
            try {
                writer = new BufferedWriter(new OutputStreamWriter(tmpWriterHtml.getContentOutputStream(), tmpWriterHtml.getEncoding()));
            } catch (UnsupportedEncodingException uee) {  // fallback to default encoding
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Unsupported encoding: " + tmpWriterHtml.getEncoding() + ", falling back to default encoding.", uee);
                }
                tmpWriterHtml.setEncoding(null);
                writer = new BufferedWriter(new OutputStreamWriter(tmpWriterHtml.getContentOutputStream()));
            }

            templateService.processTemplate("freemarker", templateRef.toString(), model, writer);
            try {
                writer.close();
            } catch (IOException ioe) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Error closing the writer on " + tmpHtml, ioe);
                }
            }

            // Conversion au format PDF
            ContentReader tmpReaderHtml = tmpWriterHtml.getReader();
            if (tmpReaderHtml != null) {
                File tmpPdf = TempFileProvider.createTempFile("tmpconv", ".pdf");
                FileContentWriter tmpWriterPdf = new FileContentWriter(tmpPdf);
                tmpWriterPdf.setMimetype(MimetypeMap.MIMETYPE_PDF);
                tmpWriterPdf.setEncoding(tmpReaderHtml.getEncoding());
                contentService.transform(tmpReaderHtml, tmpWriterPdf);
                return tmpPdf;
            }
        } // Fin generation page de garde
        return null;
    }

    public File genererDossierPDF(NodeRef dossier, String statutTdt, String ackDate) {
        // Liste des documents
        List<NodeRef> documents = getDocuments(dossier);
        ArrayList<File> tmpFiles = new ArrayList<File>(documents.size() + 1);
        // Bordereau de circulation
        File pageSignataires = genererPageSignatairesPDF(dossier, statutTdt, ackDate);
        if (null != pageSignataires) {
            tmpFiles.add(pageSignataires);
        }

        // Conversion des documents
        for (NodeRef doc : documents) {
            ContentReader tmpReader = null;
            if (this.nodeService.getProperty(doc, ParapheurModel.PROP_VISUEL_PDF) == null) {
                tmpReader = this.contentService.getReader(doc, ContentModel.PROP_CONTENT);
            } else {
                tmpReader = this.contentService.getReader(doc, ParapheurModel.PROP_VISUEL_PDF);
            }
            if (tmpReader == null) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Erreur lors de la récupération du contenu du document: id = " + doc);
                }
                continue;
            }
            File tmp = TempFileProvider.createTempFile("tmpconv", ".pdf");
            FileContentWriter tmpWriter = new FileContentWriter(tmp);
            tmpWriter.setMimetype(MimetypeMap.MIMETYPE_PDF);
            tmpWriter.setEncoding(tmpReader.getEncoding());
            try {
                contentService.transform(tmpReader, tmpWriter);
                tmpFiles.add(tmp);
            } catch (NoTransformerException nte) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn(nte);
                }
                throw new RuntimeException("Aucune conversion trouvée, veuillez contacter votre administrateur.", nte);
            } catch (Exception e) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn(e);
                }
                throw new RuntimeException("Erreur lors de la conversion, veuillez contacter votre administrateur.", e);
            }
        } // Fin conversion des documents

        // Assemblage des PDFs en un seul
        if (!tmpFiles.isEmpty()) {
            File archive = TempFileProvider.createTempFile("tmpconvfinal", ".pdf");

            // usage de com.lowagie.iText
            PdfReader itextPdfReader = null;
            try {
      /*              com.itextpdf.text.Document doc = new com.itextpdf.text.Document(PageSize.A4);
                    PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(archive));
                    writer.setPDFXConformance(PdfWriter.PDFA1B);
                    doc.open();
                    PdfDictionary outi = new PdfDictionary(PdfName.OUTPUTINTENT);
                    outi.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString("sRGB IEC61966-2.1"));
                    outi.put(PdfName.INFO, new PdfString("sRGB IEC61966-2.1"));
                    outi.put(PdfName.S, PdfName.GTS_PDFA1);
                    String iccString = this.configuration.getProperty("archive.iccprofile.location");
                    ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream(iccString));
                    PdfICCBased ib = new PdfICCBased(icc);
                    ib.remove(PdfName.ALTERNATE);
                    outi.put(PdfName.DESTOUTPUTPROFILE, writer.addToBody(ib).getIndirectReference());
                    writer.getExtraCatalog().put(PdfName.OUTPUTINTENTS, new PdfArray(outi));

                    // BaseFont bf = BaseFont.createFont("c:\\windows\\fonts\\arial.ttf", BaseFont.WINANSI, true);
                    // Font f = new Font(bf, 12); doc.add(new Paragraph("hello", f));
                    for (File tmp : tmpFiles) {
                        PdfReader tmpPdf = new PdfReader(new FileInputStream(tmp));
                        if (logger.isEnabledFor(Level.DEBUG)) {
                            // int tmpNbOfPages = tmpPdf.getNumberOfPages();
                            logger.debug("dossier de " + tmpPdf.getNumberOfPages() + "pages");
                        }

                        doc.add(tmpPdf.getPageN(1));
                        writer.freeReader(tmpPdf);
                        for (int pageN = 1; pageN <= tmpNbOfPages; pageN++) {
                            PdfDictionary tmpPage = tmpPdf.getPageN(pageN);
                            writer.setCollection(tmpPage);
                        }
                    }

                    writer.createXmpMetadata();
                    doc.close();
    */              /////////////////////////////////////////////

                // 1- assemblage
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                PdfCopyFields copy = new PdfCopyFields(baos);
                for (File tmp : tmpFiles) {
                    copy.addDocument(new PdfReader(new FileInputStream(tmp)));
                }
                copy.close();
                // 2- personnalisation de certaines propriétés PDF
                String nomDossier = this.nodeService.getProperty(dossier, ContentModel.PROP_NAME).toString();
                String typeDoss = this.nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_METIER).toString();
                String sstypeDoss = this.nodeService.getProperty(dossier, ParapheurModel.PROP_SOUSTYPE_METIER).toString();
                String nomAuteur  = getNomProprietaire(getParentParapheur(dossier));
                itextPdfReader = new PdfReader(baos.toByteArray());
                PdfStamper stamper = new PdfStamper(itextPdfReader, new FileOutputStream(archive));

                ByteArrayOutputStream os = new ByteArrayOutputStream();
                XmpWriter xmp = new XmpWriter(os);
                XmpSchema dc = new DublinCoreSchema();
                XmpArray subject = new XmpArray(XmpArray.UNORDERED);
                subject.add("iParapheur");
                subject.add(nomDossier);
                subject.add(typeDoss);
                subject.add(sstypeDoss);
                dc.setProperty(DublinCoreSchema.SUBJECT, subject);
                xmp.addRdfDescription(dc);
                PdfSchema pdf = new PdfSchema();
                pdf.setProperty(PdfSchema.KEYWORDS, nomDossier +", "+typeDoss+", "+sstypeDoss);//"Hello World, XMP, Metadata");
                pdf.setProperty(PdfSchema.VERSION, "1.4");
                xmp.addRdfDescription(pdf);
                xmp.close();
                stamper.setXmpMetadata(os.toByteArray());

                HashMap info = itextPdfReader.getInfo();
                info.put("Title"  , nomDossier);
                info.put("Subject", "Archive iParapheur de: "+ nomDossier);
                info.put("Creator", "iParapheur");
                info.put("Author", nomAuteur);
                stamper.setMoreInfo(info);
                stamper.setFormFlattening(true);

                Properties archivesConfiguration = getArchivesConfiguration();

                boolean displayTampon = true;
                if (archivesConfiguration.containsKey("archive.tamponActes.visible")) {
                    displayTampon = Boolean.parseBoolean(archivesConfiguration.getProperty("archive.tamponActes.visible"));
                }

                if (statutTdt != null) {
                    // 3- si status S2LOW ajout tampon sur toutes les pages
                    String tampon = statutTdt;
                    if (ackDate != null) {
                        if ("ACTES".equals(this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_PROTOCOLE))) {
                            tampon = archivesConfiguration.getProperty("archive.tamponActes.text");
                            if (tampon == null) {
                                tampon = "Parvenu en PREFECTURE le {0,date,dd/MM/yyyy}";
                            }
                        } else {
                            tampon = "Télétransmis le {0,date,dd/MM/yyyy}";
                        }
                    }
                    try {
                        Date ack = new SimpleDateFormat("yyyy-MM-dd").parse(ackDate);
                        tampon = MessageFormat.format(tampon, ack);
                    } catch (ParseException ex) {
                        throw new IllegalArgumentException("Illegal date format", ex);
                    }
                    BaseFont bf = null;
                    String ttfString = this.configuration.getProperty("archive.ttfVerdana.location");
                    if (ttfString == null) {
                        bf = BaseFont.createFont(BaseFont.HELVETICA_BOLDOBLIQUE, BaseFont.CP1252, BaseFont.EMBEDDED);
                    } else {
                        bf = BaseFont.createFont(ttfString, BaseFont.CP1252, BaseFont.EMBEDDED);
                    }
                    for (int i=1; i<= itextPdfReader.getNumberOfPages(); i++) {
                        PdfContentByte cb = stamper.getOverContent(i);
                        cb.beginText();
                        // Pour le choix de la fonte, voir page 265!!
                        cb.setFontAndSize(bf, 12);
                        cb.setColorFill(com.itextpdf.text.BaseColor.RED);
                        cb.setTextMatrix(PageSize.A4.getWidth() /2, 16); // marge par défaut: 36, - leading 18 =16
                        if (displayTampon) {
                            cb.showTextAligned(PdfContentByte.ALIGN_RIGHT, tampon,
                                    (PageSize.A4.getWidth() -20), 8, 0);
                        }
                        cb.endText();
                    }
                }
                stamper.close();

            } catch (com.itextpdf.text.DocumentException ex) {
                if (logger.isEnabledFor(Level.ERROR)) { logger.error(ex);  }
                throw new RuntimeException("Erreur dans la creation du PDF d'archive, veuillez contacter votre administrateur.", ex);
            } catch (IOException ex) {
                throw new ContentIOException("Erreur lors de l'assemblage des documents dans l'archive.", ex);
            }

            /* Le code etymon.pjx ne traite pas tous les cas...
            // STV (2008): Changement de lib PDF (etymon PJX) pour traiter les fichiers sortant de OOo
            List<PdfManager> m = new ArrayList<PdfManager>();
            try {  for (File tmp : tmpFiles) {  m.add(new PdfManager(new PdfReader(new PdfInputFile(tmp)))); }
                PdfWriter w = new PdfWriter(archive); PdfAppender a = new PdfAppender(m, w); a.append(); w.close();
            } catch (PdfFormatException e) { throw new RuntimeException("Erreur dans la creation du PDF d'archive, veuillez contacter votre administrateur.", e);
            } catch (IOException e1) {  throw new ContentIOException("Erreur lors de l'assemblage des documents dans l'archive.", e1);           }   */

            // BIDOUILLAGE STV: ce code (lib PDFbox) ne marche pas avec les PDF venant d'OpenOffice
            // PDFMergerUtility merger = new PDFMergerUtility(); merger.setDestinationFileName(archive.getAbsolutePath());
            // for (File tmp : tmpFiles)	 { merger.addSource(tmp); }
            // try { merger.mergeDocuments(); }
            // catch (COSVisitorException cve) { throw new RuntimeException(cve); }
            // catch (IOException ioe) { throw new ContentIOException("Erreur lors de l'assemblage des documents dans l'archive", ioe); }
            return archive;
        }
        return null;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#archiver(org.alfresco.service.cmr.repository.NodeRef)
     */
    @SuppressWarnings("unchecked")
    public String archiver(NodeRef dossier, String nomArchive) {
        logger.debug("ARCHIVER - Entree dans la methode");
        String urlArchive = null;
        String currentUser = authenticationService.getCurrentUserName();

        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");
        Assert.isTrue(isTermine(dossier), "Le dossier n'est pas \"terminé\": id = " + dossier);
        // Assert.isTrue(getActeurCourant(dossier) == null, "Toutes les étapes du dossier n'ont pas été validées: id = " + dossier);
        Assert.notNull(currentUser, "Aucun utilisateur courant !");

        EtapeCircuit etapeCourante = this.getCurrentEtapeCircuit(dossier);
        if (etapeCourante == null) {   // compatibilité ascendante
            Assert.isTrue(currentUser.equals(getParapheurOwner(getEmetteur(dossier))), "Le dossier ne peut être archivé que par son émetteur.");
        } else {
            Assert.isTrue(etapeCourante.getActionDemandee().trim().equalsIgnoreCase(EtapeCircuit.ETAPE_ARCHIVAGE), "Le dossier n'est pas à une étape d'archivage.");
            Assert.isTrue(currentUser.equals(getParapheurOwner(getParentParapheur(dossier))), "Le dossier ne peut être archivé que par le parapheur autorisé.");
        }

        // Récupération des infos S2low (ACTES pour le moment..... )
        String statut = null;
        String ackDate = null;
        if (s2lowService != null && s2lowService.isEnabled() && nodeService.hasAspect(dossier, ParapheurModel.ASPECT_S2LOW)) {
            int codeInfoS2low = -1;
            try {
                codeInfoS2low = s2lowService.getInfosS2low(dossier);
            } catch (IOException ioe) {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Impossible de récupérer le statut s2low ref = " + dossier, ioe);
                }
            }
            // FIXME : Spécifique S2LOW ACTES !!!
            // Check statut!={1:posté, 2:en attente de trs, 3:transmis} pour ne pas archiver un état transitoire
            Assert.isTrue((codeInfoS2low < 1 || codeInfoS2low > 3), "Dossier non archivable actuellement: traitement S2LOW en cours");

            statut = this.nodeService.getProperty(dossier, ParapheurModel.PROP_STATUS).toString();

            // Récup date acquittement MIAT depuis ParapheurModel.PROP_ARACTE_XML
            if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_ARACTE_XML) != null) {
                ContentReader contentreader = contentService.getReader(dossier, ParapheurModel.PROP_ARACTE_XML);
                SAXReader saxreader = new SAXReader();
                try {
                    Document document = saxreader.read(contentreader.getContentInputStream());
                    Element rootElement = document.getRootElement();
                    // L'attribut est au format AAAA-MM-JJ
                    ackDate = rootElement.attributeValue("DateReception");
                } catch (DocumentException e) {
                    if (logger.isEnabledFor(Level.WARN)) {
                        logger.warn("Erreur sur récup Date MIAT dossier ref = " + dossier, e);
                    }
                }

            } else {
                if (logger.isEnabledFor(Level.WARN)) {
                    logger.warn("Statut [" + statut + "], mais pas de date MIAT, XML absent!");
                }
            }
        } // fin infos S2low


        // Récupération du répertoire des archives
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.archives.childname");
        List<NodeRef> results = null;
        results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        if (results == null || results.size() != 1) {
            throw new RuntimeException("Il n'y a pas de dossier \"Archives\"");
        }
        NodeRef archivesRef = results.get(0);

        if (nomArchive == null || nomArchive.length() == 0) {
            nomArchive = (String) nodeService.getProperty(dossier, ContentModel.PROP_NAME) + ".pdf";
        }
        String description = (String) nodeService.getProperty(dossier, ContentModel.PROP_DESCRIPTION);

        // generer PDF archive ici
        File archive = genererDossierPDF(dossier, statut, ackDate);
        if (null != archive) {
            try {
                // Création du noeud
                logger.debug("ARCHIVER - Creation du noeud archive");
                FileInfo fileInfo = fileFolderService.create(archivesRef, nomArchive, ParapheurModel.TYPE_ARCHIVE);
                NodeRef archiveRef = fileInfo.getNodeRef();
                ContentWriter writer = contentService.getWriter(archiveRef, ContentModel.PROP_CONTENT, true);
                writer.setMimetype(MimetypeMap.MIMETYPE_PDF);
                writer.setEncoding("UTF-8");
                logger.debug("ARCHIVER - Ecriture du contenu PDF");
                writer.putContent(archive);
                nodeService.setProperty(archiveRef, ContentModel.PROP_DESCRIPTION, description);

                /*
                 * List<NodeRef> archiveRefs = new ArrayList<NodeRef>(); archiveRefs.add(archiveRef);
                 */
                // Sauvegarde du document principal et de sa signature
                byte[] signature = this.getSignature(dossier);
                if (signature != null && signature.length > 0) {
                    logger.debug("ARCHIVER - Signature recuperee");
                    NodeRef docRef = this.getDocuments(dossier).get(0);
                    logger.debug("ARCHIVER - Ajout de l'aspect SIGNED");
                    this.nodeService.addAspect(archiveRef, ParapheurModel.ASPECT_SIGNED, null);
                    logger.debug("ARCHIVER - Sauvegarde du nom original");
                    String docFileName = (String) this.nodeService.getProperty(docRef, ContentModel.PROP_NAME);
                    this.nodeService.setProperty(archiveRef, ParapheurModel.PROP_ORIGINAL_NAME, docFileName);

                    logger.debug("ARCHIVER - Sauvegarde du document original");
                    ContentReader sigReader = this.contentService.getReader(docRef, ContentModel.PROP_CONTENT);
                    ContentWriter sigWriter = this.contentService.getWriter(archiveRef, ParapheurModel.PROP_ORIGINAL, true);
                    sigWriter.setEncoding(sigReader.getEncoding());
                    sigWriter.setMimetype(sigReader.getMimetype());
                    sigWriter.putContent(sigReader);

                    List<EtapeCircuit> etapes = this.getCircuit(dossier);
                    if (etapes.get(0).getActionDemandee() == null) {   // compatibilité ascendante
                        logger.debug("ARCHIVER - Sauvegarde de la signature");
                        sigWriter = this.contentService.getWriter(archiveRef, ParapheurModel.PROP_SIG, true);
                        String sigFormat = nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT).toString();
                        if (sigFormat.startsWith("XAdES")) {
                            sigWriter.setMimetype(MimetypeMap.MIMETYPE_XML);
                        } else if (sigFormat.startsWith("PKCS#7")) {
                            sigWriter.setMimetype("application/pkcs7-signature");
                        } else {
                            throw new UnsupportedOperationException("Unknown signature format: " + sigFormat);
                        }
                        sigWriter.setEncoding("UTF-8");
                        sigWriter.putContent(new ByteArrayInputStream(signature));
                    } else {   //  Traiter l'archivage de signatures multiples
                        File tmpZipFile = TempFileProvider.createTempFile(docFileName + "_SIGs", "zip");
                        ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(tmpZipFile));
                        int ordre = 0;
                        for (EtapeCircuit etape : etapes) {
                            if (etape.getActionDemandee().trim().equalsIgnoreCase(EtapeCircuit.ETAPE_SIGNATURE)) {
                                byte[] signatureEtape = this.getSignature(etape);
                                if (signatureEtape != null && signatureEtape.length > 0) {
                                    logger.debug("ARCHIVER - Sauvegarde de la signature Etape " + etapes.indexOf(etape));
                                    sigWriter = this.contentService.getWriter(archiveRef, ParapheurModel.PROP_SIG, true);
                                    String extensionSignature = null;
                                    String sigFormat = nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT).toString();
                                    if (sigFormat.startsWith("XAdES")) {
                                        sigWriter.setMimetype(MimetypeMap.MIMETYPE_XML);
                                        extensionSignature = ".xml";
                                    } else if (sigFormat.startsWith("PKCS#7")) {
                                        sigWriter.setMimetype("application/pkcs7-signature");
                                        extensionSignature = ".p7s";
                                    } else {
                                        throw new UnsupportedOperationException("Unknown signature format: " + sigFormat);
                                    }
                                    ordre++;
                                    sigWriter.setEncoding("UTF-8");
                                    sigWriter.putContent(new ByteArrayInputStream(signatureEtape));
                                    // Add a ZIP entry to the zip output stream.
                                    zout.putNextEntry(new ZipEntry(ordre + "- "  + etape.getSignataire() + extensionSignature));
                                    // Transfer bytes from the file to the ZIP file
                                    zout.write(signatureEtape, 0, signatureEtape.length);
                                    zout.closeEntry();
                                }
                            }
                        }
                        zout.close();
                        sigWriter = this.contentService.getWriter(archiveRef, ParapheurModel.PROP_SIG, true);
                        sigWriter.setMimetype(MimetypeMap.MIMETYPE_ZIP);
                        sigWriter.setEncoding("UTF-8");
                        sigWriter.putContent(tmpZipFile);
                    }

                    // Copie du document
		    /*
                     * logger.debug("ARCHIVER - Copie du document signe"); fileInfo = fileFolderService.copy(documents.get(0), archivesRef, nomArchive + ".old");
                     * archiveRefs.add(fileInfo.getNodeRef());
                     * logger.debug("ARCHIVER - Creation du fichier signature"); fileInfo = fileFolderService.create(archivesRef, nomArchive + ".p7s", ContentModel.TYPE_CONTENT);
                     * NodeRef sigRef = fileInfo.getNodeRef(); logger.debug("ARCHIVER - Recuperation du writer de la signature");
                     * writer = contentService.getWriter(sigRef, ContentModel.PROP_CONTENT, true); writer.setMimetype("application/pkcs7-signature");
                     * writer.setEncoding("UTF-8"); logger.debug("ARCHIVER - Ecriture de la signature"); File tmpFile = TempFileProvider.createTempFile("tmp", null);
                     * FileOutputStream fos = new FileOutputStream(tmpFile); fos.write(signature); fos.close(); writer.putContent(tmpFile); archiveRefs.add(sigRef);
                     */
                }

                nodeService.setProperty(((EtapeCircuitImpl) etapeCourante).getNodeRef(),
                        ParapheurModel.PROP_EFFECTUEE,
                        Boolean.TRUE);
                nodeService.setProperty(((EtapeCircuitImpl) etapeCourante).getNodeRef(),
                        ParapheurModel.PROP_DATE_VALIDATION,
                        new Date());

                // Copie les étapes du circuit sur l'archive pour conservation
                // Cette copie doit etre faite avant de modifier les permissions
                // sous peine d'access denied
                List<ChildAssociationRef> children = nodeService.getChildAssocs(dossier,
                        ParapheurModel.CHILD_ASSOC_PREMIERE_ETAPE,
                        RegexQNamePattern.MATCH_ALL);
                copyService.copy(children.get(0).getChildRef(),
                        archiveRef,
                        ParapheurModel.CHILD_ASSOC_OLD_PREMIERE_ETAPE,
                        children.get(0).getQName(),
                        true);

                Boolean confidentiel = (Boolean) this.nodeService.getProperty(dossier, ParapheurModel.PROP_CONFIDENTIEL);
                Boolean visibiliteP = (Boolean) this.nodeService.getProperty(dossier, ParapheurModel.PROP_PUBLIC);
                // Si le dossier est public, on hérite les permissions => visibilité pour tous
                if (Boolean.FALSE.equals(visibiliteP)) {   // Dans le cas contraire, on les règles "à la main"
                    logger.debug("ARCHIVER - Heritage des permissions set");
                    permissionService.setInheritParentPermissions(archiveRef, false);
                    // Ajout des permissions sur l'archive
                    String authority;
                    for (EtapeCircuit etape : getCircuit(dossier)) {
                        authority = getParapheurOwner(etape.getParapheur());
                        if (authority != null && !authority.equals("")) {
                            logger.debug("ARCHIVER - Permission READ attribuee a acteur:" + authority);
                            permissionService.setPermission(archiveRef, authority, "Read", true);
                        }
                        // Liste de notification
                        Set<NodeRef> listeNotifNodeRefs = etape.getListeNotification();
                        if (listeNotifNodeRefs != null) {
                            for (NodeRef parapheur : etape.getListeNotification()) {
                                authority = getParapheurOwner(parapheur);
                                if (authority != null && !authority.equals("")) {
                                    logger.debug("ARCHIVER - Permission READ attribuee a notifie:" + authority);
                                    permissionService.setPermission(archiveRef, authority, "Read", true);
                                }
                            }
                        }
                    }
                    // si le dossier n'est pas confidentiel, il est visible par les membres des groupes auxquels appartient son émetteur
                    if (Boolean.FALSE.equals(confidentiel)) {
                        String emetteur = getParapheurOwner(getEmetteur(dossier));
                        permissionService.setPermission(archiveRef, emetteur, "Read", true);
                        Set<String> setGroupes = authorityService.getContainingAuthorities(AuthorityType.GROUP,
                                emetteur, true);
                        logger.debug("ARCHIVER - Recuperation des groupes : " + setGroupes);
                        for (String groupe : setGroupes) {
                            if (groupe != null && !groupe.equals("")) {
                                logger.debug("ARCHIVER - Permission READ attribuee a " + groupe);
                                permissionService.setPermission(archiveRef, groupe, "Read", true);
                            }
                        }
                    }
                }

                // Si envoyé à S2LOW: sauvegarde ID transaction, renseignement de l'URL d'archive dans S2LOW
                if (s2lowService != null && s2lowService.isEnabled() && nodeService.hasAspect(dossier, ParapheurModel.ASPECT_S2LOW)) {
                    logger.debug("ARCHIVER - Sauvegarde du numero de Transaction S2LOW");
                    Map<QName, Serializable> pptes = new HashMap<QName, Serializable>();
                    pptes.put(ParapheurModel.PROP_TRANSACTION_ID, this.nodeService.getProperty(dossier,
                            ParapheurModel.PROP_TRANSACTION_ID));

                    this.nodeService.addAspect(archiveRef, ParapheurModel.ASPECT_S2LOW, pptes);
                    // this.nodeService.setProperty(archiveRef, ParapheurModel.PROP_TRANSACTION_ID,
                    // this.nodeService.getProperty(dossier, ParapheurModel.PROP_TRANSACTION_ID));

                    if (this.contentService.getReader(dossier, ParapheurModel.PROP_ARACTE_XML) != null) {
                        logger.debug("ARCHIVER - Sauvegarde du XML retour MIAT");
                        ContentReader sigReader = this.contentService.getReader(dossier, ParapheurModel.PROP_ARACTE_XML);
                        ContentWriter sigWriter = this.contentService.getWriter(archiveRef,
                                ParapheurModel.PROP_ARACTE_XML, true);
                        sigWriter.setEncoding(sigReader.getEncoding());
                        sigWriter.setMimetype(sigReader.getMimetype());
                        sigWriter.putContent(sigReader);

                        logger.debug("ARCHIVER - Renseignement URL archivage dans S2LOW ");
                        urlArchive = s2lowService.setS2lowActesArchiveURL(archiveRef);
                    } else {
                        logger.warn("ARCHIVER - XML retour MIAT Absent !");
                    }
                }

                // Si dossier "typé metier" on récupère les meta donnees
                if (nodeService.hasAspect(dossier, ParapheurModel.ASPECT_TYPAGE_METIER)) {
                    logger.debug("ARCHIVER - Sauvegarde meta données Type Metier");
                    Map<QName, Serializable> pptes = new HashMap<QName, Serializable>();
                    pptes.put(ParapheurModel.PROP_TYPE_METIER, this.nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_METIER));
                    pptes.put(ParapheurModel.PROP_SOUSTYPE_METIER, this.nodeService.getProperty(dossier, ParapheurModel.PROP_SOUSTYPE_METIER));
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_NOM) != null) {
                        pptes.put(ParapheurModel.PROP_TDT_NOM, this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_NOM));
                    }
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_PROTOCOLE) != null) {
                        pptes.put(ParapheurModel.PROP_TDT_PROTOCOLE, this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_PROTOCOLE));
                    }
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_FICHIER_CONFIG) != null) {
                        pptes.put(ParapheurModel.PROP_TDT_FICHIER_CONFIG, this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_FICHIER_CONFIG));
                    }
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_SIGNATURE) != null) {
                        pptes.put(ParapheurModel.PROP_TYPE_SIGNATURE, this.nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_SIGNATURE));
                    }
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT) != null) {
                        pptes.put(ParapheurModel.PROP_SIGNATURE_FORMAT, this.nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT));
                    }
                    if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE) != null) {
                        pptes.put(ParapheurModel.PROP_XPATH_SIGNATURE, this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE));
                    }
                    this.nodeService.addAspect(archiveRef, ParapheurModel.ASPECT_TYPAGE_METIER, pptes);
                }
                List<Object> list = new ArrayList<Object>();
                list.add("Archive");
                if (urlArchive != null) {
                    list.add(urlArchive);
                }
                auditService.audit("ParapheurService", "Archivage du dossier", dossier, list);


                // TODO: avant de supprimer le dossier: relevé des indicateurs de gestion, audit au format XML
                // TODO:   META Donnees d'archivage ?? QUELLES DONNEES ??



                // On supprime le dossier archivé
                logger.debug("ARCHIVER - Suppression du dossier");
                nodeService.deleteNode(dossier);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return urlArchive;
    }

    protected Properties getArchivesConfiguration() {
        List<NodeRef> nodes = searchService.selectNodes(
                nodeService.getRootNode(new StoreRef(configuration.getProperty("spaces.store"))),
                ARCHIVES_CONFIGURATION_PATH,
                null,
                namespaceService, false);

        if (nodes.isEmpty()) {
            throw new IllegalStateException("Unable to find archives configuration file");
        }


        NodeRef archivesConfNode = nodes.get(0);

        ContentReader reader = contentService.getReader(archivesConfNode, ContentModel.PROP_CONTENT);
        Properties archivesConf = new Properties();
        try {
            archivesConf.load(reader.getContentInputStream());
        } catch (IOException ex) {
            throw new RuntimeException("Unable to read archives configuration", ex);
        }

        return archivesConf;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#secretariat(org.alfresco.service.cmr.repository.NodeRef)
     */
    public void secretariat(NodeRef dossier) {
        logger.debug("SECRETARIAT - Entree dans la methode");

        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");
        Assert.isTrue(!isTermine(dossier), "Le dossier est \"terminé\": id = " + dossier);

        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);

        NodeRef parapheur = this.getParentParapheur(dossier);
        Assert.notNull(parapheur, "Impossible de retrouver le parapheur du dossier : id = " + dossier);

        Assert.isTrue(isActeurCourant(dossier, this.authenticationService.getCurrentUserName()) || isParapheurSecretaire(parapheur, this.authenticationService.getCurrentUserName()),
                "Vous n'êtes pas acteur courant de ce dossier, ni membre du secrétariat!");

        NodeRef corbeille = null;
        if (this.nodeService.hasAspect(dossier, ParapheurModel.ASPECT_SECRETARIAT)) {
            if (this.isEmis(dossier)) {
                corbeille = this.getCorbeille(parapheur, ParapheurModel.NAME_A_TRAITER);
            } else {
                corbeille = this.getCorbeille(parapheur, ParapheurModel.NAME_EN_PREPARATION);
            }

            if (corbeille != null) {
                logger.debug("SECRETARIAT - Suppression de l'aspect SECRETARIAT");
                this.nodeService.removeAspect(dossier, ParapheurModel.ASPECT_SECRETARIAT);
            }
        } else {
            corbeille = this.getCorbeille(parapheur, ParapheurModel.NAME_SECRETARIAT);
            if (corbeille != null) {
                logger.debug("SECRETARIAT - Ajout de l'aspect SECRETARIAT");
                this.nodeService.addAspect(dossier, ParapheurModel.ASPECT_SECRETARIAT, null);
            }
        }

        if (corbeille == null) {
            throw new RuntimeException("Impossible de trouver la corbeille destination du dossier : id = " + dossier);
        }

        logger.debug("SECRETARIAT - Deplacement du noeud");
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());

            List<Object> list = new ArrayList<Object>();
            list.add((String) nodeService.getProperty(dossier, ParapheurModel.PROP_STATUS_METIER));
            auditService.audit("ParapheurService", "Opération de secrétariat sur le dossier", dossier, list);

        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans la corbeille de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe dans la corbeille de destination", ex);
        }

        if (this.nodeService.hasAspect(dossier, ParapheurModel.ASPECT_SECRETARIAT)) {
            mail("secretariat", "parapheur-secretariat-relecture.ftl", dossier);
        } else {
            mail("current", "parapheur-secretariat-retour.ftl", dossier);
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSavedWorkflows()
     */
    public Map<String, NodeRef> getSavedWorkflows() {
        Map<String, NodeRef> res = new HashMap<String, NodeRef>();
        String xpath = this.configuration.getProperty("spaces.company_home.childname")
                + "/" + this.configuration.getProperty("spaces.dictionary.childname")
                + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            List<NodeRef> results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) {
                List<ChildAssociationRef> children = nodeService.getChildAssocs(results.get(0), ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                logger.debug("getSavedWorkflows: found " + children.size() + "elements");
                for (ChildAssociationRef childAssoRef : children) {
                    NodeRef ref = childAssoRef.getChildRef();

                    if (nodeService.getType(ref).equals(ParapheurModel.TYPE_SAVED_WORKFLOW)) {

                        boolean isPrivate = nodeService.hasAspect(ref, ParapheurModel.ASPECT_PRIVATE_WORKFLOW);

                        NodeRef ownedParapheur = getOwnedParapheur(AuthenticationUtil.getRunAsUser());
                        Set<String> authorities = authorityService.getAuthoritiesForUser(authenticationComponent.getCurrentUserName());

                        Collection<String> aclParapheurs = Collections.EMPTY_LIST;
                        Collection<String> aclGroups = Collections.EMPTY_LIST;
                        if (isPrivate) {
                            aclParapheurs = (Collection<String>) nodeService.getProperty(ref, ParapheurModel.PROP_PRIVATE_WORKFLOW_ACL_PARAPHEURS);
                            aclGroups = (Collection<String>) nodeService.getProperty(ref, ParapheurModel.PROP_PRIVATE_WORKFLOW_ACL_GROUPS);
                        }

                        Collection<String> aclAuthorities = new ArrayList<String>();
                        for (String group : aclGroups) {
                            NodeRef groupRef = new NodeRef(group);
                            aclAuthorities.add(nodeService.getProperty(groupRef, ContentModel.PROP_NAME).toString());
                        }

                        logger.debug("getSavedWorkflows: " + isPrivate + ":"
                                + ownedParapheur + ":"
                                + authorities + ":" +
                                aclParapheurs + ":" +
                                aclAuthorities);

                        String name = (String) nodeService.getProperty(ref, ContentModel.PROP_NAME);

                        if (!isPrivate
                                || (ownedParapheur!=null && aclParapheurs.contains(ownedParapheur.toString()))
                                || CollectionsUtils.containsOneOf(aclAuthorities, authorities)) {
                            logger.debug("getSavedWorkflows: added " + name);
                            res.put(name, ref);
                        } else {
                            logger.debug("getSavedWorkflows: rejected " + name);
                        }
                    }
                }
            }
        } catch (AccessDeniedException err) {   // ignore
            logger.warn("Access denied to workflow directory for user " + AuthenticationUtil.getRunAsUser(), err);
        }
        logger.debug("getSavedWorkflows: " + res);
        return res;
    }

    public Map<String, NodeRef> getOwnedWorkflows() {
        Map<String, NodeRef> res = new HashMap<String, NodeRef>();
        String xpath = this.configuration.getProperty("spaces.company_home.childname") +
                "/" + this.configuration.getProperty("spaces.dictionary.childname") +
                "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            List<NodeRef> results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) {
                List<ChildAssociationRef> children = nodeService.getChildAssocs(results.get(0), ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef childAssoRef : children) {
                    NodeRef ref = childAssoRef.getChildRef();
                    String owner = (String) nodeService.getProperty(ref, ContentModel.PROP_CREATOR);
                    boolean isPrivate = nodeService.hasAspect(ref, ParapheurModel.ASPECT_PRIVATE_WORKFLOW);
                    if (isOfType(ref, ParapheurModel.TYPE_SAVED_WORKFLOW)
                            && isPrivate
                            && AuthenticationUtil.getRunAsUser().equals(owner)) {
                        String name = (String) nodeService.getProperty(ref, ContentModel.PROP_NAME);
                        res.put(name, ref);
                    }
                }
            }
        } catch (AccessDeniedException err) {   // ignore
            logger.warn("Access denied to workflow directory for user " + AuthenticationUtil.getRunAsUser(), err);
        }
        return res;
    }

    public Map<String, NodeRef> getPublicWorkflows() {
        Map<String, NodeRef> res = new HashMap<String, NodeRef>();
        String xpath = this.configuration.getProperty("spaces.company_home.childname") +
                "/" + this.configuration.getProperty("spaces.dictionary.childname") +
                "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            List<NodeRef> results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) {
                List<ChildAssociationRef> children = nodeService.getChildAssocs(results.get(0), ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef childAssoRef : children) {
                    NodeRef ref = childAssoRef.getChildRef();
                    boolean isPrivate = nodeService.hasAspect(ref, ParapheurModel.ASPECT_PRIVATE_WORKFLOW);
                    if (isOfType(ref, ParapheurModel.TYPE_SAVED_WORKFLOW)
                            && !isPrivate) {
                        String name = (String) nodeService.getProperty(ref, ContentModel.PROP_NAME);
                        res.put(name, ref);
                    }
                }
            }
        } catch (AccessDeniedException err) {   // ignore
            logger.warn("Access denied to workflow directory for user " + AuthenticationUtil.getRunAsUser(), err);
        }
        return res;
    }

    public Map<String, NodeRef> getAllWorkflows() {
        Map<String, NodeRef> res = new HashMap<String, NodeRef>();
        String xpath = this.configuration.getProperty("spaces.company_home.childname") +
                "/" + this.configuration.getProperty("spaces.dictionary.childname") +
                "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            List<NodeRef> results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) {
                List<ChildAssociationRef> children = nodeService.getChildAssocs(results.get(0), ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef childAssoRef : children) {
                    NodeRef ref = childAssoRef.getChildRef();
                    if (isOfType(ref, ParapheurModel.TYPE_SAVED_WORKFLOW)) {
                        String name = (String) nodeService.getProperty(ref, ContentModel.PROP_NAME);
                        res.put(name, ref);
                    }
                }
            }
        } catch (AccessDeniedException err) {   // ignore
            logger.warn("Access denied to workflow directory for user " + AuthenticationUtil.getRunAsUser(), err);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("getAllWorkflows: returned " + res);
        }
        return res;
    }

    // STV:  Backup de la précédente méthode
    @Deprecated
    public Map<String, NodeRef> getSavedWorkflows(String type, String sstype) {
        Map<String, NodeRef> res = new HashMap<String, NodeRef>();
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");

        try {   // Récupération du répertoire de circuits
            List<NodeRef> results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) {
                NodeRef circuitsRep = null;
                List<ChildAssociationRef> circuitsPrives = nodeService.getChildAssocs(results.get(0),
                        ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef prive : circuitsPrives) {
                    NodeRef rep = prive.getChildRef();
                    if (isOfType(rep, ContentModel.TYPE_FOLDER) && this.authenticationService.getCurrentUserName().equals(nodeService.getProperty(rep, ContentModel.PROP_NAME))) {   // On a trouvé un dossier contenant les circuits privés de l'utilisateur courant
                        circuitsRep = rep;
                        break;
                    }
                }

                if (circuitsRep != null) {   // On alimente la liste avec les circuits privés
                    List<ChildAssociationRef> circuit = nodeService.getChildAssocs(circuitsRep, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                    for (ChildAssociationRef child : circuit) {
                        NodeRef circuitRef = child.getChildRef();
                        String name = (String) nodeService.getProperty(circuitRef, ContentModel.PROP_NAME);
                        res.put(name, circuitRef);
                    }
                }

                // On alimente la liste avec les circuits publics
                circuitsRep = results.get(0);
                List<ChildAssociationRef> circuit = nodeService.getChildAssocs(circuitsRep, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef child : circuit) {
                    NodeRef circuitRef = child.getChildRef();
                    if (isOfType(circuitRef, ContentModel.TYPE_CONTENT)) {
                        String name = (String) nodeService.getProperty(circuitRef, ContentModel.PROP_NAME);
                        res.put(name + " (public)", circuitRef);
                    }
                }
            }
        } catch (AccessDeniedException err) {   // ignore
        }
        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getCircuitHierarchique(NodeRef)
     */
    public List<EtapeCircuit> getCircuitHierarchique(NodeRef emetteur) {
        List<EtapeCircuit> circuit = new ArrayList<EtapeCircuit>();
        NodeRef parapheur = emetteur;
        while (parapheur != null) {
            EtapeCircuitImpl etape = new EtapeCircuitImpl();
            etape.setParapheur(parapheur);
            etape.setTransition("PARAPHEUR");
            parapheur = this.getParapheurResponsable(parapheur);
            if (parapheur == null) {
                etape.setActionDemandee(EtapeCircuit.ETAPE_SIGNATURE);
            } else {
                etape.setActionDemandee(EtapeCircuit.ETAPE_VISA);
            }
            circuit.add(etape);//this.addParapheurVisa(parapheur);
        }
        // ajout d'une dernière étape archivage par l'émetteur
        EtapeCircuitImpl finale = new EtapeCircuitImpl();
        finale.setParapheur(emetteur);
        finale.setTransition("PARAPHEUR");
        finale.setActionDemandee(EtapeCircuit.ETAPE_ARCHIVAGE);
        circuit.add(finale);
        return circuit;
    }

    /**
     * This method is used to read saved workflows up to version 3.0.
     *
     * To read more recent workflows, please see WorkflowService
     *
     * @see com.atolcd.parapheur.repo.ParapheurService#loadSavedWorkflow(org.alfresco.service.cmr.repository.NodeRef)
     * @see com.atolcd.parapheur.repo.WorkflowService#getSavedWorkflow(org.alfresco.service.cmr.repository.NodeRef)
     * @deprecated Since 3.1
     */
    @SuppressWarnings("unchecked")
    @Deprecated
    public SavedWorkflow loadSavedWorkflow(NodeRef workflow) {
        Assert.isTrue(nodeService.exists(workflow),
                "Le Node Ref passé en paramètre ne correspond pas à un noeud existant");

        SavedWorkflowImpl res = new SavedWorkflowImpl();
        res.setNodeRef(workflow);
        res.setName((String) nodeService.getProperty(workflow, ContentModel.PROP_NAME));
        logger.debug("ParapheurServiceImpl::loadSavedWorkflow, circuit : "+ res.getName());
        ContentReader contentreader = contentService.getReader(workflow, ContentModel.PROP_CONTENT);
        SAXReader saxreader = new SAXReader();

        try {
            if (contentreader == null)  logger.error("contentReader is NULL.");
            String myString = contentreader.getContentString();
            StringReader stringReader = new StringReader(myString);
            Document document = saxreader.read(stringReader);
            Element rootElement = document.getRootElement();
            // Check l'attribut version: si présent=3.0 ou absent (vieille implémentation!)
            if (rootElement.attributeValue("version") == null) {
                logger.debug("Circuit OLD SCHOOL !!!");
                /**
                 * Attention ici: vieille implémentation avec juste des éléments "validation" et "notification"
                 * Donc: on notifie à TOUTES les étapes
                 */
                Element validation = rootElement.element("validation");
                if (validation != null) {
                    for (Iterator i = validation.elementIterator("etape"); i.hasNext();) {
                        Element etape = (Element) i.next();
                        EtapeCircuit eci = new EtapeCircuitImpl();
                        ((EtapeCircuitImpl) eci).setTransition("PARAPHEUR"); // valeur par défaut
                        ((EtapeCircuitImpl) eci).setParapheur(new NodeRef(etape.getText()));
                        if (i.hasNext()) {
                            ((EtapeCircuitImpl) eci).setActionDemandee(EtapeCircuit.ETAPE_VISA);
                        } else {
                            ((EtapeCircuitImpl) eci).setActionDemandee(EtapeCircuit.ETAPE_SIGNATURE);
                        }
                        Element notification = rootElement.element("notification");
                        if (notification != null) {
                            Set<NodeRef> liste = new HashSet<NodeRef>();
                            for (Iterator j = notification.elementIterator("etape"); j.hasNext();) {
                                Element etapeElt = (Element) j.next();
                                liste.add(new NodeRef(etapeElt.getText().trim()));
                            }
                            ((EtapeCircuitImpl) eci).setListeDiffusion(liste);
                        }
                        res.addToCircuit(((EtapeCircuitImpl) eci));
                    }
                }
            } else if (rootElement.attributeValue("version").equalsIgnoreCase("3.0")) {
                logger.debug("Circuit v3");
                // Droits d'usage: acl + groupes
                Element aclElt = rootElement.element("acl");
                if (aclElt != null) {
                    for (Iterator i = aclElt.elementIterator("parapheur"); i.hasNext();) {
                        Element parapheurElt = (Element) i.next();
                        res.addToAclParapheurs(new NodeRef(parapheurElt.getText().trim()));
                    }
                }
                Element groupesElt = rootElement.element("groupes");
                if (groupesElt != null) {
                    for (Iterator i = groupesElt.elementIterator("groupe"); i.hasNext();) {
                        Element groupeElt = (Element) i.next();
                        res.addToAclGroupes(groupeElt.getText().trim());
                    }
                }
                // Etapes du circuit de validation
                Element etapesElt = rootElement.element("etapes");
                if (etapesElt != null) {
                    for (Iterator i = etapesElt.elementIterator("etape"); i.hasNext();) {
                        Element etapeElt = (Element) i.next();
                        EtapeCircuitImpl eci = new EtapeCircuitImpl();
                        eci.setTransition(etapeElt.element("transition").getText().trim());
                        if (EtapeCircuit.TRANSITION_PARAPHEUR.equals(eci.getTransition())) {
                            eci.setParapheur(new NodeRef(etapeElt.element("parapheur").getText().trim()));
                        }
                        eci.setActionDemandee(etapeElt.element("action-demandee").getText().trim());
                        // Liste de diffusion
                        Element diffElt = etapeElt.element("diffusion");
                        if (diffElt != null) {
                            Set<NodeRef> liste = new HashSet<NodeRef>();
                            for (Iterator j = diffElt.elementIterator("noderef"); j.hasNext();) {
                                Element nodeElt = (Element) j.next();
                                liste.add(new NodeRef(nodeElt.getText().trim()));
                            }
                            eci.setListeDiffusion(liste);
                        }
                        res.addToCircuit(eci);
                    }
                }
            } else if (rootElement.attributeValue("version").equalsIgnoreCase("3.1")) {
                /*
                 * Quick and dirty patch
                 * If workflow version is 3.1, then workflowService is called to
                 * load the workflow.
                 */
                return workflowService.getSavedWorkflow(workflow);
            }
        } catch (DocumentException e) {
        }

        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#saveWorkflow(java.lang.String, java.util.List, java.util.Set, boolean)
     */
    @Deprecated
    public NodeRef saveWorkflow(String name, List<NodeRef> circuit, Set<NodeRef> diffusion, boolean publicWorkflow) {
        Assert.hasText(name, "Le circuit doit être nommé");

        NodeRef res = null;
        NodeRef circuitsRef = null;
        List<NodeRef> results = null;

        // On prépare le dossier de sauvegarde
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException err) {
            if (logger.isDebugEnabled()) {
                logger.debug("Access denied");
            }
            return null;    // ignore and return null
        }

        if (results != null && results.size() == 1) {
            if (publicWorkflow) {
                circuitsRef = results.get(0);
            } else {   // Circuit privé: recherche du sous-répertoire
                List<ChildAssociationRef> circuitsPrives = nodeService.getChildAssocs(results.get(0),
                        ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
                for (ChildAssociationRef prive : circuitsPrives) {
                    NodeRef rep = prive.getChildRef();
                    // On a trouvé un dossier contenant les circuits privés de l'utilisateur courant
                    if (isOfType(rep, ContentModel.TYPE_FOLDER) && this.authenticationService.getCurrentUserName().equals(
                            nodeService.getProperty(rep, ContentModel.PROP_NAME))) {
                        circuitsRef = rep;
                        break;
                    }
                }

                if (circuitsRef == null) {
                    try {
                        FileInfo info = this.fileFolderService.create(results.get(0), this.authenticationService.getCurrentUserName(), ContentModel.TYPE_FOLDER);
                        circuitsRef = info.getNodeRef();
                        // Mise en place des permissions du répertoire
                        permissionService.setInheritParentPermissions(circuitsRef, false);
                        permissionService.setPermission(circuitsRef, authenticationService.getCurrentUserName(),
                                PermissionService.COORDINATOR, true);
                    } catch (FileExistsException e) {
                        logger.warn("Impossible de créer le répertoire de circuits privés pour l'utilisateur : " + this.authenticationService.getCurrentUserName());
                        return null;
                    }
                }
            }

            try {
                // On vérifie si le circuit existe déjà
                res = nodeService.getChildByName(circuitsRef, ContentModel.ASSOC_CONTAINS, name);
                if (res == null) {
                    // Création du noeud
                    FileInfo fileInfo = fileFolderService.create(circuitsRef, name, ContentModel.TYPE_CONTENT);
                    res = fileInfo.getNodeRef();
                    permissionService.setInheritParentPermissions(res, false);
                    permissionService.setPermission(res, authenticationService.getCurrentUserName(),
                            PermissionService.COORDINATOR, true);
                    permissionService.setPermission(res, "GROUP_EVERYONE", PermissionService.CONSUMER, true);
                }
            } catch (FileExistsException e) {
                logger.warn("Erreur lors de la modification du circuit");
                return null;
            }

            // Préparation des données XML
            String XMLData = null;
            Document doc = DocumentHelper.createDocument();
            Element root = doc.addElement("circuit");

            Element validation = root.addElement("validation");
            for (NodeRef etape : circuit) {
                validation.addElement("etape").addText(etape.toString());
            }

            Element notification = root.addElement("notification");
            for (NodeRef notifie : diffusion) {
                notification.addElement("etape").addText(notifie.toString());
            }

            try {
                StringWriter out = new StringWriter(1024);
                XMLWriter writerTmp = new XMLWriter(OutputFormat.createPrettyPrint());
                writerTmp.setWriter(out);
                writerTmp.write(doc);
                XMLData = out.toString();

                // Ecriture des données XML dans le nouveau noeud
                ContentWriter writer = this.contentService.getWriter(res, ContentModel.PROP_CONTENT, true);
                writer.setMimetype(MimetypeMap.MIMETYPE_XML);
                writer.setEncoding("UTF-8");
                writer.putContent(XMLData);
            } catch (IOException e) {
                if (nodeService.exists(res)) {
                    fileFolderService.delete(res);
                }
                return null;
            }
        }

        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#saveWorkflow(java.lang.String, java.util.List, java.util.Set, java.util.Set, boolean)
     */
    @Deprecated
    public NodeRef saveWorkflow(String name, List<EtapeCircuit> circuit, Set<NodeRef> acl, Set<String> groupes, boolean publicCircuit) {
        Assert.hasText(name, "Le circuit doit être nommé");

        NodeRef res = null;
        NodeRef circuitsRef = null;
        List<NodeRef> results = null;

        // Dossier de sauvegarde
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException err) {
            if (logger.isDebugEnabled()) {
                logger.debug(err.getLocalizedMessage());
            }
            return null;    // on ignore
        }

        if (results != null && results.size() == 1) {
            circuitsRef = results.get(0);

            try {
                // On vérifie si le circuit existe déjà
                res = nodeService.getChildByName(circuitsRef, ContentModel.ASSOC_CONTAINS, name);
                if (res == null) {
                    // Création du noeud
                    FileInfo fileInfo = fileFolderService.create(circuitsRef, name, ParapheurModel.TYPE_SAVED_WORKFLOW);
                    res = fileInfo.getNodeRef();
                    permissionService.setInheritParentPermissions(res, false);
                    permissionService.setPermission(res, authenticationService.getCurrentUserName(),
                            PermissionService.COORDINATOR, true);
                    permissionService.setPermission(res, "GROUP_EVERYONE", PermissionService.CONSUMER, true);
                }
            } catch (FileExistsException e) {
                logger.warn("Erreur lors de la modification du circuit");
                return null;
            }

            if (!publicCircuit) {
                Map<QName, Serializable> privProps = new HashMap<QName, Serializable>();
                privProps.put(ParapheurModel.PROP_PRIVATE_WORKFLOW_ACL_PARAPHEURS, (Serializable) acl);
                privProps.put(ParapheurModel.PROP_PRIVATE_WORKFLOW_ACL_GROUPS, (Serializable) groupes);
                nodeService.addAspect(res, ParapheurModel.ASPECT_PRIVATE_WORKFLOW, privProps);
            } else if (nodeService.hasAspect(res, ParapheurModel.ASPECT_PRIVATE_WORKFLOW)) {
                nodeService.removeAspect(res, ParapheurModel.ASPECT_PRIVATE_WORKFLOW);
            }

            // Construction arbre XML
            String XMLData = null;
            Document doc = DocumentHelper.createDocument();
            Element root = doc.addElement("circuit").addAttribute("version", "3.0");

            //  droits d'usage si !=public
            if (!publicCircuit) {
                if (acl != null) {
                    Element aclElt = root.addElement("acl");
                    for (NodeRef elt : acl) {
                        aclElt.addElement("parapheur").addText(elt.toString());
                    }
                }
                if (groupes != null) {
                    Element grpElt = root.addElement("groupes");
                    for (String elt : groupes) {
                        if (!elt.equalsIgnoreCase("GESTIONNAIRE_CIRCUITS_IPARAPHEUR")) {
                            grpElt.addElement("groupe").addText(elt);
                        }
                    }
                }
            }
            // Etapes du circuit
            Element etapes = root.addElement("etapes");
            for (EtapeCircuit etape : circuit) {
                Element etapeElt = etapes.addElement("etape");
                // Objet: parapheur, 'chef de'
                etapeElt.addElement("transition").addText(etape.getTransition());
                if (EtapeCircuit.TRANSITION_PARAPHEUR.equals(etape.getTransition())) {
                    etapeElt.addElement("parapheur").addText(etape.getParapheur().toString());
                }
                // Action demandée
                etapeElt.addElement("action-demandee").addText(etape.getActionDemandee());
                // liste de notification !
                Element diffusionElt = etapeElt.addElement("diffusion");
                for (NodeRef notifie : etape.getListeNotification()) {
                    diffusionElt.addElement("noderef").addText(notifie.toString());
                }
            }

            try {   // transformation XML==>String
                StringWriter out = new StringWriter(1024);
                XMLWriter writerTmp = new XMLWriter(OutputFormat.createPrettyPrint());
                writerTmp.setWriter(out);
                writerTmp.write(doc);
                XMLData = out.toString();

                // Ecriture des données XML dans le nouveau noeud
                ContentWriter writer = this.contentService.getWriter(res, ContentModel.PROP_CONTENT, true);
                writer.setMimetype(MimetypeMap.MIMETYPE_XML);
                writer.setEncoding("UTF-8");
                writer.putContent(XMLData);
                if (!publicCircuit) {   // Aspect pour ACL
                    Map<QName, Serializable> ppties = new HashMap<QName, Serializable>();
                    if (acl != null) {
                        StringBuilder str = new StringBuilder("");
                        for (NodeRef elt : acl) {
                            str.append(elt.toString())
                                    .append(",");
                        }
                        ppties.put(ParapheurModel.PROP_CIRCUIT_ACL_PARAPHEURS, str.toString());
                    }
                    if (groupes != null) {
                        StringBuilder str = new StringBuilder("");
                        for (String elt : groupes) {
                            if (!elt.equalsIgnoreCase("GESTIONNAIRE_CIRCUITS_IPARAPHEUR")) {
                                str.append(elt)
                                        .append(",");
                            }
                        }
                        ppties.put(ParapheurModel.PROP_CIRCUIT_ACL_GROUPES, str.toString());
                    }
                    nodeService.addAspect(res, ParapheurModel.ASPECT_CIRCUIT_ACCESS_LISTE, ppties);
                }
            } catch (IOException e) {
                if (nodeService.exists(res)) {
                    fileFolderService.delete(res);
                }
                return null;
            }
        }
        return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isGestionnaireCircuits(String)
     */
    public boolean isGestionnaireCircuits(String userName) {
        Set<String> authorities = this.authorityService.getAuthoritiesForUser(userName);
        // Dans Alfresco les groupes sont préfixés par 'GROUP_', d'où la chaine ci-dessous
        return authorities.contains("GROUP_GESTIONNAIRE_CIRCUITS_IPARAPHEUR");
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#isRecuperable(org.alfresco.service.cmr.repository.NodeRef)
     */
    public boolean isRecuperable(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        return (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_RECUPERABLE);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getParapheurResponsable(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getParapheurResponsable(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        List<AssociationRef> hierarchie = nodeService.getTargetAssocs(parapheur, ParapheurModel.ASSOC_HIERARCHIE);

        if (hierarchie.size() == 1) {
            return hierarchie.get(0).getTargetRef();
        } else {
            return null;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setDelegation(org.alfresco.service.cmr.repository.NodeRef,
     *      org.alfresco.service.cmr.repository.NodeRef)
     */
    public void setDelegation(NodeRef parapheur, NodeRef parapheurDelegue) {
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        if (parapheurDelegue != null) {
            Assert.isTrue(isParapheur(parapheurDelegue), "NodeRef doit être un ph:parapheur");
        }

        // On supprime la délégation passée si elle existe
        List<AssociationRef> listAssoc = nodeService.getTargetAssocs(parapheur, ParapheurModel.ASSOC_DELEGATION);
        for (AssociationRef assoRef : listAssoc) {
            nodeService.removeAssociation(parapheur, assoRef.getTargetRef(), ParapheurModel.ASSOC_DELEGATION);
        }

        if (parapheurDelegue != null) {
            NodeRef delegue = followDelegation(parapheurDelegue);
            // étant donné qu'on a déjà "cassé" l'ancienne délégation, si mettre en place
            // cette nouvelle délégation devait créer une boucle, ça ne pourrait être
            // que parce que "parapheurDelegue" délègue (directement ou indirectement) à
            // "parapheur". "followDelegation" détecte déjà les boucles existantes.
            if (parapheur.equals(delegue)) {
                throw new RuntimeException("Impossible choisir cette délégation, celà créerait une boucle.");
            }

            nodeService.createAssociation(parapheur, parapheurDelegue, ParapheurModel.ASSOC_DELEGATION);

            listAssoc = nodeService.getTargetAssocs(parapheur, ParapheurModel.ASSOC_OLD_DELEGATION);
            for (AssociationRef assoRef : listAssoc) {
                nodeService.removeAssociation(parapheur, assoRef.getTargetRef(), ParapheurModel.ASSOC_OLD_DELEGATION);
            }
            nodeService.createAssociation(parapheur, parapheurDelegue, ParapheurModel.ASSOC_OLD_DELEGATION);
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDelegation(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getDelegation(NodeRef parapheur) {
        if (parapheur == null) {
            return null;
        }

        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        List<AssociationRef> listAssoc = nodeService.getTargetAssocs(parapheur, ParapheurModel.ASSOC_DELEGATION);
        if (listAssoc != null && listAssoc.size() == 1) {
            return listAssoc.get(0).getTargetRef();
        }

        return null;
    }

    /**
     * @see ParapheurService#getOldDelegation(org.alfresco.service.cmr.repository.NodeRef) 
     */
    public NodeRef getOldDelegation(NodeRef parapheur) {
        if (parapheur == null) {
            return null;
        }
        
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        List<AssociationRef> listAssoc = nodeService.getTargetAssocs(parapheur, ParapheurModel.ASSOC_OLD_DELEGATION);
        if (listAssoc != null && listAssoc.size() == 1) {
            return listAssoc.get(0).getTargetRef();
        }

        return null;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSecretariatParapheur(org.alfresco.service.cmr.repository.NodeRef)
     */
    @SuppressWarnings("unchecked")
    public List<String> getSecretariatParapheur(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        List<String> lstSecretaires;
        if (nodeService.getProperty(parapheur, ParapheurModel.PROP_SECRETAIRES) != null) {
            lstSecretaires = (List<String>) nodeService.getProperty(parapheur, ParapheurModel.PROP_SECRETAIRES);
        } else {
            lstSecretaires = Collections.<String>emptyList();
        }

        return lstSecretaires;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#moveDossier(org.alfresco.service.cmr.repository.NodeRef,
     *      org.alfresco.service.cmr.repository.NodeRef)
     */
    public void moveDossier(NodeRef dossier, NodeRef parapheur) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");
        Assert.isTrue(!isTermine(dossier), "Le dossier est \"terminé\" : id=" + dossier);
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        // Modification de l'étape courante
        EtapeCircuitImpl etapeCourante = (EtapeCircuitImpl) this.getCurrentEtapeCircuit(dossier);
        nodeService.setProperty(etapeCourante.getNodeRef(), ParapheurModel.PROP_PASSE_PAR, parapheur);

        // Déplacement du dossier
        NodeRef corbeille = null;
        ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
        corbeille = getCorbeille(parapheur, nodeService.getPrimaryParent(oldParentAssoc.getParentRef()).getQName());
        Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);
        try {
            nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
        } catch (DuplicateChildNodeNameException ex) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Impossible de transmettre le dossier : un dossier de même nom existe dans le parapheur de destination");
            }
            throw new RuntimeException("Un dossier de même nom existe dans le parapheur de destination", ex);
        }

        // Envoi d'un mail à l'utilisateur concerné
        mail("current", "parapheur-current-reception.ftl", dossier);

        // Mise à jour des indicateurs de lecture
        nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.TRUE);
        setDossierNonLu(dossier);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSignature(org.alfresco.service.cmr.repository.NodeRef)
     */
    public byte[] getSignature(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");
        String signatureString = (String) this.nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_ELECTRONIQUE);
        byte[] signature = null;
        if (signatureString != null) {
            signature = Base64.decode(signatureString);
        }
        return signature;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSignature(com.atolcd.parapheur.repo.EtapeCircuit)
     */
    public byte[] getSignature(EtapeCircuit etape) {
        if (etape == null) {
            return null;
        }
        String signatureString = (String) this.nodeService.getProperty(((EtapeCircuitImpl) etape).getNodeRef(), ParapheurModel.PROP_SIGNATURE_ETAPE);
        byte[] signature = null;
        if (signatureString != null) {
            signature = Base64.decode(signatureString);
        }
        return signature;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setSignature(org.alfresco.service.cmr.repository.NodeRef, EtapeCircuit, byte[])
     */
    public void setSignature(NodeRef dossier, EtapeCircuit etapeCircuit, byte[] signedBytes) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        String sigFormat = (String) nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT);
        if (sigFormat.startsWith("PKCS#7")) {
            int stI = (signedBytes.length>10 ? 10 : signedBytes.length);
            String sigStart = new String(signedBytes, 0,  stI);
            if (!sigStart.equals("-----BEGIN")) {
                /**
                 * On stocke la signature format PEM, pas DER
                 */
                signedBytes = PKCS7VerUtil.der2pem(signedBytes);
            }
        }

        String signatureString = Base64.encodeBytes(signedBytes, Base64.DONT_BREAK_LINES);
        this.nodeService.setProperty(((EtapeCircuitImpl) etapeCircuit).getNodeRef(),
                ParapheurModel.PROP_SIGNATURE_ETAPE, signatureString);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#setSignature(org.alfresco.service.cmr.repository.NodeRef, byte[])
     */
    public void setSignature(NodeRef dossier, byte[] signedBytes) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        String sigFormat = (String) nodeService.getProperty(dossier, ParapheurModel.PROP_SIGNATURE_FORMAT);
        if (sigFormat.startsWith("PKCS#7")) {
            int stI = (signedBytes.length>10 ? 10 : signedBytes.length);
            String sigStart = new String(signedBytes, 0,  stI);
            if (!sigStart.equals("-----BEGIN")) {
                /**
                 * On stocke la signature format PEM, pas DER
                 */
                signedBytes = PKCS7VerUtil.der2pem(signedBytes);
            }
        }

        String signatureString = Base64.encodeBytes(signedBytes, Base64.DONT_BREAK_LINES);
        this.nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE_ELECTRONIQUE, signatureString);
        // MODIF STV: la signature est portée par l'étape, pas par le dossier
        // Cela permet la co-signature
        this.nodeService.setProperty(((EtapeCircuitImpl) this.getCurrentEtapeCircuit(dossier)).getNodeRef(),
                ParapheurModel.PROP_SIGNATURE_ETAPE, signatureString);

        //  Cas signature HELIOS-XADES on tente d'encapsuler la signature dans le document principal au chemin XPath fourni
        Serializable tdt_Protocole = nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_PROTOCOLE);
        if (tdt_Protocole != null && tdt_Protocole.toString().equalsIgnoreCase("HELIOS") & signedBytes != null) {
            logger.debug("Encapsulage signature XAdES dans doc PES");
            NodeRef nodeDoc = getDocuments(dossier).get(0);
            ContentReader reader = contentService.getReader(nodeDoc, ContentModel.PROP_CONTENT);
            String docEncoding = reader.getEncoding().toUpperCase();
            byte[] bytesToSign = new byte[(int) reader.getSize()];  //  on se limite à Integer.MAX_VALUE = 2147483647, quid si le fichier est plus gros (>2Go) ?
            try {
                reader.getContentInputStream().read(bytesToSign);
            } catch (IOException e) {
                logger.warn("Signature XAdES: Impossible de lire le doc XML non signé pour injection.");
                bytesToSign = null;
                return;
            }
            String strSig = null;
            String PES_Signe = null;
            try {
                strSig = new String(signedBytes, docEncoding);
                String strXml = new String(bytesToSign, docEncoding);
                String strXPath = (String) this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE);
                bytesToSign = null;  // Liberez la memoire!!
                rt.gc();
                if (logger.isDebugEnabled()) {
                    logger.debug("Injection '"+docEncoding+"' dans '"+ strXPath +"' de Signature:" + strSig.substring(0, (strSig.length()>40)?40:strSig.length()));
                }
                PES_Signe = xades.injectXadesSigIntoXml(strXml, strSig, strXPath, docEncoding);
                if (logger.isDebugEnabled()) {
                    logger.debug("  Tentative d'injection finie");
                }
                // logger.debug("MON PES Signé:\n" + PES_Signe);
                strXml = null;   // Liberez la memoire!!
                rt.gc();
            } catch (UnsupportedEncodingException e) {
                logger.error("Encoding not supported", e);
            }
            if (PES_Signe != null && !PES_Signe.trim().isEmpty()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("  MaJ en entrepot...");
                }
                ContentWriter writer = this.contentService.getWriter(getDocuments(dossier).get(0), ContentModel.PROP_CONTENT, true);
                writer.setEncoding(reader.getEncoding());
                writer.setMimetype(reader.getMimetype());
                writer.putContent(PES_Signe);
                if (logger.isDebugEnabled()) {
                    logger.debug("    ...MaJ entrepot OK, Fin setSignature.");
                }
            } else {
                logger.warn("  PES_Signe est NULL, Pas de MaJ en Entrepot !!! ############");
            }
        } else {
            /**
             * Cas de signature DIA: la signature qui remonte de l'applet EST
             * le flux signé en entier : donc on met à jour le document
             * principal
             */
            if ("XAdES/DIA".equalsIgnoreCase(sigFormat)) {
                NodeRef diaRef = getDocuments(dossier).get(0);
                ContentReader reader = contentService.getReader(diaRef, ContentModel.PROP_CONTENT);
                ContentWriter writer = this.contentService.getWriter(diaRef, ContentModel.PROP_CONTENT, true);
                writer.setEncoding(reader.getEncoding());
                writer.setMimetype(reader.getMimetype());
                try {
                    writer.putContent(new String(signedBytes, reader.getEncoding().toUpperCase()));
                } catch (UnsupportedEncodingException ex) {
                    logger.error("Encoding not supported", ex);
                }
            }
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSignAppletURL()
     */
    public String getSignAppletURL() {
        String appletUrl = this.configuration.getProperty("signature.applet.url");
        return (appletUrl != null ? appletUrl : null);
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getXPathSignature
     */
    public String getXPathSignature(NodeRef dossier) {
        if (dossier== null) {
            return null;
        }
        Serializable xps = this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE);
        if (xps == null) {
            return null;
        }
        return xps.toString();
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getXadesSignatureProperties
     */
    public Properties getXadesSignatureProperties(NodeRef dossier) {
        Properties props;
        java.io.Serializable tdtNom = this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_NOM);
        if (tdtNom != null && tdtNom.toString().equals("S²LOW")) {
            props = s2lowService.getXadesSignatureProperties();
            logger.debug("getXadesSignatureProperties" + props.toString());
            return props;
        } else {
            // Dorénavant tout type de flux XML pourrait etre signé XAdES (cf DIA), donc on autorise quand même.
            logger.warn("Le dossier n'a pas la propriété TDT_NOM=S²LOW.");
            props = s2lowService.getXadesSignatureProperties();
            return props;
        }
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#parapheurFromName(java.lang.String)
     */
    public NodeRef parapheurFromName(String parapheurName) {
        Assert.hasText(parapheurName, "Le nom passé en paramètre n'est pas une chaîne de caractères valide.");

        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.parapheurs.childname");

        List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
        } catch (AccessDeniedException e) {
            logger.error("Impossible d'accéder au répertoire de stockage des parapheurs.");
            return null;
        }

        if (results != null && results.size() == 1) {
            return nodeService.getChildByName(results.get(0), ContentModel.ASSOC_CONTAINS, parapheurName);
        } else {
            logger.error("Erreur lors de la récupération des parapheurs.");
            return null;
        }
    }

    public NodeRef createDossierPesRetour(String id, String parapheurName, InputStream xmlIS) {
        NodeRef dossierRef = null;
        UserTransaction tx = null;
        tx = this.transactionService.getUserTransaction();
        try {
            tx.begin();

            NodeRef parapheur = this.parapheurFromName(parapheurName);
            if (parapheur != null) {
                Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
                Map<QName, Serializable> typageProps = new HashMap<QName, Serializable>();
                // nom Dossier
                properties.put(ContentModel.PROP_NAME, id);
                dossierRef = this.createDossier(parapheur, properties);
                // doc principal
                FileInfo fileInfo = fileFolderService.create(dossierRef, id, ContentModel.TYPE_CONTENT);
                NodeRef docNodeRef = fileInfo.getNodeRef();
                ContentWriter cw = contentService.getWriter(docNodeRef, ContentModel.PROP_CONTENT, Boolean.TRUE);
                cw.setMimetype("text/xml");
                cw.setEncoding("ISO-8859-1");
                cw.putContent(xmlIS);
                typageProps = this.getTypeMetierProperties("PES");
                typageProps.put(ParapheurModel.PROP_TYPE_METIER, "PES");
                this.nodeService.addAspect(dossierRef, ParapheurModel.ASPECT_TYPAGE_METIER, typageProps);
                // Dans ce cas: pas d'annotations, ni de circuit ni de signataire
                // a priori le dossier est cree dans 'a transmettre'.
                // TODO Vérifier la méthode, peut-etre pas finie
            } else {
                logger.error("Parapheur '" + parapheurName+ "' inconnu.");
            }
            tx.commit();
        } catch (Exception e) {
            try {
                tx.rollback();
            } catch (Exception e1) {
                logger.error(e1.getMessage(), e1);
            }
            logger.error(e.getMessage(), e);
        }

        return dossierRef;
    }

    /**
     * Interroge iParapheur pour obtenir la liste des Types techniques
     * 
     * @return flux XML des types techniques
     */
    @Override
    public InputStream getSavedXMLTypes() {
        logger.debug("getSavedXMLTypes: BEGIN");
        NodeRef xmlTypesNoderef = null;
        // Construction de: app:company_home/app:dictionary/cm:ph_x003a_metiertype/TypesMetier.xml
        String xpath = "/" + this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/cm:metiertype/*";
        List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
            if (results != null && results.size() == 1) { // found what we were looking for
                logger.debug("getSavedXMLTypes: Found 1 node");
                xmlTypesNoderef = results.get(0);
                ContentReader contentreader = contentService.getReader(xmlTypesNoderef, ContentModel.PROP_CONTENT);
                return contentreader.getContentInputStream(); // return contentreader.getContentString();
            } else {
                logger.debug("getSavedXMLTypes: (xpath=" + xpath + "), Found " + results.size() + " node(s).");
                return null;
            }
        } catch (AccessDeniedException err) {
            // ignore and return null
            logger.error("getSavedXMLTypes: Access denied Exception");
            return null;
        }
    }

    public List<String> getSavedTypes() {
        List<String> savedTypes = new ArrayList<String>();
        try {
            SAXReader saxreader = new SAXReader();
            InputStream istream = getSavedXMLTypes();
            if (null == istream) {
                throw new DocumentException("cannot access list of types");
            }
            Document docXml = saxreader.read(istream);
            Element rootElement = docXml.getRootElement();
            if (rootElement.getName().equalsIgnoreCase("MetierTypes")) {
                Iterator<Element> itMetierType = rootElement.elementIterator("MetierType");
                String tID = null;
                for (Iterator<Element> ie = itMetierType; ie.hasNext();) {
                    Element mtype = ie.next();
                    tID = mtype.element("ID").getTextTrim();
                    if (getSavedSousTypes(tID).size() > 0) {
                        savedTypes.add(tID);
                    }
                }
            }
        } catch (DocumentException ex) {
            logger.error("Exception parsing saved types XML document", ex);
        }
        return savedTypes;
    }

    /**
     * Interroge iParapheur pour obtenir la liste des sous-Types techniques
     * 
     * @return flux XML des sous-types techniques
     */
    @Override
    public InputStream getSavedXMLSousTypes(String typeID) {
        logger.debug("getSavedXMLSousTypes: BEGIN");

        //StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        StoreRef spacesStoreRef = new StoreRef(this.configuration.getProperty("spaces.store"));
        NodeRef xmlSousTypesNoderef = null;
        // Construction de: app:company_home/app:dictionary/cm:ph_x003a_metiersoustype/...
        // String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" +
        // this.configuration.getProperty("spaces.dictionary.childname") + "/cm:ph_x003a_metiersoustype/cm:" + typeID + ".xml";
        // PATH:"/app:company_home/app:dictionary/cm:ph_x003a_metiersoustype/*" +@cm\:name:"Nouveau Type de ouf.xml"
        //String queryStr = "+PATH:\"/app:company_home/app:dictionary/cm:ph_x003a_metiersoustype/*\" +@cm\\:name:\"" + typeID + ".xml\"";
        //String queryStr = "+PATH:\"/app:company_home/app:dictionary/cm:metiersoustype/*\" +@cm\\:name:\"" + typeID + ".xml\"";
        String xpath = "/"+ this.configuration.getProperty("spaces.company_home.childname") +"/"+ this.configuration.getProperty("spaces.dictionary.childname")
                +"/cm:metiersoustype/*[@cm:name='"+ typeID +".xml']";
        List<NodeRef> results = null; // List<NodeRef> results = null;
        try {
            results = searchService.selectNodes(nodeService.getRootNode(spacesStoreRef), xpath, null, namespaceService, false);
            // results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
            if (results.size() == 1) { // found what we were looking for
                logger.debug("getSavedXMLSousTypes: Found 1 node");
                xmlSousTypesNoderef = results.get(0); // .get(0);
                ContentReader contentreader = contentService.getReader(xmlSousTypesNoderef, ContentModel.PROP_CONTENT);
                if (contentreader != null) {
                    logger.debug("getSavedXMLSousTypes:  contentreader Ok !");
                    return contentreader.getContentInputStream(); // return contentreader.getContentString();
                } else {
                    logger.warn("getSavedXMLSousTypes:  contentreader est NULL !");
                    return null;
                }
            } else {
                logger.warn("getSavedXMLSousTypes: (" + typeID + "), Found " + results.size() + " node(s).");
                return null;
            }
        } catch (AccessDeniedException err) {
            // ignore and return null
            logger.error("getSavedXMLSousTypes: Access denied Exception");
            return null;
        }
    }

    public List<String> getSavedSousTypes(String type) {
        List<String> savedSousTypes = new ArrayList<String>();
        try {
            SAXReader saxreader = new SAXReader();
            InputStream istream = getSavedXMLSousTypes(type);
            if (null == istream) {
                throw new DocumentException("cannot get access to "+type);
            }
            Document docXml = saxreader.read(istream);
            Element rootElement = docXml.getRootElement();
            if (rootElement.getName().equalsIgnoreCase("MetierSousTypes")) {
                Iterator<Element> itMetierSousType = rootElement.elementIterator("MetierSousType");
                String tID = null;
                for (Iterator<Element> ie = itMetierSousType; ie.hasNext();) {
                    Element msoustype = ie.next();
                    tID = msoustype.element("ID").getTextTrim();
                    Attribute attrVisibility = msoustype.attribute("visibility");

                    boolean isPublic = false;
                    List<String> aclParapheurs = new ArrayList<String>();
                    List<String> aclGroups = new ArrayList<String>();

                    if (attrVisibility != null
                            && "public".equals(attrVisibility.getText())) {
                        isPublic = true;
                    }

                    Element mAclParaph = msoustype.element("parapheurs");
                    if (mAclParaph != null) {
                        Iterator<Element> parapheurs = mAclParaph.elementIterator("parapheur");
                        while (parapheurs.hasNext()) {
                            Element parapheur = parapheurs.next();
                            aclParapheurs.add(parapheur.getTextTrim());
                        }
                    }

                    Element mAclGroups = msoustype.element("groups");
                    if (mAclGroups != null) {
                        Iterator<Element> groups = mAclGroups.elementIterator("group");
                        while (groups.hasNext()) {
                            Element group = groups.next();
                            NodeRef groupRef = new NodeRef(group.getTextTrim());
                            String groupName = (String) nodeService.getProperty(groupRef, ContentModel.PROP_AUTHORITY_NAME);
                            aclGroups.add(groupName);
                        }
                    }

                    String currentUser = AuthenticationUtil.getRunAsUser();
                    Collection<String> groups = authorityService.getAuthoritiesForUser(currentUser);
                    NodeRef currentParapheur = getOwnedParapheur(currentUser);

                    if (isPublic
                            || (currentParapheur != null &&
                                aclParapheurs.contains(currentParapheur.toString()))
                            || CollectionsUtils.containsOneOf(aclGroups, groups)) {
                        savedSousTypes.add(tID);
                    }
                }
            }
        } catch (DocumentException ex) {
            logger.error("Exception parsing saved types XML document", ex);
        }

        return savedSousTypes;
    }

    @SuppressWarnings("unchecked")
    @Override
    public NodeRef getCircuitRef(String typeID, String sousTypeID) {
        logger.debug("getCircuitRef: BEGIN");
        //StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        StoreRef spacesStoreRef = new StoreRef(this.configuration.getProperty("spaces.store"));
        NodeRef xmlSousTypeNoderef = null;
        //String queryStr = "+PATH:\"/app:company_home/app:dictionary/cm:ph_x003a_metiersoustype/*\" " + "+@cm\\:name:\""	+ typeID + ".xml\"";
        //String queryStr = "+PATH:\"/app:company_home/app:dictionary/cm:metiersoustype/*\" " + "+@cm\\:name:\"" + typeID + ".xml\"";
        String xpath = "/"+ this.configuration.getProperty("spaces.company_home.childname") +"/"+ this.configuration.getProperty("spaces.dictionary.childname")
                +"/cm:metiersoustype/*[@cm:name='"+ typeID +".xml']";
        List<NodeRef> results = null;
        //
        try {
            results = searchService.selectNodes(nodeService.getRootNode(spacesStoreRef), xpath, null, namespaceService, false);
            // results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
            if (results.size() == 1) { // found what we were looking for
                logger.debug("getCircuitRef: Found 1 xmlSousTypeNoderef node");
                xmlSousTypeNoderef = results.get(0); // .get(0);
                ContentReader contentreader = contentService.getReader(xmlSousTypeNoderef, ContentModel.PROP_CONTENT);
                if (contentreader != null) {
                    logger.debug("getCircuitRef:  contentreader Ok !");
                    SAXReader saxreader = new SAXReader();
                    Document docXml = saxreader.read(contentreader.getContentInputStream());
                    Element rootElement = docXml.getRootElement();
                    if (rootElement.getName().equalsIgnoreCase("MetierSousTypes")) {
                        Iterator<Element> itMetierSousType = rootElement.elementIterator("MetierSousType");
                        for (Iterator<Element> ie = itMetierSousType; ie.hasNext();) {
                            Element msoustype = ie.next();
                            if (sousTypeID.equalsIgnoreCase(msoustype.element("ID").getTextTrim())) {
                                // zou c'est trouvé
                                NodeRef circuitRef = findCircuitRefFromName(getCircuitsHome(), msoustype.element("Circuit").getTextTrim());
                                if (circuitRef == null) {
                                    throw new DocumentException("getCircuitRef: aucun Circuit correspondant à (" + typeID + "," + sousTypeID + ") dans iParapheur");
                                }
                                return circuitRef;
                            }
                        }
                    } else {
                        logger.debug("   getCircuitRef: pas de MetierSousTypes dans XML");
                    }
                    logger.debug("   getCircuitRef: Rien trouvé...");
                    return null; // return contentreader.getContentString();
                } else {
                    logger.debug("getCircuitRef:  contentreader est NULL !?!");
                    return null;
                }
            } else {
                logger.debug("getSavedXMLSousTypes: (" + typeID + "), Found " + results.size() + " xmlSousTypeNoderef node(s).");
                return null;
            }
        } catch (DocumentException de) {
            // ignore and return null
            logger.debug("getCircuitRef: Erreur sur parsing fichier XML");
            return null;
        } catch (AccessDeniedException err) {
            // ignore and return null
            logger.debug("getCircuitRef: Access denied Exception");
            return null;
        }
    }

    /**
     * Fournit les caractéristiques du Type technique donné en paramètre
     * @param typeID : le Type technique iParapheur
     * @return infos TdT
     */
    @SuppressWarnings("unchecked")
    @Override
    public Map<QName, Serializable> getTypeMetierProperties(String typeID) {
        SAXReader saxreader = new SAXReader();
        Map<QName, Serializable> props = new HashMap<QName, Serializable>();
        try {
            Document docXml = saxreader.read(getSavedXMLTypes());
            Element rootElement = docXml.getRootElement();
            if (rootElement.getName().equalsIgnoreCase("MetierTypes")) {
                Iterator<Element> itMetierType = rootElement.elementIterator("MetierType");
                for (Iterator<Element> ie = itMetierType; ie.hasNext();) {
                    Element mtype = ie.next();
                    if (mtype.element("ID").getTextTrim().equalsIgnoreCase(typeID.trim())) {
                        props.put(ParapheurModel.PROP_TDT_NOM, mtype.element("TdT").element("Nom").getTextTrim());
                        props.put(ParapheurModel.PROP_TDT_PROTOCOLE, mtype.element("TdT").element("Protocole").getTextTrim());
                        props.put(ParapheurModel.PROP_TDT_FICHIER_CONFIG, mtype.element("TdT").element("UriConfiguration").getTextTrim());
                        props.put(ParapheurModel.PROP_TYPE_SIGNATURE, this.getTypeSignatureFromProtocoleTDT(mtype.element("TdT").element("Protocole").getTextTrim()));
                        props.put(ParapheurModel.PROP_SIGNATURE_FORMAT, mtype.element("sigFormat").getTextTrim());
                    }
                }
            }
        } catch (DocumentException e) {
            logger.error("getTypeMetierProperties: Erreur sur parsing fichier XML", e);
            return null;
        }
        return props;
    }

    public NodeRef findUserByEmail(String from) {
        StoreRef containerStore = personService.getPeopleContainer().getStoreRef();

        ResultSet results = null;
        String queryStr = "TYPE:\"" + ContentModel.TYPE_PERSON + "\" AND " +
                "@cm\\:email:\"" + from + "\"";

        results = searchService.query(containerStore, SearchService.LANGUAGE_LUCENE, queryStr);
        if (results.length() < 1) {
            throw new NoSuchPersonException("Email inconnu dans le Parapheur");
        }
        // XXX Quid s'il existe plusieurs utilisateurs avec cet email ?? Ici on prend le 1er
        return results.getNodeRef(0);
    }

    public String findEmailForUsername(String username) {
        String emailString = null;
        if (personService.personExists(username)) {
            NodeRef personRef = personService.getPerson(username);
            emailString = (String) nodeService.getProperty(personRef, ContentModel.PROP_EMAIL);
        }
        return emailString;
    }

    public NodeRef rechercheDossier(String nomDossier) {
        //StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        StoreRef spacesStoreRef = new StoreRef(this.configuration.getProperty("spaces.store"));
        ResultSet results = null;
        // Requete:  PATH:"/app:company_home/*" +@cm\:name:"nouveau contrat Hot-line"
        String queryStr = "PATH:\"/app:company_home/*\" +TYPE:\"ph:dossier\" +@cm\\:name:\"" + nomDossier + "\"";
        results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
        if (results.length() < 1) {
            return null;
        } else {
            return results.getNodeRef(0);
        }
    }

    public boolean isNomDossierAlreadyExists(String nomDossier) {
        return (rechercheDossier(nomDossier) != null);
    }

    public List<NodeRef> rechercheDossiers(String type, String soustype, String status) {
        //StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        StoreRef spacesStoreRef = new StoreRef(this.configuration.getProperty("spaces.store"));
        ResultSet results = null;
        String queryStr = "PATH:\"/app:company_home/ph:parapheurs/*\" +TYPE:\"ph:dossier\"";
        // affinage de la requete
        if (type != null) {
            queryStr = queryStr.concat(" +@ph\\:typeMetier:\"" + type + "\"");	// +@ph\:typeMetier:"PES"
        }
        if (soustype != null) {
            queryStr = queryStr.concat(" +@ph\\:soustypeMetier:\"" + soustype + "\"");	// +@ph\:soustypeMetier:"Bordereaux"
        }
        if (status != null) {
            queryStr = queryStr.concat(" +@ph\\:status-metier:\"" + status + "\"");
        }
        logger.debug("rechercheDossiers Query=" + queryStr);
        results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
        if (results.length() < 1) {
            return null;
        } else {
            return results.getNodeRefs();
        }
    }

    /*
     * PRIVATE
     */
    private String getTypeSignatureFromProtocoleTDT(String protocole) {
        if (protocole.equalsIgnoreCase("HELIOS")) {
            return "XADES";
        } else {
            return "CMS";
        }
    }

    private NodeRef findCircuitRefFromName(NodeRef circuitsHome, String circuitName) {
        ResultSet results = null;
        NodeRef res = null;
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");
        try {
            //String query = "+PATH:\"/app:company_home/app:dictionary/ph:savedworkflows/*\" +@cm\\:name:\"" + circuitName + "\"";
            String query = "+PATH:\"/" + xpath + "/*\" +@cm\\:name:\"" + circuitName + "\"";
            logger.debug(query);
            results = searchService.query(circuitsHome.getStoreRef(), SearchService.LANGUAGE_LUCENE, query);
            int nbreResultats = results.length();
            switch (nbreResultats) {
                case 0:
                    throw new RuntimeException("findCircuitRefFromName: Circuit ["+circuitName+"] inconnu !, trouvé 0 noeud!");
                case 1:
                    res = results.getNodeRef(0);
                    break;
                default:
                    for (int i = 0; i < nbreResultats; i++) {
                        if (circuitName.equals(this.nodeService.getProperty(results.getNodeRef(i), ContentModel.PROP_NAME).toString())) {
                            res = results.getNodeRef(i);
                            break;
                        }
                    }
            }
        } catch (Exception e) {
            logger.error(e.getClass() + "---" + e.getMessage());
        } finally {
            if (results != null) {
                results.close();
            }
        }
        return res;
    }

    /**
     * @return The "circuits home" NodeRef
     */
    private NodeRef getCircuitsHome() {
        //StoreRef spacesStoreRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        StoreRef spacesStoreRef = new StoreRef(this.configuration.getProperty("spaces.store"));
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.dictionary.childname") + "/" + this.configuration.getProperty("spaces.savedworkflows.childname");

        ResultSet results = null;
        try {
            String queryStr = "PATH:\"/" + xpath + "\"";
            results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
            if (results.length() != 1) {
                throw new RuntimeException("Noeud des Circuits inconnu !");
            }
            return results.getNodeRef(0);

        } catch (Exception e) {
            logger.error(e.getClass() + "---" + e.getMessage());
        } finally {
            if (results != null) {
                results.close();
            }
        }
        return null;
    }

    /**
     * Retourne le parapheur "délégué" en suivant l'arbre de délégation à partir
     * de {@param parapheur}. Si aucune délégation n'a été mise en place sur
     * {@param parapheur}, celui-ci est retourné.
     * 
     * @param parapheur
     *                le parapheur dont on veut suivre l'arbre de délégation
     * @return le parapheur désigné par l'arbre de délégation de {@param parapheur}
     */
    private NodeRef followDelegation(NodeRef parapheur) {
        Assert.isTrue(isParapheur(parapheur), "NodeRef doit être un ph:parapheur");

        // On parcourt l'arbre de délégation et on ajoute chaque parapheur rencontré
        // à un Set. Si on a déjà rencontré le parapheur, c'est qu'il y a une boucle
        // et on lève donc une exception.
        HashSet<NodeRef> arbreDelegations = new HashSet<NodeRef>();
        for (NodeRef delegue = getDelegation(parapheur); delegue != null; parapheur = delegue, delegue = getDelegation(parapheur)) {
            if (!arbreDelegations.add(parapheur)) {
                throw new RuntimeException("Il y a une boucle de délégation à partir de " + parapheur);
            }
        }

        return parapheur;
    }

    private void setDossierNonLu(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        List<NodeRef> docs = getDocuments(dossier);
        for (NodeRef tmpDoc : docs) {
            this.nodeService.removeAspect(tmpDoc, ParapheurModel.ASPECT_LU);
        }
    }

    private void setDossierLu(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        List<NodeRef> docs = getDocuments(dossier);
        for (NodeRef tmpDoc : docs) {
            this.nodeService.addAspect(tmpDoc, ParapheurModel.ASPECT_LU, null);
        }
    }

    private void mail(String dest, String template, NodeRef ref) {
        Action mail = actionService.createAction("parapheur-mail");
        mail.setParameterValue("dest", dest);
        mail.setParameterValue("template", template);
        mail.setExecuteAsynchronously(false);
        this.actionService.executeAction(mail, ref);
    }
    private void mail(String dest, String template, NodeRef ref, String annotation) {
        Action mail = actionService.createAction("parapheur-mail");
        mail.setParameterValue("dest", dest);
        mail.setParameterValue("template", template);
        mail.setParameterValue("annotation", annotation);
        mail.setExecuteAsynchronously(false);
        this.actionService.executeAction(mail, ref);
    }

    public EtapeCircuit getCurrentEtapeCircuit(NodeRef dossier) {
        Assert.isTrue(isDossier(dossier), "NodeRef doit être un ph:dossier");

        List<EtapeCircuit> circuit = getCircuit(dossier);
        if (circuit == null) {
            return null;
        }
        if (isTermine(dossier) && circuit.get(0).getActionDemandee() == null) {
            return null;
        }

        for (EtapeCircuit etape : circuit) {
            if (!etape.isApproved()) {
                return (EtapeCircuitImpl) etape;
            }
        }
        return null;
    }

    private boolean isOfType(NodeRef nodeRef, QName type) {
        Assert.notNull(nodeRef, "Node Ref is mandatory");
        Assert.isTrue(this.nodeService.exists(nodeRef), "Node Ref must exist in the repository");
        Assert.notNull(type, "Type is mandatory");

        // find it's type so we can see if it's a node we are interested in
        QName type2 = this.nodeService.getType(nodeRef);

        // make sure the type is defined in the data dictionary
        TypeDefinition typeDef = this.dictionaryService.getType(type2);

        if (typeDef == null) {
            if (logger.isEnabledFor(Level.WARN)) {
                logger.warn("Found invalid object in database: id = " + nodeRef + ", type = " + type2);
            }

            return false;
        }

        return this.dictionaryService.isSubClass(type2, type);
    }

    private NodeRef getPrimaryParent(NodeRef nodeRef) {
        return nodeService.getPrimaryParent(nodeRef).getParentRef();
    }

    private NodeRef getFirstChild(NodeRef nodeRef, QName childAssocType) {
        List<ChildAssociationRef> childAssocs = this.nodeService.getChildAssocs(nodeRef, childAssocType,
                RegexQNamePattern.MATCH_ALL);

        if (childAssocs.size() == 1) {
            return childAssocs.get(0).getChildRef();
        }

        return null;
    }

    private NodeRef getFirstParentOfType(NodeRef nodeRef, QName parentType) {
        Assert.notNull(nodeRef, "Node Ref is mandatory");
        Assert.isTrue(this.nodeService.exists(nodeRef), "Node Ref must exist in the repository");
        Assert.notNull(parentType, "Type is mandatory");

        for (NodeRef parent = nodeRef; parent != null && this.nodeService.exists(parent); parent = getPrimaryParent(parent)) {
            if (isOfType(parent, parentType)) {
                return parent;
            }
        }

        return null;
    }

    public Map<String, String> getSignaturePolicy(X509Certificate cert) {
        /**
         * Attention: signature policy != certification policy
         * La politique de signature ne se trouve pas dans le certificat.
         * C'est pourquoi:   pId = null
         */
        Map<String, String> properties = X509Util.getPolicyProperties(cert);
        String pId = null; // properties.get("pPolicyIdentifierID");
        if (pId != null) {
            String xpath = "/app:company_home/app:dictionary/ph:signaturePolicies/*[@cm:name=\"" + pId + "\"]";
            List<NodeRef> results = searchService.selectNodes(
                    nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE),
                    xpath, null, namespaceService, false);
            NodeRef policyRef = null;
            if (results.size() >= 1) {
                policyRef = results.get(0);
            } else {
                NodeRef policyHome = getPolicyHomeNodeRef();
                Map<QName, Serializable> policyProps = new HashMap<QName, Serializable>();
                policyProps.put(ContentModel.PROP_NAME, pId);
                policyProps.put(ContentModel.PROP_TITLE, properties.get("pSPURI"));
                policyProps.put(ContentModel.PROP_DESCRIPTION, properties.get("pPolicyIdentifierDescription"));
                policyRef = nodeService.createNode(policyHome, ContentModel.ASSOC_CONTAINS,
                        QName.createQName("cm:" + pId, namespaceService), ContentModel.PROP_CONTENT, policyProps).getChildRef();
                updateSignaturePolicyHash(policyRef);
            }
            if (policyRef == null) {
                // toujours ce cas particulier
            } else {
                ContentReader reader = contentService.getReader(policyRef, ContentModel.PROP_CONTENT);
                if (reader == null) {
                    Properties xades = s2lowService.getXadesSignatureProperties();
                    properties.put("pPolicyIdentifierID", xades.getProperty("pPolicyIdentifierID"));
                    properties.put("pPolicyIdentifierDescription", xades.getProperty("pPolicyIdentifierDescription"));
                    properties.put("pPolicyDigest", xades.getProperty("pPolicyDigest"));
                    properties.put("pSPURI", xades.getProperty("pSPURI"));
                    logger.warn("Unable to get true PolicyDigest");
                } else {
                    properties.put("pPolicyDigest", reader.getContentString());
                }
            }
        } else {
            Properties xades = s2lowService.getXadesSignatureProperties();
            properties.put("pPolicyIdentifierID", xades.getProperty("pPolicyIdentifierID"));
            properties.put("pPolicyIdentifierDescription", xades.getProperty("pPolicyIdentifierDescription"));
            properties.put("pPolicyDigest", xades.getProperty("pPolicyDigest"));
            properties.put("pSPURI", xades.getProperty("pSPURI"));
        }
        return properties;
    }

    public NodeRef getPolicyHomeNodeRef() {
        NodeRef policyHome = null;
        String policiesPath = "/app:company_home/app:dictionary/ph:signaturePolicies";
        List<NodeRef> policiesHomes = searchService.selectNodes(
                nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE),
                policiesPath,
                null,
                namespaceService,
                false);

        if (policiesHomes.size() > 0) {
            policyHome = policiesHomes.get(0);
        } else {
            NodeRef dictionary = searchService.selectNodes(
                    nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE),
                    "/app:company_home/app:dictionary",
                    null,
                    namespaceService,
                    false).get(0);

            policyHome = nodeService.createNode(dictionary,
                    ContentModel.ASSOC_CONTAINS,
                    QName.createQName("ph:signaturePolicies", namespaceService),
                    ContentModel.TYPE_FOLDER).getChildRef();
        }
        return policyHome;
    }

    public void updateSignaturePolicyHash(NodeRef policy) {
        String spuri = (String) nodeService.getProperty(policy, ContentModel.PROP_TITLE);
        try {
            URL spurl = new URL(spuri);

            URLConnection spcon = spurl.openConnection();
            InputStream spin = spcon.getInputStream();
            ByteArrayOutputStream spout = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int readed;
            while ((readed = spin.read(buffer)) != -1) {
                spout.write(buffer, 0, readed);
            }

            byte[] data = spout.toByteArray();

            String content = new String(data);
            if (content.contains("<html>") || content.contains("<HTML>")) {
                Pattern pattern = Pattern.compile("(u|U)(r|R)(l|L)=([^\"]+)");
                Matcher matcher = pattern.matcher(content);

                if (matcher.find()) {
                    URL newUrl = new URL(spurl, matcher.group(4));

                    URLConnection newspcon = newUrl.openConnection();
                    InputStream newspin = newspcon.getInputStream();
                    ByteArrayOutputStream newspout = new ByteArrayOutputStream();
                    while ((readed = newspin.read(buffer)) != -1) {
                        newspout.write(buffer, 0, readed);
                    }

                    data = newspout.toByteArray();
                }
            }

            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(data);
            String b64hash = Base64.encodeBytes(digest.digest());

            ContentWriter writer = contentService.getWriter(policy, ContentModel.PROP_CONTENT, true);
            writer.setMimetype(MimetypeMap.MIMETYPE_BINARY);
            writer.putContent(b64hash);

            spin.close();
        } catch (MalformedURLException ex) {
            throw new RuntimeException("Invalid SPURI for policy", ex);
        } catch (IOException ex) {
            throw new RuntimeException("Can't download policy", ex);
        } catch (Exception ex) {
            throw new RuntimeException("Unknown exception", ex);
        }
    }

    public boolean checkSignature(EtapeCircuit etapeCircuit) {
        NodeRef dossierRef = getParentDossier(((EtapeCircuitImpl) etapeCircuit).getNodeRef());
        String sigFormat = (String) nodeService.getProperty(dossierRef, ParapheurModel.PROP_SIGNATURE_FORMAT);
        if (!sigFormat.startsWith("PKCS#7")) {
            logger.info("checkSignature: pas de vérif car sigFormat="+sigFormat);
            return false;
        }
        logger.debug("checkSignature avec format="+sigFormat);
        byte[] signature = getSignature(etapeCircuit);
        NodeRef document = getDocuments(dossierRef).get(0);
        ContentReader docReader = contentService.getReader(document, ContentModel.PROP_CONTENT);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        docReader.getContent(out);
        byte[] data = out.toByteArray();
        return PKCS7VerUtil.verify(data, PKCS7VerUtil.pem2der(signature, "-----BEGIN".getBytes(), "-----END".getBytes()));
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getSignatureProperties(com.atolcd.parapheur.repo.EtapeCircuit)
     */
    public List<Map<String, String>> getSignatureProperties(EtapeCircuit etape) {
        List<Map<String, String>> sigProps = new ArrayList<Map<String, String>>();
        NodeRef dossierRef = getParentDossier(((EtapeCircuitImpl) etape).getNodeRef());
        String sigFormat = (String) nodeService.getProperty(dossierRef, ParapheurModel.PROP_SIGNATURE_FORMAT);
        if (!sigFormat.startsWith("PKCS#7")) {
            return sigProps;
        }
        try {
            byte[] signature = getSignature(etape);
            Security.addProvider(new BouncyCastleProvider());
            CMSSignedData signedData = new CMSSignedData(PKCS7VerUtil.pem2der(signature, "-----BEGIN".getBytes(), "-----END".getBytes()));
            CertStore certs = signedData.getCertificatesAndCRLs("Collection", "BC");
            SignerInformationStore signers = signedData.getSignerInfos();
            Iterator<SignerInformation> iterator = signers.getSigners().iterator();
            while (iterator.hasNext()) {
                SignerInformation signerInfo = (SignerInformation) iterator.next();
                /*DERSequence timeSeq = (DERSequence) signerInfo.getSignedAttributes()
                        .get(new DERObjectIdentifier("1.2.840.113549.1.9.5"))
                        .toASN1Object()
                        .getDERObject();*/
                if (signerInfo != null) {
                    Date signingDate = null;
                    DERUTCTime time = null;
                    if (signerInfo.getSignedAttributes() != null
                            && signerInfo.getSignedAttributes().get(new DERObjectIdentifier("1.2.840.113549.1.9.5")) != null) {
                        /**
                         * 1.2.840.113549.1.9.5 is Signing date Attribute, equivalent to PKCS9Attribute.SIGNING_TIME_OID
                         */
                        DERObject timeObject = signerInfo.getSignedAttributes()
                                .get(new DERObjectIdentifier("1.2.840.113549.1.9.5"))
                                .toASN1Object();

                        if (timeObject != null) {
                            DERSequence timeSeq = (DERSequence) timeObject.getDERObject();
                            DERSet timeSet = (DERSet) timeSeq.getObjectAt(1);
                            time = (DERUTCTime) timeSet.getObjectAt(0);
                            signingDate = time.getAdjustedDate();
                        } else {
                            logger.warn("No SIGNING Time found for PKCS7 ???");
                        }
                    } else {
                        /**
                         * is there any Signed Attributes??
                         */
                        if (signerInfo.getSignedAttributes() != null) {
                            logger.warn("No SIGNING Time attribute found for PKCS7 ???\n toString=" + signerInfo.getSignedAttributes().toString());
                        } else {
                            logger.warn("No Signed attributes found for PKCS7???, has "
                                    + signerInfo.getUnsignedAttributes().toASN1EncodableVector().size() + " unsigned attributes."
                                    /* .getCounterSignatures().size() + "counterSigs."*/);
                        }
                        /***
                         * Let's check for the Unsigned attributes....
                         */
                        
                        /**
                         * tSTInfo ?
                         */
//                        DERObjectIdentifier tSTInfoOID = new DERObjectIdentifier("1.2.840.113549.1.9.16.1.4");
//                        if (signerInfo.getUnsignedAttributes().get(tSTInfoOID) != null) {
//                            logger.warn("\tHAZ tSTInfo");
//                        } else {
//                            logger.warn("no tSTInfo.");
//                        }

                        /**
                         * possible timestamp?
                         */
                        List<TimeStampToken> timeStamps = (List) TSPUtil.getSignatureTimestamps(signerInfo, null);
                        if (!timeStamps.isEmpty()) {
                            signingDate = timeStamps.get(0).getTimeStampInfo().getGenTime();
                        }

//                System.out.println(timeStamps.get(0).getTimeStampInfo().getGenTime().toString());

                        /**
                         * timeStampToken ?
                         *   ok The HARD part is to read it now...
                         */
//                        DERObjectIdentifier timeStampTokenOID = new DERObjectIdentifier("1.2.840.113549.1.9.16.2.14");
//                        if (signerInfo.getUnsignedAttributes().get(timeStampTokenOID) != null) {
//                            logger.warn("\tHAZ timeStampTokenOID");
//                            DERObject timeStampObject = signerInfo.getUnsignedAttributes()
//                                    .get(timeStampTokenOID).toASN1Object();
//                            if (timeStampObject != null) {
//                                /**
//                                 * try to build the object and work it out
//                                 */
////                                org.bouncycastle.asn1.cms.Attribute attribute = signerInfo.getUnsignedAttributes().get(timeStampTokenOID);
////                                logger.warn("\n \t #### " + attribute.getAttrValues().size() + " values.");
////                                ContentInfo contentInfo = ContentInfo.getInstance(attribute.getDERObject());  //((DERSequence)timeStampObject.getDERObject());
////                                TimeStampToken tsToken = new TimeStampToken(contentInfo);
////                                TimeStampTokenInfo info = tsToken.getTimeStampInfo(); // to view details
////                                signingDate = info.getGenTime();
//                            } else {
//                                logger.warn("\t no timeStampTokenOID, WTF?");
//                            }
//                        } else {
//                            logger.warn("\t no timeStampTokenOID");
//                        }

//                    SEQUENCE {
//4523   11:               OBJECT IDENTIFIER
//         :                 timeStampToken (1 2 840 113549 1 9 16 2 14)
//4536 1620:               SET {
//4540 1616:                 SEQUENCE {
//4544    9:                   OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
//4555 1601:                   [0] {
//4559 1597:                     SEQUENCE {
//4563    1:                       INTEGER 1
//4566   11:                       SET {
//4568    9:                         SEQUENCE {
//4570    5:                           OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
//4577    0:                           NULL
//         :                           }
//         :                         }
//4579  108:                       SEQUENCE {
//4581   11:                         OBJECT IDENTIFIER
//         :                           tSTInfo (1 2 840 113549 1 9 16 1 4)
//4594   93:                         [0] {
//4596   91:                           OCTET STRING, encapsulates {
//4598   89:                             SEQUENCE {
//4600    1:                               INTEGER 1
//4603   11:                               OBJECT IDENTIFIER
//         :                                 timeStampToken (1 2 840 113549 1 9 16 2 14)
//4616   33:                               SEQUENCE {
//4618    9:                                 SEQUENCE {
//4620    5:                                   OBJECT IDENTIFIER
//         :                                     sha1 (1 3 14 3 2 26)
//4627    0:                                   NULL
//         :                                   }
//4629   20:                                 OCTET STRING
//         :                   CE E6 0C C4 BB 2E BA C1 24 CF 52 70 92 C4 88 43
//         :                   F3 2E CA E8
//         :                                 }
//4651    2:                               INTEGER 32116
//4655   15:                               GeneralizedTime 15/10/2010 16:22:18 GMT
                    }
                    X509Certificate cert = (X509Certificate) certs.getCertificates(signerInfo.getSID()).iterator().next();
                    Map<String, String> props = new HashMap<String, String>();
                    props.put("subject_name", extractCN(cert.getSubjectX500Principal().getName()));
                    props.put("issuer_name", extractCN(cert.getIssuerX500Principal().getName()));
                    SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy 'à' HH:mm", Locale.FRENCH);
                    if (signingDate != null) {
                        props.put("signature_date", sdf.format(signingDate));    // time.getDate().toString()); //etape.getDateValidation().toString());
                    } else {
                        props.put("signature_date", etape.getDateValidation().toString());
                        logger.warn("PKCS7 date signature missing ???  ");
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("  PKCS7 signature found: " + props.get("subject_name"));
                    }
                    props.put("certificate_valid_from", sdf.format(cert.getNotBefore()));  // cert.getNotBefore().toString());
                    props.put("certificate_valid_to", sdf.format(cert.getNotAfter()));  // cert.getNotAfter().toString());
                    sigProps.add(props);
                }
            }
            return sigProps;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public void markAsRead(NodeRef dossier) {
        nodeService.addAspect(dossier, ParapheurModel.ASPECT_LU, null);
    }

    public boolean isRead(NodeRef dossier) {
        return nodeService.hasAspect(getDocuments(dossier).get(0), ParapheurModel.ASPECT_LU);
    }

    public boolean isReadingMandatory(NodeRef dossier) {
        Boolean readingMandatory = (Boolean) nodeService.getProperty(dossier, ParapheurModel.PROP_READING_MANDATORY);
        
        if (readingMandatory == null) {
            // Backward compatibility : reading was mandatory by default
            return true;
        }

        return readingMandatory;
    }

    public NodeRef getParapheursHome() {
        String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/" + this.configuration.getProperty("spaces.parapheurs.childname");

        List<NodeRef> results = null;
        results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);

        return results.get(0);
    }

    public List<NodeRef> getChildParapheurs(NodeRef nodeRef) {
        List<AssociationRef> associations = nodeService.getSourceAssocs(nodeRef, ParapheurModel.ASSOC_HIERARCHIE);
        List<NodeRef> children = new ArrayList<NodeRef>();
        for (AssociationRef association : associations) {
            children.add(association.getSourceRef());
        }
        return children;
    }

    private class DocumentTransactionListener extends TransactionListenerAdapter {

        @Override
        public void afterCommit() {
            logger.debug("AFTER COMMIT !");
            @SuppressWarnings("unchecked")
            Set<NodeUpdater> readNodeUpdaters = (Set<NodeUpdater>) AlfrescoTransactionSupport.getResource(KEY_PENDING_READ_NODES);
            if (readNodeUpdaters != null) {
                logger.debug("AJOUT DE NOEUDS LUS");
                for (NodeUpdater nodeUpdater : readNodeUpdaters) {
                    logger.debug("NOEUD : " + nodeUpdater.getNoderef().toString());
                    Runnable runnable = new ReadIndicatorChecker(nodeUpdater);
                    threadExecuter.execute(runnable);
                }
            }
            @SuppressWarnings("unchecked")
            Set<NodeUpdater> writeNodeUpdaters = (Set<NodeUpdater>) AlfrescoTransactionSupport.getResource(KEY_PENDING_UPDATE_NODES);
            if (writeNodeUpdaters != null) {
                logger.debug("AJOUT DE NOEUDS MIS A JOUR");
                for (NodeUpdater nodeUpdater : writeNodeUpdaters) {
                    logger.debug("NOEUD : " + nodeUpdater.getNoderef().toString());
                    Runnable runnable = new UpdateIndicatorChecker(nodeUpdater);
                    threadExecuter.execute(runnable);
                }
            }
        }

    }

    private class ReadIndicatorChecker implements Runnable {

        private NodeUpdater nodeUpdater;

        private ReadIndicatorChecker(NodeUpdater nodeUpdater) {
            this.nodeUpdater = nodeUpdater;
        }

        public void run() {
            final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
            final RetryingTransactionCallback<Object> callback = new RetryingTransactionCallback<Object>() {

                public Object execute() throws Throwable {
                    NodeRef nodeRef = nodeUpdater.getNoderef();
                    if (nodeService.exists(nodeRef)) {
                        NodeRef dossierRef = getParentDossier(nodeRef);
                        if (dossierRef != null) {
                            String username = nodeUpdater.getUsername();
                            NodeRef parapheurRef = getParentParapheur(dossierRef);
                            Boolean signaturePapier = (Boolean) nodeService.getProperty(dossierRef, ParapheurModel.PROP_SIGNATURE_PAPIER);
                            if (isActeurCourant(dossierRef, username) || (isParapheurSecretaire(parapheurRef, username) && (signaturePapier))) {
                                if (isEmis(dossierRef)) {
                                    logger.warn("OnContentRead : setProperty");
                                    nodeService.setProperty(dossierRef, ParapheurModel.PROP_RECUPERABLE, Boolean.FALSE);
                                }

                                if (!nodeService.hasAspect(nodeRef, ParapheurModel.ASPECT_LU)) {
                                    logger.warn("OnContentRead : addAspect");
                                    nodeService.addAspect(nodeRef, ParapheurModel.ASPECT_LU, null);

                                    // AUDIT sur lecture du dossier
                                    if (!isTermine(dossierRef)) {
                                        List<NodeRef> lstDocs = getDocuments(dossierRef);
                                        logger.debug("Lecture sur dossier pas fini, 1er Doc=" + lstDocs.get(0).getId().hashCode() +
                                                "\n" + "                               doc lu=" + nodeRef.getId().hashCode());
                                        if (lstDocs.get(0).hashCode() == nodeRef.hashCode()) {   // La lecture du premier document est suffisante pour autoriser la signature
                                            EtapeCircuit etape = getCurrentEtapeCircuit(dossierRef);
                                            String actDmdee = etape.getActionDemandee();
                                            if (actDmdee == null) {   /// compatibilité ascendante
                                                List<EtapeCircuit> leCircuit = getCircuit(dossierRef);
                                                if (logger.isDebugEnabled()) {
                                                    logger.debug("Etape Courante, Signataire=" + etape.getSignataire() +
                                                            " , lastIndex=" + leCircuit.lastIndexOf(etape) +
                                                            " , indexCourant=" + leCircuit.indexOf(etape));
                                                }
                                                if (leCircuit.lastIndexOf(etape) == leCircuit.indexOf(etape)) {
                                                    nodeService.setProperty(dossierRef, ParapheurModel.PROP_STATUS_METIER, "Lu");
                                                    List<Object> list = new ArrayList<Object>();
                                                    list.add("Lu");
                                                    auditService.audit("ParapheurService", username + ": Dossier lu et prêt pour la signature", dossierRef, list);
                                                }
                                            } else {
                                                if (actDmdee.trim().equalsIgnoreCase("SIGNATURE")) {
                                                    nodeService.setProperty(dossierRef, ParapheurModel.PROP_STATUS_METIER, "Lu");
                                                    List<Object> list = new ArrayList<Object>();
                                                    list.add("Lu");
                                                    auditService.audit("ParapheurService", username + ": Dossier lu et prêt pour la signature", dossierRef, list);
                                                }
                                            }
                                        }
                                    } else {
                                        logger.debug("Lecture sur dossier terminé ?!?!");
                                    }
                                }
                            }
                        }
                    }
                    return null;
                }

            };

            AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() {

                public Object doWork() throws Exception {
                    // Since Alfresco 3.2, no transaction are started at this point.
                    UserTransaction tx = transactionService.getNonPropagatingUserTransaction();
                    try {
                        tx.begin();

                        NodeRef nodeRef = nodeUpdater.getNoderef();
                        try {
                            policyFilter.disableBehaviour(nodeRef, ContentModel.TYPE_CONTENT);
                            txnHelper.doInTransaction(callback, false, true);
                        } catch (InvalidNodeRefException e) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Unable to change the read indicator on missing node: " + nodeRef);
                            }
                        } catch (Exception e) {
                            logger.error("Failed to change the read indicator on node: " + nodeRef, e);
                        } finally {
                            policyFilter.enableBehaviour(ContentModel.TYPE_CONTENT);
                        }

                        tx.commit();
                    } catch (Exception e) {
                        logger.error("Error executing transaction", e);
                        try {
                            tx.rollback();
                        } catch (Exception ex) {
                            logger.error("Error during rollback", ex);
                        }
                    }

                    return null;
                }

            }, nodeUpdater.getUsername());
        }

    }

    private class UpdateIndicatorChecker implements Runnable {

        private NodeUpdater nodeUpdater;

        private UpdateIndicatorChecker(NodeUpdater nodeUpdater) {
            this.nodeUpdater = nodeUpdater;
        }

        /**
         * Increments the write count on the node
         */
        public void run() {
            RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
            RetryingTransactionCallback<Object> callback = new RetryingTransactionCallback<Object>() {

                public Object execute() throws Throwable {
                    NodeRef nodeRef = nodeUpdater.getNoderef();
                    NodeRef dossierRef = getParentDossier(nodeRef);
                    if (dossierRef != null) {
                        String username = nodeUpdater.getUsername();
                        if (isActeurCourant(dossierRef, username)) {
                            logger.warn("OnContentUpdate : addAspect");
                            nodeService.addAspect(nodeRef, ParapheurModel.ASPECT_LU, null);
                        } else {
                            logger.warn("OnContentUpdate : removeAspect");
                            if (nodeService.hasAspect(nodeRef, ParapheurModel.ASPECT_LU)) {
                                nodeService.removeAspect(nodeRef, ParapheurModel.ASPECT_LU);
                            }
                        }
                    }
                    return null;
                }

            };

            UserTransaction tx = transactionService.getNonPropagatingUserTransaction();
            try {
                tx.begin();

                NodeRef nodeRef = nodeUpdater.getNoderef();
                try {
                    policyFilter.disableBehaviour(nodeRef, ContentModel.TYPE_CONTENT);
                    txnHelper.doInTransaction(callback, false, true);

                } catch (InvalidNodeRefException e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("(updateIndicatorChecker)Unable to change the read indicator on missing node: " + nodeRef);
                    }
                } catch (Exception e) {
                    logger.error("(updateIndicatorChecker)Failed to change the read indicator on node: " + nodeRef, e);
                } finally {
                    policyFilter.enableBehaviour(ContentModel.TYPE_CONTENT);
                }

                tx.commit();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                try {
                    tx.rollback();
                } catch (Exception ex) {
                    logger.error(ex.getMessage(), ex);
                }
            }
        }

    }

    private class NodeUpdater {

        private NodeRef noderef;

        private String username;

        public NodeUpdater(NodeRef n, String u) {
            this.noderef = n;
            this.username = u;
        }

        public NodeRef getNoderef() {
            return noderef;
        }

        public String getUsername() {
            return username;
        }

    }

}
