/*
 * 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 java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
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.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor;

import javax.transaction.UserTransaction;

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.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.permissions.AccessDeniedException;
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.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.Logger;
import org.apache.log4j.Priority;
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 com.atolcd.parapheur.model.ParapheurModel;
import com.etymon.pjx.PdfFormatException;
import com.etymon.pjx.PdfInputFile;
import com.etymon.pjx.PdfManager;
import com.etymon.pjx.PdfReader;
import com.etymon.pjx.PdfWriter;
import com.etymon.pjx.util.PdfAppender;

public final class ParapheurServiceImpl implements ParapheurService, InitializingBean
{
    private static Logger logger = Logger.getLogger(ParapheurService.class);
    private static Runtime rt = Runtime.getRuntime();


    private NodeService nodeService;
    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 S2lowService s2lowService;
    private AuthorityService authorityService;
    private AuditService auditService;

    private TransactionListener transactionListener;
    private ThreadPoolExecutor threadExecuter;
    private TransactionService transactionService;
    private BehaviourFilter policyFilter;

    private Properties configuration;

    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 setSearchService(SearchService searchService)
    {
	this.searchService = searchService;
    }

    public void setDictionaryService(DictionaryService dictionaryService)
    {
	this.dictionaryService = dictionaryService;
    }

    public void setFileFolderService(FileFolderService fileFolderService)
    {
	this.fileFolderService = fileFolderService;
    }

    /**
     * @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;
    }

    /**
     * @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() throws Exception
    {
	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"));

	this.policyComponent.bindClassBehaviour(ContentServicePolicies.ON_CONTENT_UPDATE, ContentModel.TYPE_CONTENT,
		new JavaBehaviour(this, "onContentUpdate"));

	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 + "user=" + this.authenticationService.getCurrentUserName());
	queueNode(KEY_PENDING_READ_NODES, noderef);
    }

    public void onContentUpdate(NodeRef noderef, boolean newContent)
    {
	logger.debug("MAJ DE CONTENU");
	logger.debug("OnContentRead : " + noderef + "user=" + this.authenticationService.getCurrentUserName());
	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());
	    }
	    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)
	{
	    // XXX: que faire s'il y a plusieurs résultats ?
	    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)
    {
	for (NodeRef parapheur : getParapheurs())
	{
	    if (isParapheurOwner(parapheur, userName))
	    {
		// XXX: que faire s'il y a plusieurs résultats ?
		return parapheur;
	    }
	}
	return null;
    }

    /**
     * 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.isEmpty())
	{
	    if (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");

	if (getParapheurOwner(parapheur) != null && !getParapheurOwner(parapheur).equals(""))
	{
	    NodeRef proprietaireRef = personService.getPerson(getParapheurOwner(parapheur));
	    String firstName = (String) nodeService.getProperty(proprietaireRef, ContentModel.PROP_FIRSTNAME);
	    String lastName = (String) nodeService.getProperty(proprietaireRef, ContentModel.PROP_LASTNAME);
	    String fullName = firstName + (lastName != null ? (" " + lastName) : "");
	    return fullName;
	} else
	    return "Non-attribué";
    }

    /**
     * @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) throws FileExistsException
    {
	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);
	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<NodeRef> 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
	    NodeRef emetteur = getEmetteur(dossier);
	    if (emetteur.equals(circuit.get(0)))
	    {
		// 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, à 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;
	    for (NodeRef etape : circuit)
	    {
		proprietes.put(ParapheurModel.PROP_PASSE_PAR, etape);
		assocPrecedent = this.nodeService.createNode(etapePrecedenteRef,
			ParapheurModel.CHILD_ASSOC_PROCHAINE_ETAPE, ParapheurModel.ASSOC_DOSSIER_ETAPE,
			ParapheurModel.TYPE_ETAPE_CIRCUIT, proprietes);
		etapePrecedenteRef = assocPrecedent.getChildRef();
	    }
	}
    }

    /**
     * @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) throws FileExistsException
    {
	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(Priority.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(Priority.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 à archiver");
	proprietesC.put(ContentModel.PROP_TITLE, "Dossiers à archiver");
	proprietesC.put(ContentModel.PROP_DESCRIPTION, "Dossiers signés, qu'il convient désormais d'archiver.");
	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 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);

	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#isOver()
     */
    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(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(etape));
	}

	return circuit;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDiffusion(org.alfresco.service.cmr.repository.NodeRef)
     */
    @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#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 = getCurrentEtapeCircuit(dossier);
	if (etapeCourante == null)
	{
	    if (logger.isEnabledFor(Priority.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 = getCurrentEtapeCircuit(dossier);
	if (etapeCourante == null)
	{
	    if (logger.isEnabledFor(Priority.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 = 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 = 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 = 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 = 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 = 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 = 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);
	if (cert != null && cert.length > 0)
	{
	    String signature = new StringBuilder("Certificat de classe ").append(cert.length - 1).append(" émis par ")
		    .append(extractCN(cert[0].getIssuerX500Principal().getName())).append(" pour le compte de ")
		    .append(extractCN(cert[0].getSubjectX500Principal().getName())).toString();
	    this.nodeService.setProperty(etapeRef, ParapheurModel.PROP_SIGNATURE, signature);
	}
    }

    static private 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);

	// Récupération de la prochaine étape
	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.");

	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, "Le dossier n'a pas d'étape courante: id = " + dossier);

	// En cas de signature, vérification de la lecture des documents
	if (etapeSuivante == null)
	{
	    // 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
	{
	    // Durant le circuit, seul l'acteur courant peut viser
	    Assert.isTrue(isActeurCourant(dossier, username), "Vous n'êtes pas acteur courant de ce dossier!");
	}

	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("APPROVE - Propriete PASSE_PAR set");
	    nodeService.setProperty(((EtapeCircuitImpl) etapeSuivante).getNodeRef(), ParapheurModel.PROP_PASSE_PAR,
		    parapheur);

	    corbeille = getCorbeille(parapheur, ParapheurModel.NAME_A_TRAITER);

	    if (isEmis(dossier))
	    {
		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", "Visa sur dossier", dossier, list);
		else
		    auditService.audit("ParapheurService", annotation, dossier, list);
	    }
	    else
	    {
		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", "Emission du dossier", dossier, list);
		else
		    auditService.audit("ParapheurService", annotation, 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);

	}

	// On déplace le dossier vers la corbeille
	ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(dossier);
	Assert.notNull(oldParentAssoc, "Le dossier n'a pas de parent: id = " + dossier);

	logger.debug("APPROVE - Deplacement du noeud");
	try
	{
	    nodeService.moveNode(dossier, corbeille, oldParentAssoc.getTypeQName(), oldParentAssoc.getQName());
	} catch (DuplicateChildNodeNameException ex)
	{
	    if (logger.isEnabledFor(Priority.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");
	}

	// 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"
	logger.debug("APPROVE - Propriete RECUPERABLE set");
	nodeService.setProperty(dossier, ParapheurModel.PROP_RECUPERABLE, Boolean.TRUE);

	// Il s'agissait d'une étape de signature
	if (etapeSuivante == null)
	{
	    logger.debug("APPROVE - Propriete TERMINE set");
	    nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);

	    // La signature était "réelle"
	    if (signaturePapier)
	    {
		logger.debug("APPROVE - Propriete SIGNATURE set");
		nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE, "Signature papier");
	    }
	}

	// 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
		{
		    // XXX : 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);

	    // Si le dossier est à signer physiquement et que le prochain acteur est signataire, on prévient son
	    // secrétariat
	    if (signaturePapier && etapeSuivante != null)
	    {
		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());
	}

	// On retire l'aspect "lu" de tous les documents du dossier
	logger.debug("APPROVE - Suppression de l'aspect LU pour tous les documents");
	setDossierNonLu(dossier);

	logger.debug("APPROVE - Sortie de la methode");
    }

    /**
     * @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 = getCurrentEtapeCircuit(dossier);
	Assert.isTrue(etapeCourante != null && !emetteur.equals(etapeCourante.getParapheur()),
		"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);

	List<Object> list = new ArrayList<Object>(); list.add("RejetSignataire");
	nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "RejetSignataire");
	String annotation = etapeCourante.getAnnotation();
	if (annotation == null || annotation.trim().isEmpty())
	    auditService.audit("ParapheurService", "Rejet du dossier", dossier, list);
	else
	    auditService.audit("ParapheurService", annotation, 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(Priority.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");
	}
	logger.debug("REJECT - Propriete TERMINE set");
	nodeService.setProperty(dossier, ParapheurModel.PROP_TERMINE, Boolean.TRUE);

	mail("owner", "parapheur-owner-retour.ftl", dossier);
	mail("tiers", "parapheur-tiers-retour.ftl", dossier);

	// 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");
    }

    /**
     * @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());

	    List<Object> list = new ArrayList<Object>(); list.add("NonLu");
	    nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
	    auditService.audit("ParapheurService", "Reprise du dossier", dossier, list);

	} catch (DuplicateChildNodeNameException ex)
	{
	    if (logger.isEnabledFor(Priority.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");
	}
	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 - 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, 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);

	// 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());

	    List<Object> list = new ArrayList<Object>(); list.add("NonLu");
	    nodeService.setProperty(dossier, ParapheurModel.PROP_STATUS_METIER, "NonLu");
	    auditService.audit("ParapheurService", "Recuperation du dossier", dossier, list);

	} catch (DuplicateChildNodeNameException ex)
	{
	    if (logger.isEnabledFor(Priority.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");
	}

	// 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("document", 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(Priority.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(Priority.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);

	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(Priority.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(Priority.WARN))
		    logger.warn(nte);
		throw new RuntimeException("Aucune conversion trouvée, veuillez contacter votre administrateur.");
	    } catch (Exception e)
	    {
		if (logger.isEnabledFor(Priority.WARN))
		    logger.warn(e);
		throw new RuntimeException("Erreur lors de la conversion, veuillez contacter votre administrateur.");
	    }
	} // Fin conversion des documents

	if (!tmpFiles.isEmpty())
	{
	    File archive = TempFileProvider.createTempFile("tmpconvfinal", ".pdf");
	    // BIDOUILLAGE STV
	    List<PdfManager> m = new ArrayList<PdfManager>();
	    try
	    {
		for (File tmp : tmpFiles)
		{
		    try
		    {
			m.add(new PdfManager(new PdfReader(new PdfInputFile(tmp))));
		    } catch (PdfFormatException pfe)
		    {
			logger.error(tmp.getAbsolutePath() + ": " + pfe.getMessage());
			throw new RuntimeException(
				"Erreur dans la creation de l'archive, veuillez contacter votre administrateur.", pfe);
		    } catch (IOException ioe)
		    {
			throw new ContentIOException("Erreur lors de l'assemblage des documents dans l'archive", ioe);
		    }
		}
		PdfWriter w = new PdfWriter(archive);
		PdfAppender a = new PdfAppender(m, w);
		a.append();
		w.close();
	    } catch (PdfFormatException e)
	    {
		// e.printStackTrace();
		throw new RuntimeException(
			"Erreur dans la creation du PDF d'archive, veuillez contacter votre administrateur.", e);
	    } catch (IOException e1)
	    {
		// e1.printStackTrace();
		throw new ContentIOException("Erreur lors de l'assemblage des documents dans l'archive.", e1);
	    }
	    // FIN BIDOUILLAGE STV

	    // BIDOUILLAGE STV: ce code original (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 !");
	
	// FIXME : ça, il va falloir que ça évolue!
	Assert.isTrue(currentUser.equals(getParapheurOwner(getEmetteur(dossier))), "Le dossier ne peut être archivé que par son émetteur.");

	// 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(Priority.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();
		    ackDate = rootElement.attributeValue("DateReception");
		} catch (DocumentException e)
		{
		    if (logger.isEnabledFor(Priority.WARN))
			logger.warn("Erreur sur récup Date MIAT dossier ref = " + dossier, e);
		}

	    } else
	    {
		if (logger.isEnabledFor(Priority.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, ContentModel.TYPE_CONTENT);
		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");
		    this.nodeService.setProperty(archiveRef, ParapheurModel.PROP_ORIGINAL_NAME, this.nodeService
			    .getProperty(docRef, ContentModel.PROP_NAME));

		    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);

		    logger.debug("ARCHIVER - Sauvegarde de la signature");
		    sigWriter = this.contentService.getWriter(archiveRef, ParapheurModel.PROP_SIG, true);
		    if (nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_SIGNATURE)!=null &&
			    nodeService.getProperty(dossier, ParapheurModel.PROP_TYPE_SIGNATURE).toString().equalsIgnoreCase("XADES"))
			sigWriter.setMimetype(MimetypeMap.MIMETYPE_XML);
		    else
			sigWriter.setMimetype("application/pkcs7-signature");
		    sigWriter.setEncoding("UTF-8");
		    sigWriter.putContent(new ByteArrayInputStream(signature));

		    // 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);
		     */
		}

		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");
		    /*
		     * for (NodeRef tmpRef : archiveRefs) permissionService.setInheritParentPermissions(tmpRef,false);
		     */
		    permissionService.setInheritParentPermissions(archiveRef, false);

		    // Si le dossier est confidentiel, seul les membres du circuit et de la liste ont accès
		    if (Boolean.TRUE.equals(confidentiel))
		    {
			// 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 " + authority);
				/*
				 * for (NodeRef tmpRef : archiveRefs)
				 * permissionService.setPermission(tmpRef,authority,"Read",true);
				 */
				permissionService.setPermission(archiveRef, authority, "Read", true);
			    }
			}
			for (NodeRef parapheur : getDiffusion(dossier))
			{
			    authority = getParapheurOwner(parapheur);
			    if (authority != null && !authority.equals(""))
			    {
				logger.debug("ARCHIVER - Permission READ attribuee a " + authority);
				/*
				 * for (NodeRef tmpRef : archiveRefs)
				 * permissionService.setPermission(tmpRef,authority,"Read",true);
				 */
				permissionService.setPermission(archiveRef, authority, "Read", true);
			    }
			}
		    }
		    // Cas par défaut : le dossier est visible par les membres des groupes auxquels appartient son
		    // émetteur
		    else
		    {
			String emetteur = getParapheurOwner(getEmetteur(dossier));
			/*
			 * for (NodeRef tmpRef : archiveRefs)
			 * permissionService.setPermission(tmpRef,emetteur,"Read",true);
			 */
			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);
				/*
				 * for (NodeRef tmpRef : archiveRefs)
				 * permissionService.setPermission(tmpRef,groupe,"Read",true);
				 */
				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);
		    } else
		    {
			logger.warn("ARCHIVER - XML retour MIAT Absent !");
		    }

		    // logger.debug("ARCHIVER - Renseignement URL archivage dans S2LOW _/!\\_DESACTIVE_/!\\_");
		    logger.debug("ARCHIVER - Renseignement URL archivage dans S2LOW ");
		    urlArchive = s2lowService.setS2lowActesArchiveURL(archiveRef);
		}
		
		// 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_XPATH_SIGNATURE)!=null)
			pptes.put(ParapheurModel.PROP_XPATH_SIGNATURE, this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE));
		}
		List<Object> list = new ArrayList<Object>(); list.add("Archive");
		if (urlArchive!=null) list.add(urlArchive);
		auditService.audit("ParapheurService", "Archivage du dossier", dossier, list);

		// On supprime le dossier archivé
		logger.debug("ARCHIVER - Suppression du dossier");
		nodeService.deleteNode(dossier);
	    } catch (Exception e)
	    {
		throw new RuntimeException(e);
	    }
	}
	return urlArchive;
    }

    /**
     * @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);

	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(Priority.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");
	}
    }

    /**
     * @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
	{
	    // 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();
		    // 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)))
		    {
			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);
		    }
		}

		circuitsRep = results.get(0);
		// On alimente la liste avec les circuits publiques
		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#loadSavedWorkflow(org.alfresco.service.cmr.repository.NodeRef)
     */
    @SuppressWarnings("unchecked")
    public SavedWorkflow loadSavedWorkflow(NodeRef workflow)
    {
	Assert.isTrue(nodeService.exists(workflow),
		"Le Node Ref passé en paramètre ne correspond pas à un noeud existant");

	SavedWorkflow res = new SavedWorkflowImpl();
	ContentReader contentreader = contentService.getReader(workflow, ContentModel.PROP_CONTENT);
	SAXReader saxreader = new SAXReader();

	try
	{
	    Document document = saxreader.read(new StringReader(contentreader.getContentString()));
	    Element rootElement = document.getRootElement();
	    Element validation = rootElement.element("validation");
	    if (validation != null)
	    {
		for (Iterator i = validation.elementIterator("etape"); i.hasNext();)
		{
		    Element etape = (Element) i.next();
		    ((SavedWorkflowImpl) res).addToCircuit(new NodeRef(etape.getText()));
		}
	    }

	    Element notification = rootElement.element("notification");
	    if (notification != null)
	    {
		for (Iterator i = notification.elementIterator("etape"); i.hasNext();)
		{
		    Element etape = (Element) i.next();
		    ((SavedWorkflowImpl) res).addToDiffusion(new NodeRef(etape.getText()));
		}
	    }
	} catch (DocumentException e)
	{

	}

	return res;
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#saveWorkflow(java.lang.String, java.util.List, java.util.Set)
     */
    public NodeRef saveWorkflow(String name, List<NodeRef> circuit, Set<NodeRef> diffusion, boolean publicWorkflow)
    {
	Assert.hasText(name, "Le circuit doit être nommé");

	NodeRef res = null;

	// On prépare le dossier de sauvegarde
	NodeRef circuitsRef = null;
	String xpath = this.configuration.getProperty("spaces.company_home.childname") + "/"
		+ this.configuration.getProperty("spaces.dictionary.childname") + "/"
		+ this.configuration.getProperty("spaces.savedworkflows.childname");

	List<NodeRef> results = null;
	try
	{
	    results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration
		    .getProperty("spaces.store"))), xpath, null, namespaceService, false);
	} catch (AccessDeniedException err)
	{
	    // ignore and return null
	    return null;
	}

	if (results != null && results.size() == 1)
	{

	    if (publicWorkflow)
	    {
		circuitsRef = results.get(0);
	    } else
	    {
		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#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);
	}
    }

    /**
     * @see com.atolcd.parapheur.repo.ParapheurService#getDelegation(org.alfresco.service.cmr.repository.NodeRef)
     */
    public NodeRef getDelegation(NodeRef parapheur)
    {
	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 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 = 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(Priority.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");
	}

	// 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#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 signatureString = Base64.encodeBytes(signedBytes, Base64.DONT_BREAK_LINES);
	this.nodeService.setProperty(dossier, ParapheurModel.PROP_SIGNATURE_ELECTRONIQUE, 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();
	    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, "UTF-8");
		String strXml = new String(bytesToSign, docEncoding);
		bytesToSign = null;  // Liberez la memoire!!
		rt.gc();
		logger.debug("Signature XAdES détachée:\n"+ strSig);
		PES_Signe = xades.injectXadesSigIntoXml(strXml, strSig,
			(String)this.nodeService.getProperty(dossier, ParapheurModel.PROP_XPATH_SIGNATURE),
			docEncoding);
		// logger.debug("MON PES Signé:\n" + PES_Signe);
		strXml = null;   // Liberez la memoire!!
		rt.gc();
	    } catch (UnsupportedEncodingException e)
	    {
		e.printStackTrace();
	    }    	    	    
	    if (PES_Signe!=null && !PES_Signe.trim().isEmpty())
	    {  // MaJ du Document PES avec la signature injectée
		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);
	    }
	}
    }

    /**
     * @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#getXadesSignatureProperties
     */
    public Properties getXadesSignatureProperties(NodeRef dossier)
    {
	Properties props;
	if (this.nodeService.getProperty(dossier, ParapheurModel.PROP_TDT_NOM).toString().equals("S²LOW"))
	{
	    props = s2lowService.getXadesSignatureProperties();
	    logger.debug("getXadesSignatureProperties"+ props.toString());
	    return props;
	}
	else
	{
	    logger.warn("Le dossier n'a pas la propriété TDT_NOM=S²LOW.");
	    return null;
	}
    }
    
    /**
     * @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);
	  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
	  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.debug("getSavedXMLTypes: Access denied Exception");
	    return null;
	}
    }

    /**
     * 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\"";
	ResultSet results = null; // List<NodeRef> results = null;
	try
	{
	    // results = searchService.selectNodes(nodeService.getRootNode(new StoreRef(this.configuration.getProperty("spaces.store"))), xpath, null, namespaceService, false);
	    results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
	    if (results.length() == 1)
	    { // found what we were looking for
		logger.debug("getSavedXMLSousTypes: Found 1 node");
		xmlSousTypesNoderef = results.getNodeRef(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: (lucene=" + queryStr + "), Found " + results.length()+ " node(s).");
		return null;
	    }
	} catch (AccessDeniedException err)
	{
	    // ignore and return null
	    logger.debug("getSavedXMLSousTypes: Access denied Exception");
	    return null;
	}
    }

    @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\"";
	ResultSet results = null;
	//
	try
	{
	    results = searchService.query(spacesStoreRef, SearchService.LANGUAGE_LUCENE, queryStr);
	    if (results.length() == 1)
	    { // found what we were looking for
		logger.debug("getCircuitRef: Found 1 xmlSousTypeNoderef node");
		xmlSousTypeNoderef = results.getNodeRef(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: (lucene=" + queryStr + "), Found " + results.length()
			+ " 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()));
		    }
		}
	    }
	} catch (DocumentException e)
	{
	    logger.debug("getTypeMetierProperties: Erreur sur parsing fichier XML");
	    e.printStackTrace();
	    return null;
	}
	return props;
    }

    public NodeRef findUserByEmail(String from) throws NoSuchPersonException
    {
        StoreRef containerStore = personService.getPeopleContainer().getStoreRef();

	ResultSet results = null;
	String queryStr = "TYPE:\""+ContentModel.TYPE_PERSON + "\" AND " +
                "@cm\\:email:\"" + from + "\"";
        System.out.println(queryStr);

	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 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/*\" +@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)
    {
	if (rechercheDossier(nomDossier) != null)
	    return true;
	else
	    return false;
    }
    
    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 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 EtapeCircuitImpl getCurrentEtapeCircuit(NodeRef dossier)
    {
	if (isTermine(dossier))
	    return null;

	List<EtapeCircuit> circuit = getCircuit(dossier);
	if (circuit == 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(Priority.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;
    }

    private class EtapeCircuitImpl implements EtapeCircuit
    {
	private NodeRef nodeRef;

	private NodeRef parapheur;
	private boolean approved;
	private String annotation;
	private String annotationPrivee;
	private String signataire;
	private Date dateValidation;
	private String signature;
	//private String notificationsExternes;

	public EtapeCircuitImpl(NodeRef nodeRef)
	{
	    this.nodeRef = nodeRef;

	    this.parapheur = (NodeRef) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_PASSE_PAR);
	    this.approved = (Boolean) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_EFFECTUEE);
	    this.annotation = (String) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_ANNOTATION);
	    this.annotationPrivee = (String) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_ANNOTATION_PRIVEE);
	    this.signataire = (String) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_SIGNATAIRE);
	    this.dateValidation = (Date) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_DATE_VALIDATION);
	    this.signature = (String) nodeService.getProperty(this.nodeRef, ParapheurModel.PROP_SIGNATURE);
	}

	public NodeRef getNodeRef()
	{
	    return nodeRef;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getParapheur()
	 */
	public NodeRef getParapheur()
	{
	    return this.parapheur;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#isApproved()
	 */
	public boolean isApproved()
	{
	    return this.approved;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getAnnotation()
	 */
	public String getAnnotation()
	{
	    return this.annotation;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getAnnotationPrivee()
	 */
	public String getAnnotationPrivee()
	{
	    return this.annotationPrivee;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getSignataire()
	 */
	public String getSignataire()
	{
	    return this.signataire;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getDateValidation()
	 */
	public Date getDateValidation()
	{
	    return this.dateValidation;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getSignature()
	 */
	public String getSignature()
	{
	    return signature;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getListeDiffusion()
	 */
	@Override
	public List<NodeRef> getListeDiffusion()
	{
	    // TODO Auto-generated method stub
	    return null;
	}

	/**
	 * @see com.atolcd.parapheur.repo.EtapeCircuit#getNotificationsExternes()
	 */
	@Override
	public String getNotificationsExternes()
	{
	    // TODO Auto-generated method stub
	    return null;
	}
    }

    private class SavedWorkflowImpl implements SavedWorkflow
    {

	private List<NodeRef> circuit;
	private Set<NodeRef> diffusion;

	SavedWorkflowImpl()
	{
	    this.circuit = new ArrayList<NodeRef>();
	    this.diffusion = new HashSet<NodeRef>();
	}

	/**
	 * @see com.atolcd.parapheur.repo.SavedWorkflow#getCircuit()
	 */
	public List<NodeRef> getCircuit()
	{
	    return this.circuit;
	}

	/**
	 * @see com.atolcd.parapheur.repo.SavedWorkflow#getDiffusion()
	 */
	public Set<NodeRef> getDiffusion()
	{
	    return this.diffusion;
	}

	public void addToCircuit(NodeRef noderef)
	{
	    this.circuit.add(noderef);
	}

	public void addToDiffusion(NodeRef noderef)
	{
	    this.diffusion.add(noderef);
	}

    }

    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()
	{
	    RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
	    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);

				    // ajout STV : audit de lecture
				    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);
					    logger.debug("Etape Courante, Signataire=" + etape.getSignataire()+ 
							" , lastIndex="+getCircuit(dossierRef).lastIndexOf(etape) +
							" , indexCourant=" + getCircuit(dossierRef).indexOf(etape));
					    if (getCircuit(dossierRef).lastIndexOf(etape) == getCircuit(dossierRef).indexOf(etape))
					    {
						List<Object> list = new ArrayList<Object>(); list.add("Lu");
						nodeService.setProperty(dossierRef, ParapheurModel.PROP_STATUS_METIER, "Lu");
						auditService.audit("ParapheurService", username+": Dossier lu et prêt pour la signature", dossierRef, list);
					    }
					}
				    }
				    else
				    {
					logger.debug("Lecture sur dossier terminé ?!?!");
				    }
				}
			    }
			}
		    }
		    return null;
		}
	    };

	    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 (Throwable e)
	    {
		if (logger.isDebugEnabled())
		{
		    logger.debug(e);
		}
		e.printStackTrace();
		logger.error("Failed to change the read indicator on node: " + nodeRef);
	    } finally
	    {
		policyFilter.enableBehaviour(ContentModel.TYPE_CONTENT);
	    }
	}
    }

    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;
		}
	    };

	    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 (Throwable e)
	    {
		if (logger.isDebugEnabled())
		{
		    logger.debug(e);
		}
		e.printStackTrace();
		logger.error("(updateIndicatorChecker)Failed to change the read indicator on node: " + nodeRef);
	    } finally
	    {
		policyFilter.enableBehaviour(ContentModel.TYPE_CONTENT);
	    }
	}
    }

    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;
	}
    }

}
