org.silverpeas.components.kmelia.service.DefaultKmeliaService.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.components.kmelia.service.DefaultKmeliaService.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Affero General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of the GPL, you may
 * redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS")
 * applications as described in Silverpeas's FLOSS exception. You should have received a copy of the
 * text describing the FLOSS exception, and it is also available here:
 * "http://www.silverpeas.org/docs/core/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 */
package org.silverpeas.components.kmelia.service;

import org.apache.commons.io.FilenameUtils;
import org.silverpeas.components.kmelia.InstanceParameters;
import org.silverpeas.components.kmelia.KmeliaAuthorization;
import org.silverpeas.components.kmelia.KmeliaContentManager;
import org.silverpeas.components.kmelia.KmeliaCopyDetail;
import org.silverpeas.components.kmelia.KmeliaPasteDetail;
import org.silverpeas.components.kmelia.KmeliaPublicationHelper;
import org.silverpeas.components.kmelia.PublicationImport;
import org.silverpeas.components.kmelia.model.KmaxRuntimeException;
import org.silverpeas.components.kmelia.model.KmeliaPublication;
import org.silverpeas.components.kmelia.model.KmeliaRuntimeException;
import org.silverpeas.components.kmelia.model.TopicComparator;
import org.silverpeas.components.kmelia.model.TopicDetail;
import org.silverpeas.components.kmelia.notification.*;
import org.silverpeas.core.ActionType;
import org.silverpeas.core.ResourceReference;
import org.silverpeas.core.admin.ObjectType;
import org.silverpeas.core.admin.PaginationPage;
import org.silverpeas.core.admin.service.AdminController;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.admin.user.model.ProfileInst;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.admin.user.model.User;
import org.silverpeas.core.comment.service.CommentService;
import org.silverpeas.core.contribution.attachment.AttachmentException;
import org.silverpeas.core.contribution.attachment.AttachmentServiceProvider;
import org.silverpeas.core.contribution.attachment.model.DocumentType;
import org.silverpeas.core.contribution.attachment.model.HistorisedDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleAttachment;
import org.silverpeas.core.contribution.attachment.model.SimpleDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocumentPK;
import org.silverpeas.core.contribution.attachment.notification.AttachmentRef;
import org.silverpeas.core.contribution.content.form.FormException;
import org.silverpeas.core.contribution.content.form.RecordSet;
import org.silverpeas.core.contribution.content.form.RecordTemplate;
import org.silverpeas.core.contribution.content.form.XMLField;
import org.silverpeas.core.contribution.content.form.record.GenericRecordSet;
import org.silverpeas.core.contribution.content.wysiwyg.WysiwygException;
import org.silverpeas.core.contribution.content.wysiwyg.service.WysiwygController;
import org.silverpeas.core.contribution.publication.model.Alias;
import org.silverpeas.core.contribution.publication.model.CompletePublication;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.publication.model.PublicationLink;
import org.silverpeas.core.contribution.publication.model.PublicationPK;
import org.silverpeas.core.contribution.publication.model.ValidationStep;
import org.silverpeas.core.contribution.publication.service.PublicationService;
import org.silverpeas.core.contribution.template.form.dao.ModelDAO;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateException;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.datereminder.persistence.service.PersistentDateReminderService;
import org.silverpeas.core.i18n.I18NHelper;
import org.silverpeas.core.index.indexing.model.IndexManager;
import org.silverpeas.core.io.media.image.thumbnail.ThumbnailException;
import org.silverpeas.core.io.media.image.thumbnail.control.ThumbnailController;
import org.silverpeas.core.io.media.image.thumbnail.model.ThumbnailDetail;
import org.silverpeas.core.io.media.image.thumbnail.service.ThumbnailServiceProvider;
import org.silverpeas.core.node.coordinates.model.Coordinate;
import org.silverpeas.core.node.coordinates.model.CoordinatePK;
import org.silverpeas.core.node.coordinates.model.CoordinatePoint;
import org.silverpeas.core.node.coordinates.service.CoordinatesService;
import org.silverpeas.core.node.model.NodeDetail;
import org.silverpeas.core.node.model.NodePK;
import org.silverpeas.core.node.service.NodeService;
import org.silverpeas.core.notification.user.UserNotification;
import org.silverpeas.core.notification.user.builder.helper.UserNotificationHelper;
import org.silverpeas.core.notification.user.client.constant.NotifAction;
import org.silverpeas.core.pdc.pdc.model.ClassifyPosition;
import org.silverpeas.core.pdc.pdc.model.PdcClassification;
import org.silverpeas.core.pdc.pdc.model.PdcException;
import org.silverpeas.core.pdc.pdc.service.PdcClassificationService;
import org.silverpeas.core.pdc.pdc.service.PdcManager;
import org.silverpeas.core.pdc.subscription.service.PdcSubscriptionManager;
import org.silverpeas.core.persistence.jdbc.DBUtil;
import org.silverpeas.core.personalorganizer.model.Attendee;
import org.silverpeas.core.personalorganizer.model.TodoDetail;
import org.silverpeas.core.personalorganizer.service.SilverpeasCalendar;
import org.silverpeas.core.process.annotation.SimulationActionProcess;
import org.silverpeas.core.silverstatistics.access.model.HistoryObjectDetail;
import org.silverpeas.core.silverstatistics.access.service.StatisticService;
import org.silverpeas.core.subscription.Subscription;
import org.silverpeas.core.subscription.SubscriptionResource;
import org.silverpeas.core.subscription.SubscriptionService;
import org.silverpeas.core.subscription.SubscriptionServiceProvider;
import org.silverpeas.core.subscription.service.NodeSubscription;
import org.silverpeas.core.subscription.service.NodeSubscriptionResource;
import org.silverpeas.core.subscription.service.UserSubscriptionSubscriber;
import org.silverpeas.core.util.*;
import org.silverpeas.core.util.annotation.Action;
import org.silverpeas.core.util.annotation.SourcePK;
import org.silverpeas.core.util.annotation.TargetPK;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.silverpeas.core.util.file.FileUtil;
import org.silverpeas.core.util.logging.SilverLogger;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.util.*;

import static org.silverpeas.components.kmelia.service.KmeliaOperationContext.OperationType.*;
import static org.silverpeas.components.kmelia.service.KmeliaServiceContext.*;
import static org.silverpeas.core.admin.service.OrganizationControllerProvider.getOrganisationController;
import static org.silverpeas.core.contribution.attachment.AttachmentService.VERSION_MODE;
import static org.silverpeas.core.util.StringUtil.*;

/**
 * This is the KMelia Service controller of the MVC. It controls all the activities that happen in a
 * client session. It also provides mechanisms to access other services.
 * Service which manage kmelia and kmax application.
 * @author Nicolas Eysseric
 */
@Singleton
@Transactional(Transactional.TxType.SUPPORTS)
public class DefaultKmeliaService implements KmeliaService {

    private static final String MESSAGES_PATH = "org.silverpeas.kmelia.multilang.kmeliaBundle";
    private static final String SETTINGS_PATH = "org.silverpeas.kmelia.settings.kmeliaSettings";
    private static final SettingBundle settings = ResourceLocator.getSettingBundle(SETTINGS_PATH);
    private static final String UNKNOWN = "unknown";
    private static final String PUBLICATION = "Publication";
    private static final String USELESS = "useless";
    private static final String NODE_PREFIX = "Node_";
    private static final String ADMIN_ROLE = "admin";
    @Inject
    private NodeService nodeService;
    @Inject
    private PublicationService publicationService;
    @Inject
    private StatisticService statisticService;
    @Inject
    private PdcManager pdcManager;
    @Inject
    private CoordinatesService coordinatesService;
    @Inject
    private CommentService commentService;
    @Inject
    private AdminController adminController;
    @Inject
    private SilverpeasCalendar calendar;
    @Inject
    private PdcClassificationService pdcClassificationService;
    @Inject
    private PdcSubscriptionManager pdcSubscriptionManager;
    @Inject
    private PersistentDateReminderService dateReminderService;
    @Inject
    private KmeliaContentManager kmeliaContentManager;

    private int getNbPublicationsOnRoot(String componentId) {
        String parameterValue = getOrganisationController().getComponentParameterValue(componentId,
                "nbPubliOnRoot");
        if (isDefined(parameterValue)) {
            return Integer.parseInt(parameterValue);
        } else {
            if (KmeliaHelper.isToolbox(componentId)) {

                return 0;
            }
            // lecture du properties
            SettingBundle theSettings = getComponentSettings();
            return theSettings.getInteger("HomeNbPublications");
        }
    }

    private boolean isDraftModeUsed(String componentId) {
        return "yes".equals(getOrganisationController().getComponentParameterValue(componentId, "draft"));
    }

    private SubscriptionService getSubscribeService() {
        return SubscriptionServiceProvider.getSubscribeService();
    }

    /**
     * Return a the detail of a topic
     * @param pk the id of the topic
     * @param userId
     * @param isTreeStructureUsed
     * @param userProfile
     * @param isRightsOnTopicsUsed
     * @return
     */
    @Override
    public TopicDetail goTo(NodePK pk, String userId, boolean isTreeStructureUsed, String userProfile,
            boolean isRightsOnTopicsUsed) {
        Collection<NodeDetail> newPath = new ArrayList<>();
        NodeDetail nodeDetail = null;

        // get the basic information (Header) of this topic
        try {
            nodeDetail = nodeService.getDetail(pk);
            if (isRightsOnTopicsUsed) {
                OrganizationController orga = getOrganisationController();
                if (nodeDetail.haveRights() && !orga.isObjectAvailable(nodeDetail.getRightsDependsOn(),
                        ObjectType.NODE, pk.getInstanceId(), userId)) {
                    nodeDetail.setUserRole("noRights");
                }
                List<NodeDetail> availableChildren = getAllowedSubfolders(nodeDetail, userId);
                nodeDetail.setChildrenDetails(availableChildren);
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        // get publications
        List<KmeliaPublication> pubDetails = getPublicationsOfFolder(pk, userProfile, userId, isTreeStructureUsed,
                isRightsOnTopicsUsed);

        // get the path to this topic
        if (pk.isRoot()) {
            newPath.add(nodeDetail);
        } else {
            newPath = getPathFromAToZ(nodeDetail);
        }

        // set the currentTopic and return it
        return new TopicDetail(newPath, nodeDetail, pubDetails);
    }

    @Override
    public List<KmeliaPublication> getPublicationsOfFolder(NodePK pk, String userProfile, String userId,
            boolean isTreeStructureUsed, boolean isRightsOnTopicsUsed) {
        Collection<PublicationDetail> pubDetails = null;

        // get the publications associated to this topic
        if (pk.isTrash()) {
            // Topic = Basket
            pubDetails = getPublicationsInBasket(pk, userProfile, userId);
        } else if (pk.isRoot()) {
            try {
                int nbPublisOnRoot = getNbPublicationsOnRoot(pk.getInstanceId());
                if (nbPublisOnRoot == 0 || !isTreeStructureUsed || KmeliaHelper.isToolbox(pk.getInstanceId())) {
                    pubDetails = publicationService.getDetailsByFatherPK(pk, "P.pubUpdateDate desc", false);
                } else {
                    return getLatestPublications(pk.getInstanceId(), nbPublisOnRoot, isRightsOnTopicsUsed, userId);
                }
            } catch (Exception e) {
                throw new KmeliaRuntimeException(e);
            }
        } else {
            try {
                // get the publication details linked to this topic
                pubDetails = publicationService.getDetailsByFatherPK(pk, "P.pubUpdateDate DESC, P.pubId DESC",
                        false);
            } catch (Exception e) {
                throw new KmeliaRuntimeException(e);
            }
        }
        return pubDetails2userPubs(pubDetails);
    }

    @Override
    public List<KmeliaPublication> getLatestPublications(String instanceId, int nbPublisOnRoot,
            boolean isRightsOnTopicsUsed, String userId) {
        PublicationPK pubPK = new PublicationPK(UNKNOWN, instanceId);
        Collection<PublicationDetail> pubDetails = publicationService
                .getDetailsByBeginDateDescAndStatusAndNotLinkedToFatherId(pubPK, PublicationDetail.VALID_STATUS,
                        nbPublisOnRoot, NodePK.BIN_NODE_ID);
        if (isRightsOnTopicsUsed) {// The list of publications must be filtered
            List<PublicationDetail> filteredList = new ArrayList<>();
            KmeliaAuthorization security = new KmeliaAuthorization();
            for (PublicationDetail pubDetail : pubDetails) {
                if (security.isObjectAvailable(instanceId, userId, pubDetail.getPK().getId(), PUBLICATION)) {
                    filteredList.add(pubDetail);
                }
            }
            pubDetails.clear();
            pubDetails.addAll(filteredList);
        }
        return pubDetails2userPubs(pubDetails);
    }

    @Override
    public List<NodeDetail> getAllowedSubfolders(NodeDetail folder, String userId) {
        NodePK pk = folder.getNodePK();
        List<NodeDetail> children = (List<NodeDetail>) folder.getChildrenDetails();
        List<NodeDetail> availableChildren = new ArrayList<>();
        for (NodeDetail child : children) {
            NodePK childId = child.getNodePK();
            if (childId.isTrash() || childId.isUnclassed() || !child.haveRights()) {
                availableChildren.add(child);
            } else {
                addAccordingToRights(userId, pk, availableChildren, child);
            }
        }
        return availableChildren;
    }

    private void addAccordingToRights(final String userId, final NodePK pk,
            final List<NodeDetail> availableChildren, final NodeDetail child) {
        int rightsDependsOn = child.getRightsDependsOn();
        boolean nodeAvailable = getOrganisationController().isObjectAvailable(rightsDependsOn, ObjectType.NODE,
                pk.getInstanceId(), userId);
        if (nodeAvailable) {
            availableChildren.add(child);
        } else { // check if at least one descendant is available
            Iterator<NodeDetail> descendants = nodeService.getDescendantDetails(child).iterator();
            addDescendantIfAvailable(userId, pk, availableChildren, child, rightsDependsOn, descendants);
        }
    }

    private void addDescendantIfAvailable(final String userId, final NodePK pk,
            final List<NodeDetail> availableChildren, final NodeDetail child, final int rightsDependsOn,
            final Iterator<NodeDetail> descendants) {
        boolean childAllowed = false;
        while (!childAllowed && descendants.hasNext()) {
            NodeDetail descendant = descendants.next();
            if (descendant.getRightsDependsOn() != rightsDependsOn && getOrganisationController().isObjectAvailable(
                    descendant.getRightsDependsOn(), ObjectType.NODE, pk.getInstanceId(), userId)) {
                // different rights of father check if it is available
                childAllowed = true;
                if (!availableChildren.contains(child)) {
                    availableChildren.add(child);
                }
            }
        }
    }

    private Collection<NodeDetail> getPathFromAToZ(NodeDetail nd) {
        Collection<NodeDetail> newPath = new ArrayList<>();
        try {
            List<NodeDetail> pathInReverse = nodeService.getPath(nd.getNodePK());
            // reverse the path from root to leaf
            for (int i = pathInReverse.size() - 1; i >= 0; i--) {
                newPath.add(pathInReverse.get(i));
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return newPath;
    }

    /**
     * Add a subtopic to a topic - If a subtopic of same name already exists a NodePK with id=-1 is
     * returned else the new topic NodePK
     * @param fatherPK the topic Id of the future father
     * @param subTopic the NodeDetail of the new sub topic
     * @return If a subtopic of same name already exists a NodePK with id=-1 is returned else the new
     * topic NodePK
     * @see NodeDetail
     * @see NodePK
     */
    @Override
    public NodePK addToTopic(NodePK fatherPK, NodeDetail subTopic) {
        NodePK theNodePK;
        try {
            NodeDetail fatherDetail = nodeService.getHeader(fatherPK);
            theNodePK = nodeService.createNode(subTopic, fatherDetail);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return theNodePK;
    }

    /**
     * Add a subtopic to currentTopic and alert users - If a subtopic of same name already exists a
     * NodePK with id=-1 is returned else the new topic NodePK
     * @param fatherPK
     * @param subTopic the NodeDetail of the new sub topic
     * @param alertType Alert all users, only publishers or nobody of the topic creation alertType =
     * "All"|"Publisher"|"None"
     * @return If a subtopic of same name already exists a NodePK with id=-1 is returned else the new
     * topic NodePK
     */
    @Override
    public NodePK addSubTopic(NodePK fatherPK, NodeDetail subTopic, String alertType) {
        // Construction de la date de cration (date courante)
        String creationDate = DateUtil.today2SQLDate();
        subTopic.setCreationDate(creationDate);
        // Web visibility parameter. The topic is by default invisible.
        subTopic.setStatus("Invisible");
        // add new topic to current topic
        NodePK pk = addToTopic(fatherPK, subTopic);
        // Creation alert
        if (!"-1".equals(pk.getId())) {
            subTopic.setNodePK(pk);
            subTopic.setFatherPK(fatherPK);
            topicCreationAlert(subTopic, NotifAction.CREATE, alertType);
        }
        return pk;
    }

    /**
     * Alert all users, only publishers or nobody of the topic creation or update
     * @param alertType alertType = "All"|"Publisher"|"None"
     * @see NodePK
     * @since 1.0
     */
    private void topicCreationAlert(final NodeDetail node, NotifAction action, final String alertType) {
        UserNotificationHelper.buildAndSend(new KmeliaTopicUserNotification(node, action, alertType));
    }

    /**
     * Update a subtopic to currentTopic and alert users - If a subtopic of same name already exists
     * a
     * NodePK with id=-1 is returned else the new topic NodePK
     * @param topic the NodeDetail of the updated sub topic
     * @param alertType Alert all users, only publishers or nobody of the topic creation alertType =
     * "All"|"Publisher"|"None"
     * @return If a subtopic of same name already exists a NodePK with id=-1 is returned else the new
     * topic NodePK
     * @see NodeDetail
     * @see NodePK
     * @since 1.0
     */
    @Override
    public NodePK updateTopic(NodeDetail topic, String alertType) {
        // Order of the node must be unchanged
        NodeDetail oldNode = nodeService.getHeader(topic.getNodePK());
        int order = oldNode.getOrder();
        topic.setOrder(order);
        nodeService.setDetail(topic);

        // manage operations relative to folder rights
        if (isRightsOnTopicsEnabled(topic.getNodePK().getInstanceId())) {
            updateNode(topic, oldNode);
        }

        // Update Alert
        topic.setFatherPK(oldNode.getFatherPK());
        topicCreationAlert(topic, NotifAction.UPDATE, alertType);
        return topic.getNodePK();
    }

    private void updateNode(final NodeDetail newNode, final NodeDetail oldNode) {
        if (oldNode.getRightsDependsOn() != newNode.getRightsDependsOn()) {
            // rights dependency have changed
            if (!newNode.haveRights()) {

                NodeDetail father = nodeService.getHeader(oldNode.getFatherPK());
                newNode.setRightsDependsOn(father.getRightsDependsOn());

                // Topic profiles must be removed
                List<ProfileInst> profiles = adminController.getProfilesByObject(newNode.getNodePK().getId(),
                        ObjectType.NODE.getCode(), newNode.getNodePK().getInstanceId());
                deleteProfiles(profiles);
            } else {
                newNode.setRightsDependsOnMe();
            }
            nodeService.updateRightsDependency(newNode);
        }
    }

    private void deleteProfiles(final List<ProfileInst> profiles) {
        for (ProfileInst profile : profiles) {
            if (profile != null) {
                adminController.deleteProfileInst(profile.getId());
            }
        }
    }

    @Override
    public NodeDetail getSubTopicDetail(NodePK pk) {
        NodeDetail subTopic;
        // get the basic information (Header) of this topic
        try {
            subTopic = nodeService.getDetail(pk);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return subTopic;
    }

    /**
     * Delete a topic and all descendants. Delete all links between descendants and publications.
     * This
     * publications will be visible in the Declassified zone. Delete All subscriptions and favorites
     * on this topics and all descendants
     * @param pkToDelete the id of the topic to delete
     * @since 1.0
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void deleteTopic(NodePK pkToDelete) {
        try {
            // get all nodes which will be deleted
            Collection<NodePK> nodesToDelete = nodeService.getDescendantPKs(pkToDelete);
            nodesToDelete.add(pkToDelete);
            Iterator<PublicationPK> itPub;
            Collection<PublicationPK> pubsToCheck; // contains all PubPKs concerned by
            // the delete
            NodePK oneNodeToDelete; // current node to delete
            Collection<NodePK> pubFathers; // contains all fatherPKs to a given
            // publication
            PublicationPK onePubToCheck; // current pub to check
            Iterator<NodePK> itNode = nodesToDelete.iterator();
            List<Alias> aliases = new ArrayList<>();
            while (itNode.hasNext()) {
                oneNodeToDelete = itNode.next();
                // get pubs linked to current node (includes alias)
                pubsToCheck = publicationService.getPubPKsInFatherPK(oneNodeToDelete);
                itPub = pubsToCheck.iterator();
                // check each pub contained in current node
                while (itPub.hasNext()) {
                    onePubToCheck = itPub.next();
                    if (onePubToCheck.getInstanceId().equals(oneNodeToDelete.getInstanceId())) {
                        // get fathers of the pub
                        pubFathers = publicationService.getAllFatherPK(onePubToCheck);
                        if (pubFathers.size() >= 2) {
                            // the pub have got many fathers
                            // delete only the link between pub and current node
                            publicationService.removeFather(onePubToCheck, oneNodeToDelete);
                        } else {
                            sendPublicationToBasket(onePubToCheck);
                        }
                    } else {
                        // remove alias
                        aliases.clear();
                        aliases.add(new Alias(oneNodeToDelete.getId(), oneNodeToDelete.getInstanceId()));
                        publicationService.removeAlias(onePubToCheck, aliases);
                    }
                }
            }

            // Delete all subscriptions on this topic and on its descendants
            removeSubscriptionsByTopic(nodesToDelete);

            // Delete the topic
            nodeService.removeNode(pkToDelete);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void changeSubTopicsOrder(String way, NodePK subTopicPK, NodePK fatherPK) {
        List<NodeDetail> subTopics;
        try {
            subTopics = (List<NodeDetail>) nodeService.getChildrenDetails(fatherPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }

        if (subTopics != null && !subTopics.isEmpty()) {
            int indexOfTopic;

            if (fatherPK.isRoot() && !KmeliaHelper.isToolbox(subTopicPK.getInstanceId())) {
                // search the place of the basket
                indexOfTopic = getIndexOfNode("1", subTopics);
                // remove the node
                subTopics.remove(indexOfTopic);
                // search the place of the declassified
                indexOfTopic = getIndexOfNode("2", subTopics);
                // remove the node
                subTopics.remove(indexOfTopic);
            }

            // search the place of the topic we want to move
            indexOfTopic = getIndexOfNode(subTopicPK.getId(), subTopics);
            // get the node to move
            NodeDetail node2move = subTopics.get(indexOfTopic);
            // remove the node to move
            subTopics.remove(indexOfTopic);

            if (way.equals("up")) {
                subTopics.add(indexOfTopic - 1, node2move);
            } else {
                subTopics.add(indexOfTopic + 1, node2move);
            }

            // for each node, change the order and store it
            for (int i = 0; i < subTopics.size(); i++) {
                NodeDetail nodeDetail = subTopics.get(i);
                try {
                    nodeDetail.setOrder(i);
                    nodeService.setDetail(nodeDetail);
                } catch (Exception e) {
                    throw new KmeliaRuntimeException(e);
                }
            }
        }
    }

    private int getIndexOfNode(String nodeId, List<NodeDetail> nodes) {
        int index = 0;
        if (nodes != null) {
            for (NodeDetail node : nodes) {
                if (nodeId.equals(node.getNodePK().getId())) {
                    return index;
                }
                index++;
            }
        }
        return index;
    }

    @Override
    public void changeTopicStatus(String newStatus, NodePK nodePK, boolean recursiveChanges) {
        try {
            if (!recursiveChanges) {
                NodeDetail nodeDetail = nodeService.getHeader(nodePK);
                changeTopicStatus(newStatus, nodeDetail);
            } else {
                List<NodeDetail> subTree = nodeService.getSubTree(nodePK);
                for (NodeDetail aSubTree : subTree) {
                    NodeDetail nodeDetail = aSubTree;
                    changeTopicStatus(newStatus, nodeDetail);
                }
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void sortSubTopics(NodePK fatherPK) {
        sortSubTopics(fatherPK, false, null);
    }

    @Override
    public void sortSubTopics(NodePK fatherPK, boolean recursive, String[] criteria) {
        List<NodeDetail> subTopics = null;
        try {
            subTopics = (List<NodeDetail>) nodeService.getChildrenDetails(fatherPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }

        if (subTopics != null && !subTopics.isEmpty()) {
            Collections.sort(subTopics, new TopicComparator(criteria));
            // for each node, change the order and store it
            for (int i = 0; i < subTopics.size(); i++) {
                NodeDetail nodeDetail = subTopics.get(i);
                try {
                    nodeDetail.setOrder(i);
                    nodeService.setDetail(nodeDetail);
                } catch (Exception e) {
                    throw new KmeliaRuntimeException(e);
                }
                if (recursive) {
                    sortSubTopics(nodeDetail.getNodePK(), true, criteria);
                }
            }
        }
    }

    private void changeTopicStatus(String newStatus, NodeDetail topic) {
        try {
            topic.setStatus(newStatus);
            nodeService.setDetail(topic);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public List<NodeDetail> getTreeview(NodePK nodePK, String profile, boolean coWritingEnable,
            boolean draftVisibleWithCoWriting, String userId, boolean displayNb, boolean isRightsOnTopicsUsed) {

        String instanceId = nodePK.getInstanceId();
        List<NodeDetail> allowedTree = nodeService.getSubTree(nodePK);

        if (profile == null) {
            profile = getProfile(userId, nodePK);
        }

        KmeliaUserTreeViewFilter.from(userId, instanceId, nodePK, profile, isRightsOnTopicsUsed)
                .setBestUserRoleAndFilter(allowedTree);

        if (displayNb) {
            buildTreeView(nodePK, profile, coWritingEnable, draftVisibleWithCoWriting, userId, allowedTree);
        }

        return allowedTree;
    }

    private void buildTreeView(final NodePK nodePK, final String profile, final boolean coWritingEnable,
            final boolean draftVisibleWithCoWriting, final String userId, final List<NodeDetail> allowedTree) {
        boolean checkVisibility = false;
        StringBuilder statusSubQuery = new StringBuilder();
        if (profile.equals("user")) {
            checkVisibility = true;
            statusSubQuery.append(" AND sb_publication_publi.pubStatus = 'Valid' ");
        } else if (profile.equals("writer")) {
            statusSubQuery.append(" AND (");
            if (coWritingEnable && draftVisibleWithCoWriting) {
                statusSubQuery.append("sb_publication_publi.pubStatus = 'Valid' OR ")
                        .append("sb_publication_publi.pubStatus = 'Draft' OR ")
                        .append("sb_publication_publi.pubStatus = 'Unvalidate' ");
            } else {
                checkVisibility = true;
                statusSubQuery.append("sb_publication_publi.pubStatus = 'Valid' OR ")
                        .append("(sb_publication_publi.pubStatus = 'Draft' AND ")
                        .append("sb_publication_publi.pubUpdaterId = '").append(userId)
                        .append("') OR (sb_publication_publi.pubStatus = 'Unvalidate' AND ")
                        .append("sb_publication_publi.pubUpdaterId = '").append(userId).append("') ");
            }
            statusSubQuery.append("OR (sb_publication_publi.pubStatus = 'ToValidate' ")
                    .append("AND sb_publication_publi.pubUpdaterId = '").append(userId).append("') ");
            statusSubQuery.append("OR sb_publication_publi.pubUpdaterId = '").append(userId).append("')");
        } else {
            statusSubQuery.append(" AND (");
            if (coWritingEnable && draftVisibleWithCoWriting) {
                statusSubQuery.append("sb_publication_publi.pubStatus IN ('Valid','ToValidate','Draft') ");
            } else {
                if (profile.equals("publisher")) {
                    checkVisibility = true;
                }
                statusSubQuery
                        .append("sb_publication_publi.pubStatus IN ('Valid','ToValidate') OR (sb_publication_publi"
                                + ".pubStatus = 'Draft' AND sb_publication_publi.pubUpdaterId = '")
                        .append(userId).append("') ");
            }
            statusSubQuery.append("OR sb_publication_publi.pubUpdaterId = '").append(userId).append("')");
        }

        Map<String, Integer> numbers = publicationService.getDistributionTree(nodePK.getInstanceId(),
                statusSubQuery.toString(), checkVisibility);

        // set right number of publications in basket
        NodePK trashPk = new NodePK(NodePK.BIN_NODE_ID, nodePK.getInstanceId());
        int nbPubsInTrash = getPublicationsInBasket(trashPk, profile, userId).size();
        numbers.put(NodePK.BIN_NODE_ID, nbPubsInTrash);

        decorateWithNumberOfPublications(allowedTree, numbers);
    }

    private void decorateWithNumberOfPublications(List<NodeDetail> nodes, Map<String, Integer> numbers) {
        for (NodeDetail node : nodes) {
            decorateWithNumberOfPublications(node, numbers);
        }
    }

    private int decorateWithNumberOfPublications(NodeDetail node, Map<String, Integer> numbers) {
        Integer nb = numbers.get(node.getNodePK().getId());
        for (NodeDetail child : node.getChildrenDetails()) {
            nb += decorateWithNumberOfPublications(child, numbers);
        }
        node.setNbObjects(nb);
        return nb;
    }

    private Collection<PublicationDetail> getPublicationsInBasket(NodePK pk, String userProfile, String userId) {
        String currentUserId = userId;
        try {
            // Give the publications associated to basket topic and visibility period expired
            if (SilverpeasRole.admin.isInRole(userProfile)) {// Admin can see all Publis in the basket.
                currentUserId = null;
            }
            return publicationService.getDetailsByFatherPK(pk, null, false, currentUserId);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Subscriptions - get the subscription list of the current user
     * @param userId
     * @param componentId
     * @return a Path Collection - it's a Collection of NodeDetail collection
     * @see NodeDetail
     * @since 1.0
     */
    @Override
    public Collection<Collection<NodeDetail>> getSubscriptionList(String userId, String componentId) {
        try {
            Collection<Subscription> list = getSubscribeService()
                    .getBySubscriberAndComponent(UserSubscriptionSubscriber.from(userId), componentId);
            Collection<Collection<NodeDetail>> detailedList = new ArrayList<>();
            // For each favorite, get the path from root to favorite
            for (Subscription subscription : list) {
                Collection<NodeDetail> path = nodeService.getPath((NodePK) subscription.getResource().getPK());
                detailedList.add(path);
            }
            return detailedList;
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Subscriptions - remove a subscription to the subscription list of the current user
     * @param topicPK the subscribe topic Id to remove
     * @param userId
     * @since 1.0
     */
    @Override
    public void removeSubscriptionToCurrentUser(NodePK topicPK, String userId) {
        try {
            getSubscribeService().unsubscribe(new NodeSubscription(userId, topicPK));
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Subscriptions - remove all subscriptions from topic
     * @param topicPKsToDelete the subscription topic Ids to remove
     * @since 1.0
     */
    private void removeSubscriptionsByTopic(Collection<NodePK> topicPKsToDelete) {
        try {
            Collection<SubscriptionResource> subscriptionResourcesToDelete = new ArrayList<>();
            for (NodePK topicPK : topicPKsToDelete) {
                subscriptionResourcesToDelete.add(NodeSubscriptionResource.from(topicPK));
            }
            getSubscribeService().unsubscribeByResources(subscriptionResourcesToDelete);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Subscriptions - add a subscription
     * @param topicPK the subscription topic Id to add
     * @param userId the subscription userId
     * @since 1.0
     */
    @Override
    public void addSubscription(NodePK topicPK, String userId) {
        getSubscribeService().subscribe(new NodeSubscription(userId, topicPK));
    }

    /**
     * @param topicPK
     * @param userId
     * @return true if this topic does not exists in user subscriptions and can be added to them.
     */
    @Override
    public boolean checkSubscription(NodePK topicPK, String userId) {
        return !getSubscribeService().existsSubscription(new NodeSubscription(userId, topicPK));
    }

    /**
     * ***********************************************************************************
     * Interface - Gestion des publications
     * **********************************************************************************
     */
    private List<KmeliaPublication> pubDetails2userPubs(Collection<PublicationDetail> pubDetails) {
        List<KmeliaPublication> publications = new ArrayList<>();
        int i = -1;
        for (PublicationDetail publicationDetail : pubDetails) {
            publications.add(KmeliaPublication.aKmeliaPublicationFromDetail(publicationDetail, i++));
        }
        return publications;
    }

    /**
     * Return the detail of a publication (only the Header)
     * @param pubPK the id of the publication
     * @return a PublicationDetail
     * @see org.silverpeas.core.contribution.publication.model.PublicationDetail
     * @since 1.0
     */
    @Override
    public PublicationDetail getPublicationDetail(PublicationPK pubPK) {
        try {
            return publicationService.getDetail(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Return list of all path to this publication - it's a Collection of NodeDetail collection
     * @param pubPK the id of the publication
     * @return a Collection of NodeDetail collection
     * @see NodeDetail
     * @since 1.0
     */
    @Override
    public Collection<Collection<NodeDetail>> getPathList(PublicationPK pubPK) {
        Collection<NodePK> fatherPKs = null;
        try {
            // get all nodePK whick contains this publication
            fatherPKs = publicationService.getAllFatherPK(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        try {
            List<Collection<NodeDetail>> pathList = new ArrayList<>();
            if (fatherPKs != null) {
                // For each topic, get the path to it
                for (NodePK pk : fatherPKs) {
                    Collection<NodeDetail> path = nodeService.getPath(pk);
                    // add this path
                    pathList.add(path);
                }
            }
            return pathList;
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Create a new Publication (only the header - parameters) to the current Topic
     * @param pubDetail a PublicationDetail
     * @return the id of the new publication
     * @see org.silverpeas.core.contribution.publication.model.PublicationDetail
     * @since 1.0
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public String createPublicationIntoTopic(PublicationDetail pubDetail, NodePK fatherPK) {
        PdcClassification predefinedClassification = pdcClassificationService
                .findAPreDefinedClassification(fatherPK.getId(), fatherPK.getInstanceId());
        return createPublicationIntoTopic(pubDetail, fatherPK, predefinedClassification);
    }

    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public String createPublicationIntoTopic(PublicationDetail pubDetail, NodePK fatherPK,
            PdcClassification classification) {
        final String pubId;
        KmeliaOperationContext.about(CREATION);
        try {
            pubId = createPublicationIntoTopicWithoutNotifications(pubDetail, fatherPK, classification);

            // creates todos for publishers
            createTodosForPublication(pubDetail, true);

            // alert supervisors
            sendAlertToSupervisors(fatherPK, pubDetail);

            // alert subscribers
            sendSubscriptionsNotification(pubDetail, NotifAction.CREATE, false);

        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return pubId;
    }

    private String createPublicationIntoTopicWithoutNotifications(PublicationDetail pubDetail, NodePK fatherPK,
            PdcClassification classification) {
        PublicationPK pubPK;
        try {
            PublicationDetail detail = changePublicationStatusOnCreation(pubDetail, fatherPK);
            // create the publication
            pubPK = publicationService.createPublication(detail);
            detail.getPK().setId(pubPK.getId());
            // register the new publication as a new content to content manager
            createSilverContent(detail, detail.getCreatorId());
            // add this publication to the current topic
            addPublicationToTopicWithoutNotifications(pubPK, fatherPK, true);
            // classify the publication on the PdC if its classification is defined
            // subscribers are notified later (only if publication is valid)
            classification.classifyContent(detail, false);

            createdIntoRequestContext(detail);

        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return pubPK.getId();
    }

    private String getProfile(String userId, NodePK nodePK) {
        String profile;
        OrganizationController orgCtrl = getOrganisationController();
        if (isRightsOnTopicsEnabled(nodePK.getInstanceId())) {
            NodeDetail topic = nodeService.getHeader(nodePK);
            if (topic.haveRights()) {
                profile = KmeliaHelper.getProfile(orgCtrl.getUserProfiles(userId, nodePK.getInstanceId(),
                        topic.getRightsDependsOn(), ObjectType.NODE));
            } else {
                profile = KmeliaHelper.getProfile(getUserRoles(nodePK.getInstanceId(), userId));
            }
        } else {
            profile = KmeliaHelper.getProfile(getUserRoles(nodePK.getInstanceId(), userId));
        }
        return profile;
    }

    private String getProfileOnPublication(String userId, PublicationPK pubPK) {
        final String profile;
        final List<NodePK> fathers = (List<NodePK>) getPublicationFathers(pubPK);
        profile = getProfileForDirectNodeOfPublication(userId, pubPK, fathers);
        return profile;
    }

    private String getProfileForDirectNodeOfPublication(final String userId, final PublicationPK pubPK,
            final List<NodePK> fathers) {
        final String profile;
        if (fathers != null && !fathers.isEmpty()) {
            final NodePK nodePK = fathers.get(0);
            profile = getProfile(userId, nodePK);
        } else {
            // peculiar case in which the publication isn't in any node: this situation occurs if the
            // publication is definitely deleted or orphaned. In that case, we take the profile of the
            // user in the concerned kmelia instance. This shouldn't occur!
            SilverLogger.getLogger(this).warn("The publication {0} is orphaned!", pubPK);
            profile = SilverpeasRole
                    .getHighestFrom(SilverpeasRole
                            .from(getOrganisationController().getUserProfiles(userId, pubPK.getInstanceId())))
                    .getName();

        }
        return profile;
    }

    private PublicationDetail changePublicationStatusOnCreation(PublicationDetail pubDetail, NodePK nodePK) {
        String status = pubDetail.getStatus();
        if (!isDefined(status)) {
            status = PublicationDetail.TO_VALIDATE_STATUS;

            boolean draftModeUsed = isDraftModeUsed(pubDetail.getPK().getInstanceId());

            if (draftModeUsed) {
                status = PublicationDetail.DRAFT_STATUS;
            } else {
                String profile = getProfile(pubDetail.getCreatorId(), nodePK);
                if (SilverpeasRole.publisher.isInRole(profile) || SilverpeasRole.admin.isInRole(profile)) {
                    status = PublicationDetail.VALID_STATUS;
                }
            }
        }
        pubDetail.setStatus(status);
        KmeliaHelper.checkIndex(pubDetail);
        return pubDetail;
    }

    private boolean changePublicationStatusOnMove(PublicationDetail pub, NodePK to) {
        String oldStatus = pub.getStatus();
        String status = pub.getStatus();
        if (!status.equals(PublicationDetail.DRAFT_STATUS)) {
            status = PublicationDetail.TO_VALIDATE_STATUS;
            String profile = getProfile(pub.getUpdaterId(), to);
            if (SilverpeasRole.publisher.isInRole(profile) || SilverpeasRole.admin.isInRole(profile)) {
                status = PublicationDetail.VALID_STATUS;
            }
        }
        pub.setStatus(status);
        KmeliaHelper.checkIndex(pub);
        return !oldStatus.equals(status);
    }

    /**
     * determine new publication's status according to actual status and current user's profile
     * @param pubDetail
     * @return true if status has changed, false otherwise
     */
    private boolean changePublicationStatusOnUpdate(PublicationDetail pubDetail) {
        String oldStatus = pubDetail.getStatus();
        String newStatus = oldStatus;

        List<NodePK> fathers = (List<NodePK>) getPublicationFathers(pubDetail.getPK());

        if (pubDetail.isStatusMustBeChecked() && !pubDetail.isDraft() && !pubDetail.isClone()) {
            newStatus = setPublicationStatus(pubDetail, newStatus, fathers);
        }

        KmeliaHelper.checkIndex(pubDetail);

        if (fathers == null || fathers.isEmpty() || (fathers.size() == 1 && fathers.get(0).isTrash())) {
            // la publication est dans la corbeille
            pubDetail.setIndexOperation(IndexManager.NONE);
        }
        return !oldStatus.equalsIgnoreCase(newStatus);
    }

    private String setPublicationStatus(final PublicationDetail pubDetail, String newStatus,
            final List<NodePK> fathers) {
        final String profile = getProfileForDirectNodeOfPublication(pubDetail.getUpdaterId(), pubDetail.getPK(),
                fathers);
        if (SilverpeasRole.writer.isInRole(profile)) {
            newStatus = PublicationDetail.TO_VALIDATE_STATUS;
        } else if (pubDetail.isRefused()
                && (SilverpeasRole.admin.isInRole(profile) || SilverpeasRole.publisher.isInRole(profile))) {
            newStatus = PublicationDetail.VALID_STATUS;
        }
        pubDetail.setStatus(newStatus);
        return newStatus;
    }

    /**
     * Update a publication (only the header - parameters)
     * @param pubDetail a PublicationDetail
     * @see org.silverpeas.core.contribution.publication.model.PublicationDetail
     * @since 1.0
     */
    @Override
    public void updatePublication(PublicationDetail pubDetail) {
        updatePublication(pubDetail, KmeliaHelper.PUBLICATION_HEADER, false);
    }

    @Override
    public void updatePublication(PublicationDetail pubDetail, boolean forceUpdateDate) {
        updatePublication(pubDetail, KmeliaHelper.PUBLICATION_HEADER, forceUpdateDate);
    }

    private void updatePublication(PublicationDetail pubDetail, int updateScope, boolean forceUpdateDate) {
        KmeliaOperationContext.about(UPDATE);
        try {
            // if pubDetail is a clone
            boolean isClone = pubDetail.isClone();
            PublicationDetail old = getPublicationDetail(pubDetail.getPK());

            // prevents to lose some data
            if (StringUtil.isDefined(old.getTargetValidatorId())
                    && !StringUtil.isDefined(pubDetail.getTargetValidatorId())) {
                pubDetail.setTargetValidatorId(old.getTargetValidatorId());
            }
            final boolean isPublicationInBasket = isPublicationInBasket(pubDetail.getPK());
            if (isClone) {
                // update only updateDate
                publicationService.setDetail(pubDetail, forceUpdateDate);
                performValidatorChanges(old, pubDetail);
            } else {
                boolean statusChanged = changePublicationStatusOnUpdate(pubDetail);
                publicationService.setDetail(pubDetail, forceUpdateDate);

                if (!isPublicationInBasket) {
                    updatePublicationContent(pubDetail, updateScope, old, statusChanged);
                }
            }

            // Sending a subscription notification if the publication updated comes not from the
            // basket, has not been created or already updated from the same request
            if (!isPublicationInBasket && !hasPublicationBeenCreatedFromRequestContext(pubDetail)
                    && !hasPublicationBeenUpdatedFromRequestContext(pubDetail)) {
                sendSubscriptionsNotification(pubDetail, NotifAction.UPDATE, false);
            }

            updatedIntoRequestContext(pubDetail);

        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void updatePublicationContent(final PublicationDetail pubDetail, final int updateScope,
            final PublicationDetail old, final boolean statusChanged) {
        if (statusChanged) {
            // creates todos for publishers
            this.createTodosForPublication(pubDetail, false);
        } else {
            performValidatorChanges(old, pubDetail);
        }

        updateSilverContentVisibility(pubDetail);

        // la publication a t modifi par un superviseur
        // le crateur de la publi doit tre averti
        String profile = KmeliaHelper
                .getProfile(getUserRoles(pubDetail.getPK().getInstanceId(), pubDetail.getUpdaterId()));
        if ("supervisor".equals(profile)) {
            sendModificationAlert(updateScope, pubDetail.getPK());
        }

        boolean visibilityPeriodUpdated = isVisibilityPeriodUpdated(pubDetail, old);

        if (statusChanged || visibilityPeriodUpdated) {
            if (KmeliaHelper.isIndexable(pubDetail)) {
                indexExternalElementsOfPublication(pubDetail);
            } else {
                unIndexExternalElementsOfPublication(pubDetail.getPK());
            }
        }
    }

    /**
     * Performs the treatments associated to changes about set of validators linked to a publication.
     * @param previousPublication the publication data (or clone data) before changes.
     * @param currentPublication the publication data (or clone data if previousPublication is a
     * clone) containing the changes.
     */
    private void performValidatorChanges(final PublicationDetail previousPublication,
            final PublicationDetail currentPublication) {

        // The publication (or the clone) must be into "ToValidate" state, and validator identifiers
        // must exist.
        if (currentPublication.isValidationRequired() && currentPublication.getTargetValidatorIds() != null) {

            // Getting validator identifiers from previous and current data.
            List<String> oldValidatorIds = Arrays.asList(previousPublication.getTargetValidatorIds());
            List<String> newValidatorIds = Arrays.asList(currentPublication.getTargetValidatorIds());

            // Computing identifiers of removed validators, and the ones of added validators.
            List<String> toRemoveToDo = new ArrayList<>(oldValidatorIds);
            List<String> toAlert = new ArrayList<>(newValidatorIds);
            toRemoveToDo.removeAll(newValidatorIds);
            toAlert.removeAll(oldValidatorIds);

            // Performing the actions.
            removeTodoForPublication(currentPublication.getPK(), toRemoveToDo);
            addTodoAndSendNotificationToValidators(currentPublication, toAlert);
        }
    }

    private boolean isVisibilityPeriodUpdated(PublicationDetail pubDetail, PublicationDetail old) {
        boolean beginVisibilityPeriodUpdated = ((pubDetail.getBeginDate() != null && old.getBeginDate() == null)
                || (pubDetail.getBeginDate() == null && old.getBeginDate() != null)
                || (pubDetail.getBeginDate() != null && old.getBeginDate() != null
                        && !pubDetail.getBeginDate().equals(old.getBeginDate())));
        boolean endVisibilityPeriodUpdated = ((pubDetail.getEndDate() != null && old.getEndDate() == null)
                || (pubDetail.getEndDate() == null && old.getEndDate() != null) || (pubDetail.getEndDate() != null
                        && old.getEndDate() != null && !pubDetail.getEndDate().equals(old.getEndDate())));
        return beginVisibilityPeriodUpdated || endVisibilityPeriodUpdated;
    }

    @SimulationActionProcess(elementLister = KmeliaPublicationSimulationElementLister.class)
    @Action(ActionType.MOVE)
    @Override
    public void movePublication(@SourcePK PublicationPK pubPK, @TargetPK NodePK to,
            KmeliaPasteDetail pasteContext) {
        PublicationDetail pub = getPublicationDetail(pubPK);
        if (pub != null) {
            if (pubPK.getInstanceId().equals(to.getInstanceId())) {
                movePublicationInSameApplication(pub, to, pasteContext);
            } else {
                movePublicationInAnotherApplication(pub, to, pasteContext);
            }
        }
    }

    @SimulationActionProcess(elementLister = KmeliaPublicationSimulationElementLister.class)
    @Action(ActionType.MOVE)
    @Override
    public void movePublicationInSameApplication(@SourcePK PublicationPK pubPK, @TargetPK NodePK from,
            KmeliaPasteDetail pasteContext) {
        PublicationDetail pub = getPublicationDetail(pubPK);
        String userId = pasteContext.getUserId();
        NodePK to = pasteContext.getToPK();

        // check if user can cut publication from source folder
        String profile = getUserTopicProfile(from, userId);
        boolean cutAllowed = KmeliaPublicationHelper.isCanBeCut(from.getComponentName(), userId, profile,
                pub.getCreator());

        // check if user can paste publication into target folder
        String profileInTarget = getUserTopicProfile(to, userId);
        boolean pasteAllowed = KmeliaPublicationHelper.isCreationAllowed(to, profileInTarget);

        if (cutAllowed && pasteAllowed) {
            movePublicationInSameApplication(pub, to, pasteContext);
        }
    }

    private void movePublicationInSameApplication(PublicationDetail pub, NodePK to,
            KmeliaPasteDetail pasteContext) {
        if (to.isTrash()) {
            sendPublicationToBasket(pub.getPK());
        } else {
            // update parent
            publicationService.removeAllFather(pub.getPK());
            publicationService.addFather(pub.getPK(), to);
            pub.setTargetValidatorId(pasteContext.getTargetValidatorIds());
            processPublicationAfterMove(pub, to, pasteContext.getUserId());
        }
    }

    /**
     * Move a publication to another component. Moving in this order : <ul>
     * <li>moving the metadata</li>
     * <li>moving the thumbnail</li>
     * <li>moving the content</li>
     * <li>moving the wysiwyg</li>
     * <li>moving the images linked to the wysiwyg</li>
     * <li>moving the xml form content (files and images)</li>
     * <li>moving attachments</li>
     * <li>moving the pdc poistion</li>
     * <li>moving the statistics</li>
     * </ul>
     */
    private void movePublicationInAnotherApplication(PublicationDetail pub, NodePK to,
            KmeliaPasteDetail pasteContext) {
        try {
            ResourceReference fromResourceReference = new ResourceReference(pub.getPK());
            String fromComponentId = pub.getInstanceId();
            ResourceReference toPubliResourceReference = new ResourceReference(pub.getId(), to.getInstanceId());

            // remove index relative to publication
            unIndexExternalElementsOfPublication(pub.getPK());

            // move thumbnail
            ThumbnailController.moveThumbnail(fromResourceReference, toPubliResourceReference);

            moveAdditionalFiles(pub, fromResourceReference, toPubliResourceReference);

            // change images path in wysiwyg
            WysiwygController.wysiwygPlaceHaveChanged(fromResourceReference.getInstanceId(), pub.getPK().getId(),
                    to.getInstanceId(), pub.getPK().getId());

            // move regular files
            List<SimpleDocument> docs = AttachmentServiceProvider.getAttachmentService()
                    .listDocumentsByForeignKeyAndType(fromResourceReference, DocumentType.attachment, null);
            for (SimpleDocument doc : docs) {
                AttachmentServiceProvider.getAttachmentService().moveDocument(doc, toPubliResourceReference);
            }

            // move form content
            String infoId = pub.getInfoId();
            if (infoId != null && !"0".equals(infoId)) {
                // register content to component
                PublicationTemplateManager templateManager = PublicationTemplateManager.getInstance();
                GenericRecordSet toRecordSet = templateManager.addDynamicPublicationTemplate(
                        to.getInstanceId() + ":" + pub.getInfoId(), pub.getInfoId() + ".xml");
                RecordTemplate toRecordTemplate = toRecordSet.getRecordTemplate();

                // get xmlContent to move
                PublicationTemplate pubTemplateFrom = templateManager
                        .getPublicationTemplate(fromComponentId + ":" + pub.getInfoId());

                RecordSet set = pubTemplateFrom.getRecordSet();
                set.move(fromResourceReference, toPubliResourceReference, toRecordTemplate);
            }

            // move comments
            getCommentService().moveComments(PublicationDetail.getResourceType(), fromResourceReference,
                    toPubliResourceReference);

            // move pdc positions
            // Careful! positions must be moved according to taxonomy restrictions of target application
            int fromSilverObjectId = getSilverObjectId(pub.getPK());
            // get positions of cutted publication
            List<ClassifyPosition> positions = pdcManager.getPositions(fromSilverObjectId, fromComponentId);

            // delete taxonomy data relative to moved publication
            deleteSilverContent(pub.getPK());

            // move statistics
            statisticService.moveStat(toPubliResourceReference, 1, PUBLICATION);

            // move publication itself
            publicationService.movePublication(pub.getPK(), to, false);
            pub.getPK().setComponentName(to.getInstanceId());

            pub.setTargetValidatorId(pasteContext.getTargetValidatorIds());

            processPublicationAfterMove(pub, to, pasteContext.getUserId());

            // index moved publication
            if (KmeliaHelper.isIndexable(pub)) {
                indexPublication(pub);
            }

            // reference pasted publication on taxonomy service
            int toSilverObjectId = getSilverObjectId(pub.getPK());
            // add original positions to pasted publication
            pdcManager.addPositions(positions, toSilverObjectId, to.getInstanceId());
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void moveAdditionalFiles(final @SourcePK PublicationDetail pub,
            final ResourceReference fromResourceReference, final ResourceReference toPubliResourceReference) {
        try {
            // move additional files
            List<SimpleDocument> documents = AttachmentServiceProvider.getAttachmentService()
                    .listDocumentsByForeignKeyAndType(fromResourceReference, DocumentType.image, null);
            documents.addAll(AttachmentServiceProvider.getAttachmentService()
                    .listDocumentsByForeignKeyAndType(fromResourceReference, DocumentType.wysiwyg, null));
            for (SimpleDocument doc : documents) {
                AttachmentServiceProvider.getAttachmentService().moveDocument(doc, toPubliResourceReference);
            }
        } catch (AttachmentException e) {
            SilverLogger.getLogger(this).error("Cannot move attachments of publication {0}",
                    new String[] { pub.getPK().getId() }, e);
        }
    }

    private void processPublicationAfterMove(PublicationDetail pub, NodePK to, String userId) {
        // update last modifier
        pub.setUpdaterId(userId);
        // status must be checked according to topic rights and last modifier (current user)
        boolean statusChanged = changePublicationStatusOnMove(pub, to);
        // update publication
        publicationService.setDetail(pub, statusChanged);

        // check visibility on taxonomy
        updateSilverContentVisibility(pub);

        if (statusChanged) {
            // creates todos for publishers
            createTodosForPublication(pub, false);
            // index or unindex external elements
            if (KmeliaHelper.isIndexable(pub)) {
                indexExternalElementsOfPublication(pub);
            } else {
                unIndexExternalElementsOfPublication(pub.getPK());
            }
        }
        // send notifications like a publish action
        sendSubscriptionsNotification(pub, NotifAction.PUBLISHED, false);
    }

    @Override
    public void externalElementsOfPublicationHaveChanged(PublicationPK pubPK, String userId) {
        externalElementsOfPublicationHaveChanged(pubPK, userId, true);
    }

    private void externalElementsOfPublicationHaveChanged(PublicationPK pubPK, String userId,
            boolean indexExternalElements) {
        // check if related contribution is managed by kmelia
        if (pubPK == null || StringUtil.isNotDefined(pubPK.getInstanceId())
                || (!pubPK.getInstanceId().startsWith("kmelia") && !pubPK.getInstanceId().startsWith("toolbox")
                        && !pubPK.getInstanceId().startsWith("kmax"))) {
            return;
        }

        PublicationConcernedByUpdate publicationConcernedByUpdate = new PublicationConcernedByUpdate(pubPK)
                .invoke();
        if (publicationConcernedByUpdate.isPublicationNotDefined()) {
            return;
        }

        PublicationDetail pubDetail = publicationConcernedByUpdate.getPubDetail();
        boolean isPublicationInBasketBeforeUpdate = publicationConcernedByUpdate
                .isPublicationInBasketBeforeUpdate();

        if (pubDetail.isClone()) {
            pubDetail.setIndexOperation(IndexManager.NONE);
        }

        if (isDefined(userId)) {
            pubDetail.setUpdaterId(userId);
        }

        // update publication header to store last modifier and update date
        if (!isDefined(userId)) {
            updatePublication(pubDetail, KmeliaHelper.PUBLICATION_CONTENT, false);
        } else {
            updatePublicationAccordingToProfile(userId, pubDetail);
        }

        if (KmeliaHelper.isIndexable(pubDetail) && !isPublicationInBasketBeforeUpdate) {
            publicationService.createIndex(pubDetail);
        }

        if (indexExternalElements) {
            // index all attached files to taking into account visibility period
            indexExternalElementsOfPublication(pubDetail);
        }
    }

    private void updatePublicationAccordingToProfile(final String userId, final PublicationDetail pubDetail) {
        // check if user have sufficient rights to update a publication
        String profile = getProfileOnPublication(userId, pubDetail.getPK());
        if ("supervisor".equals(profile) || SilverpeasRole.publisher.isInRole(profile)
                || SilverpeasRole.admin.isInRole(profile) || SilverpeasRole.writer.isInRole(profile)) {
            updatePublication(pubDetail, KmeliaHelper.PUBLICATION_CONTENT, false);
        } else {
            SilverLogger.getLogger(this).warn("User {0} not allowed to update publication {1}", userId,
                    pubDetail.getPK().getId());
        }
    }

    private boolean isClone(PublicationDetail publication) {
        return isDefined(publication.getCloneId()) && !"-1".equals(publication.getCloneId())
                && !isDefined(publication.getCloneStatus());
    }

    /**
     * HEAD Delete a publication If this publication is in the basket or in the DZ, it's deleted from
     * the database Else it only send to the basket.
     * @param pubPK the id of the publication to delete
     * @see TopicDetail
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void deletePublication(PublicationPK pubPK) {
        // if the publication is in the basket or in the DZ
        // this publication is deleted from the database
        KmeliaOperationContext.about(DELETION);
        try {
            // remove form content
            removeXMLContentOfPublication(pubPK);
            // delete all reading controls associated to this publication
            deleteAllReadingControlsByPublication(pubPK);
            // delete all links
            publicationService.removeAllFather(pubPK);
            // delete the publication
            publicationService.removePublication(pubPK);
            // delete reference to contentManager
            deleteSilverContent(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }

    }

    /**
     * Send the publication in the basket topic
     * @param pubPK the id of the publication
     * @param kmaxMode
     * @see TopicDetail
     * @since 1.0
     */
    @Override
    public void sendPublicationToBasket(PublicationPK pubPK, boolean kmaxMode) {
        KmeliaOperationContext.about(REMOVING);
        try {
            // remove coordinates for Kmax
            if (kmaxMode) {
                CoordinatePK coordinatePK = new CoordinatePK(UNKNOWN, pubPK.getSpaceId(), pubPK.getComponentName());

                Collection<NodePK> fatherPKs = publicationService.getAllFatherPK(pubPK);
                // delete publication coordinates
                Iterator<NodePK> it = fatherPKs.iterator();
                List<String> coordinates = new ArrayList<>();
                while (it.hasNext()) {
                    String coordinateId = (it.next()).getId();
                    coordinates.add(coordinateId);
                }
                if (!coordinates.isEmpty()) {
                    coordinatesService.deleteCoordinates(coordinatePK, coordinates);
                }
            }

            // remove all links between this publication and topics
            publicationService.removeAllFather(pubPK);
            // add link between this publication and the basket topic
            publicationService.addFather(pubPK, new NodePK("1", pubPK));

            // remove all the todos attached to the publication
            removeAllTodosForPublication(pubPK);

            // publication is no more accessible
            updateSilverContentVisibility(pubPK, false);

            unIndexExternalElementsOfPublication(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void sendPublicationToBasket(PublicationPK pubPK) {
        sendPublicationToBasket(pubPK, KmeliaHelper.isKmax(pubPK.getInstanceId()));
    }

    /**
     * Add a publication to a topic and send email alerts to topic subscribers
     * @param pubPK the id of the publication
     * @param fatherPK the id of the topic
     * @param isACreation
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void addPublicationToTopic(PublicationPK pubPK, NodePK fatherPK, boolean isACreation) {
        addPublicationToTopicWithoutNotifications(pubPK, fatherPK, isACreation);
        PublicationDetail pubDetail = getPublicationDetail(pubPK);
        sendSubscriptionsNotification(pubDetail, NotifAction.CREATE, false);
    }

    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void addPublicationToTopicWithoutNotifications(PublicationPK pubPK, NodePK fatherPK,
            boolean isACreation) {
        PublicationDetail pubDetail = getPublicationDetail(pubPK);
        if (!isACreation) {
            try {
                Collection<NodePK> fathers = publicationService.getAllFatherPK(pubPK);
                if (isPublicationInBasket(pubPK, fathers)) {
                    publicationService.removeFather(pubPK, new NodePK(NodePK.BIN_NODE_ID, fatherPK));
                    if (PublicationDetail.VALID_STATUS.equalsIgnoreCase(pubDetail.getStatus())) {
                        // index publication
                        publicationService.createIndex(pubPK);
                        // index external elements
                        indexExternalElementsOfPublication(pubDetail);
                        // publication is accessible again
                        updateSilverContentVisibility(pubDetail);
                    } else if (PublicationDetail.TO_VALIDATE_STATUS.equalsIgnoreCase(pubDetail.getStatus())) {
                        // create validation todos for publishers
                        createTodosForPublication(pubDetail, true);
                    }
                } else if (fathers.isEmpty()) {
                    // The publi have got no father
                    // change the end date to make this publi visible
                    pubDetail.setEndDate(null);
                    publicationService.setDetail(pubDetail);
                    // publication is accessible again
                    updateSilverContentVisibility(pubDetail);
                }
            } catch (Exception e) {
                throw new KmeliaRuntimeException(e);
            }
        }

        try {
            publicationService.addFather(pubPK, fatherPK);
            // index publication to index path
            publicationService.createIndex(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private boolean isPublicationInBasket(PublicationPK pubPK) {
        return isPublicationInBasket(pubPK, null);
    }

    private boolean isPublicationInBasket(PublicationPK pubPK, Collection<NodePK> fathers) {
        if (fathers == null) {
            fathers = publicationService.getAllFatherPK(pubPK);
        }
        if (fathers.size() == 1) {
            Iterator<NodePK> iterator = fathers.iterator();
            if (iterator.hasNext()) {
                NodePK pk = iterator.next();
                if (pk.isTrash()) {
                    return true;
                }
            }
        }
        return false;
    }

    private NodePK sendSubscriptionsNotification(PublicationDetail pubDetail, NotifAction action,
            final boolean sendOnlyToAliases) {
        NodePK oneFather = null;
        // We alert subscribers only if publication is Valid
        if (!pubDetail.haveGotClone() && pubDetail.isValid() && pubDetail.isVisible()) {
            // Topic subscriptions
            Collection<NodePK> fathers = getPublicationFathers(pubDetail.getPK());
            if (!sendOnlyToAliases) {
                for (NodePK father : fathers) {
                    oneFather = father;
                    sendSubscriptionsNotification(father, pubDetail, action);
                }
            }

            // Subscriptions related to aliases
            List<Alias> aliases = (List<Alias>) getAlias(pubDetail.getPK());
            sendSubscriptionNotificationForAliases(pubDetail, action, sendOnlyToAliases, fathers, aliases);

            // PDC subscriptions
            try {
                int silverObjectId = getSilverObjectId(pubDetail.getPK());
                List<ClassifyPosition> positions = pdcManager.getPositions(silverObjectId,
                        pubDetail.getPK().getInstanceId());
                if (positions != null) {
                    for (ClassifyPosition position : positions) {
                        pdcSubscriptionManager.checkSubscriptions(position.getValues(),
                                pubDetail.getPK().getInstanceId(), silverObjectId);
                    }
                }
            } catch (PdcException e) {
                SilverLogger.getLogger(this).error("PdC subscriptions notification failure for publication {0}",
                        new String[] { pubDetail.getPK().getId() }, e);
            }
        }
        return oneFather;
    }

    private void sendSubscriptionNotificationForAliases(final PublicationDetail pubDetail, final NotifAction action,
            final boolean sendOnlyToAliases, final Collection<NodePK> fathers, final List<Alias> aliases) {
        for (Alias alias : aliases) {
            // Transform the current alias to a NodePK (even if Alias is extending NodePK) in the aim
            // to execute the equals method of NodePK
            NodePK aliasNodePk = new NodePK(alias.getId(), alias.getInstanceId());
            if ((sendOnlyToAliases && alias.getDate() != null) || !fathers.contains(aliasNodePk)) {
                // Perform subscription notification sendings when the alias is not the one of the
                // original publication
                pubDetail.setAlias(true);
                sendSubscriptionsNotification(alias, pubDetail, action);
            }
        }
    }

    private void sendSubscriptionsNotification(NodePK fatherPK, PublicationDetail pubDetail, NotifAction action) {

        // Send email alerts
        try {

            // Building and sending the notification
            UserNotificationHelper
                    .buildAndSend(new KmeliaSubscriptionPublicationUserNotification(fatherPK, pubDetail, action));

        } catch (Exception e) {
            SilverLogger.getLogger(this).error("Subscriber notification failure about publication {0}",
                    new String[] { pubDetail.getPK().getId() }, e);
        }
    }

    /**
     * Delete a path between publication and topic
     * @param pubPK
     * @param fatherPK
     */
    @Override
    public void deletePublicationFromTopic(PublicationPK pubPK, NodePK fatherPK) {
        try {
            Collection<NodePK> pubFathers = publicationService.getAllFatherPK(pubPK);
            if (pubFathers.size() >= 2) {
                publicationService.removeFather(pubPK, fatherPK);
            } else {
                // la publication n'a qu'un seul emplacement
                // elle est donc place dans la corbeille du crateur
                sendPublicationToBasket(pubPK);
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void deletePublicationFromAllTopics(PublicationPK pubPK) {
        try {
            publicationService.removeAllFather(pubPK);

            // la publication n'a qu'un seul emplacement
            // elle est donc place dans la corbeille du crateur
            sendPublicationToBasket(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * Updates the publication links
     * @param pubPK publication identifier which you want to update links
     * @param links list of publication to link with current.
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void addInfoLinks(PublicationPK pubPK, List<ResourceReference> links) {
        try {
            publicationService.addLinks(pubPK, links);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public CompletePublication getCompletePublication(PublicationPK pubPK) {
        CompletePublication completePublication = null;

        try {
            completePublication = publicationService.getCompletePublication(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return completePublication;
    }

    @Override
    public KmeliaPublication getPublication(PublicationPK pubPK) {
        return KmeliaPublication.aKmeliaPublicationWithPk(pubPK);
    }

    @Override
    public TopicDetail getPublicationFather(PublicationPK pubPK, boolean isTreeStructureUsed, String userId,
            boolean isRightsOnTopicsUsed) {
        // fetch one of the publication fathers
        NodePK fatherPK = getPublicationFatherPK(pubPK, isTreeStructureUsed, userId, isRightsOnTopicsUsed);
        String profile = KmeliaHelper.getProfile(getUserRoles(pubPK.getInstanceId(), userId));
        return goTo(fatherPK, userId, isTreeStructureUsed, profile, isRightsOnTopicsUsed);
    }

    @Override
    public NodePK getPublicationFatherPK(PublicationPK pubPK, boolean isTreeStructureUsed, String userId,
            boolean isRightsOnTopicsUsed) {
        // fetch one of the publication fathers
        Collection<NodePK> fathers = getPublicationFathers(pubPK);
        NodePK fatherPK = new NodePK("0", pubPK); // By default --> Root
        if (fathers != null) {
            Iterator<NodePK> it = fathers.iterator();
            if (!isRightsOnTopicsUsed) {
                if (it.hasNext()) {
                    fatherPK = it.next();
                }
            } else {
                fatherPK = getAllowedFather(userId, fatherPK, it);
            }
        }
        return fatherPK;
    }

    private NodePK getAllowedFather(final String userId, NodePK fatherPK, final Iterator<NodePK> it) {
        NodeDetail allowedFather = null;
        while (allowedFather == null && it.hasNext()) {
            fatherPK = it.next();
            NodeDetail father = getNodeHeader(fatherPK);
            if (!father.haveRights() || getOrganisationController().isObjectAvailable(father.getRightsDependsOn(),
                    ObjectType.NODE, fatherPK.getInstanceId(), userId)) {
                allowedFather = father;
            }
        }
        if (allowedFather != null) {
            fatherPK = allowedFather.getNodePK();
        }
        return fatherPK;
    }

    @Override
    public Collection<NodePK> getPublicationFathers(PublicationPK pubPK) {
        try {
            Collection<NodePK> fathers = publicationService.getAllFatherPK(pubPK);
            if (CollectionUtil.isEmpty(fathers)) {
                // This publication have got no father !
                // Check if it's a clone (a clone have got no father ever)
                boolean alwaysVisibleModeActivated = StringUtil.getBooleanValue(getOrganisationController()
                        .getComponentParameterValue(pubPK.getInstanceId(), "publicationAlwaysVisible"));
                if (alwaysVisibleModeActivated) {
                    PublicationDetail publi = publicationService.getDetail(pubPK);
                    if (publi != null) {
                        boolean isClone = isClone(publi);
                        if (isClone) {
                            // This publication is a clone
                            // Get fathers from main publication
                            fathers = publicationService.getAllFatherPK(publi.getClonePK());
                        }
                    }
                }
            }
            return fathers;
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /**
     * gets a list of PublicationDetail corresponding to the links parameter
     * @param links list of publication (componentID + publicationId)
     * @return a list of PublicationDetail
     */
    @Override
    public Collection<PublicationDetail> getPublicationDetails(List<ResourceReference> links) {
        Collection<PublicationDetail> publications = null;
        List<PublicationPK> publicationPKs = new ArrayList<>();

        for (ResourceReference link : links) {
            PublicationPK pubPK = new PublicationPK(link.getId(), link.getInstanceId());
            publicationPKs.add(pubPK);
        }
        try {
            publications = publicationService.getPublications(publicationPKs);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return publications;
    }

    /**
     * gets a list of authorized publications
     * @param links list of publication defined by his id and component id
     * @param userId identifier User. allow to check if the publication is accessible for current
     * user
     * @param isRightsOnTopicsUsed indicates if the right must be checked
     * @return a collection of Kmelia publication
     * @since 1.0
     */
    @Override
    public Collection<KmeliaPublication> getPublications(List<ResourceReference> links, String userId,
            boolean isRightsOnTopicsUsed) {
        // initialization of the publications list
        List<ResourceReference> allowedPublicationIds = new ArrayList<>(links);
        if (isRightsOnTopicsUsed) {
            KmeliaAuthorization security = new KmeliaAuthorization();
            allowedPublicationIds.clear();

            // check if the publication is authorized for current user
            for (ResourceReference link : links) {
                if (security.isObjectAvailable(link.getInstanceId(), userId, link.getId(),
                        KmeliaAuthorization.PUBLICATION_TYPE)) {
                    allowedPublicationIds.add(link);
                }
            }
        }
        Collection<PublicationDetail> publications = getPublicationDetails(allowedPublicationIds);
        return pubDetails2userPubs(publications);
    }

    /**
     * Gets the publications linked with the specified one and for which the specified user is
     * authorized to access.
     * @param publication the publication from which linked publications are get.
     * @param userId the unique identifier of a user. It allows to check if a linked publication is
     * accessible for the specified user.
     * @return a list of Kmelia publications.
     * @ if an error occurs while communicating with the remote business service.
     */
    @Override
    public List<KmeliaPublication> getLinkedPublications(KmeliaPublication publication, String userId) {
        List<PublicationLink> allLinks = publication.getCompleteDetail().getLinkedPublications(userId);
        List<KmeliaPublication> authorizedLinks = new ArrayList<>();
        for (PublicationLink link : allLinks) {
            authorizedLinks.add(KmeliaPublication.aKmeliaPublicationWithPk(link.getPubPK()));
        }
        return authorizedLinks;
    }

    @Override
    public List<KmeliaPublication> getPublicationsToValidate(String componentId, String userId) {
        Collection<PublicationDetail> publications = new ArrayList<>();
        PublicationPK pubPK = new PublicationPK(USELESS, componentId);
        try {
            Collection<PublicationDetail> temp = publicationService
                    .getPublicationsByStatus(PublicationDetail.TO_VALIDATE_STATUS, pubPK);
            // only publications which must be validated by current user must be returned
            for (PublicationDetail publi : temp) {
                addPublicationsToValidate(userId, publications, publi);
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return pubDetails2userPubs(publications);
    }

    private void addPublicationsToValidate(final String userId, final Collection<PublicationDetail> publications,
            final PublicationDetail publi) {
        boolean isClone = publi.isValidationRequired() && !"-1".equals(publi.getCloneId());
        if (isClone) {
            if (isUserCanValidatePublication(publi.getPK(), userId)) {
                // publication to validate is a clone, get original one
                try {
                    PublicationDetail original = getPublicationDetail(
                            new PublicationPK(publi.getCloneId(), publi.getPK()));
                    publications.add(original);
                } catch (Exception e) {
                    // inconsistency in database! Original publication does not exist
                    SilverLogger.getLogger(this).warn("Original publication {0} of clone {1} not found",
                            publi.getId(), publi.getCloneId());
                }
            }
        } else {
            if (isUserCanValidatePublication(publi.getPK(), userId)) {
                publications.add(publi);
            }
        }
    }

    private void sendValidationNotification(final NodePK fatherPK, final PublicationDetail pubDetail,
            final String refusalMotive, final String userIdWhoRefuse) {

        try {

            UserNotificationHelper.buildAndSend(new KmeliaValidationPublicationUserNotification(fatherPK, pubDetail,
                    refusalMotive, userIdWhoRefuse));

        } catch (Exception e) {
            SilverLogger.getLogger(this).error("User notification failure about publication {0}",
                    new String[] { pubDetail.getPK().getId() }, e);
        }
    }

    private void sendAlertToSupervisors(final NodePK fatherPK, final PublicationDetail pubDetail) {
        if (pubDetail.isValid()) {
            try {
                UserNotificationHelper
                        .buildAndSend(new KmeliaSupervisorPublicationUserNotification(fatherPK, pubDetail));
            } catch (Exception e) {
                SilverLogger.getLogger(this).error("Supervisors notification failure about publication {0}",
                        new String[] { pubDetail.getPK().getId() }, e);
            }
        }
    }

    private void sendNoMoreValidatorNotification(final NodePK fatherPK, final PublicationDetail pubDetail) {
        if (pubDetail.isValidationRequired() || pubDetail.isValid()) {
            try {
                UserNotificationHelper
                        .buildAndSend(new KmeliaNoMoreValidatorPublicationUserNotification(fatherPK, pubDetail));
            } catch (Exception e) {
                SilverLogger.getLogger(this).error("fatherId = {0}, pubPK = {1}",
                        new String[] { fatherPK.getId(), pubDetail.getPK().toString() }, e);
            }
        }
    }

    private int getValidationType(String instanceId) {
        String sParam = getOrganisationController().getComponentParameterValue(instanceId,
                InstanceParameters.validation);
        if (isDefined(sParam)) {
            return Integer.parseInt(sParam);
        }
        return KmeliaHelper.VALIDATION_CLASSIC;
    }

    private boolean isTargetedValidationEnabled(String componentId) {
        int value = getValidationType(componentId);
        return value == KmeliaHelper.VALIDATION_TARGET_N || value == KmeliaHelper.VALIDATION_TARGET_1;
    }

    @Override
    public List<String> getAllValidators(PublicationPK pubPK) {
        // get all users who have to validate
        List<String> allValidators = new ArrayList<>();
        if (isTargetedValidationEnabled(pubPK.getInstanceId())) {
            allValidators = getActiveValidatorIds(pubPK);
        }
        if (allValidators.isEmpty()) {
            // It's not a targeted validation or it is but no validators has
            // been selected !
            List<String> roles = new ArrayList<>(2);
            roles.add(SilverpeasRole.admin.name());
            roles.add(SilverpeasRole.publisher.name());

            if (KmeliaHelper.isKmax(pubPK.getInstanceId())) {
                allValidators.addAll(Arrays
                        .asList(getOrganisationController().getUsersIdsByRoleNames(pubPK.getInstanceId(), roles)));
            } else {
                // get admin and publishers of all nodes where publication is
                addAdminAndPublishers(pubPK, allValidators, roles);
            }
        }
        return allValidators;
    }

    private void addAdminAndPublishers(final PublicationPK pubPK, final List<String> allValidators,
            final List<String> roles) {
        List<NodePK> nodePKs = (List<NodePK>) getPublicationFathers(pubPK);
        NodePK nodePK;
        NodeDetail node;
        boolean oneNodeIsPublic = false;
        for (int n = 0; !oneNodeIsPublic && nodePKs != null && n < nodePKs.size(); n++) {
            nodePK = nodePKs.get(n);
            node = getNodeHeader(nodePK);
            if (node != null) {
                if (!node.haveRights()) {
                    allValidators.addAll(Arrays.asList(
                            getOrganisationController().getUsersIdsByRoleNames(pubPK.getInstanceId(), roles)));
                    oneNodeIsPublic = true;
                } else {
                    allValidators.addAll(
                            Arrays.asList(getOrganisationController().getUsersIdsByRoleNames(pubPK.getInstanceId(),
                                    Integer.toString(node.getRightsDependsOn()), ObjectType.NODE, roles)));
                }
            }
        }
    }

    public void setValidators(PublicationPK pubOrClonePK, String userIds) {
        PublicationDetail publication = getPublicationDetail(pubOrClonePK);

        String[] validatorIds = StringUtil.split(userIds, ',');

        if (!ArrayUtil.isEmpty(validatorIds)) {
            // set new validators in database
            publication.setTargetValidatorId(userIds);
            publication.setStatusMustBeChecked(false);
            publication.setIndexOperation(IndexManager.NONE);
            publicationService.setDetail(publication);

            //notify them if the publication (or the clone) is in validation required state...
            if (publication.isValidationRequired()) {
                sendValidationAlert(publication, validatorIds);
            }
        }
    }

    /**
     * @param pubPK
     * @param allValidators
     * @return
     * @
     */
    private boolean isValidationComplete(PublicationPK pubPK, List<String> allValidators) {
        List<ValidationStep> steps = publicationService.getValidationSteps(pubPK);

        // get users who have already validate
        List<String> stepUserIds = new ArrayList<>();
        for (ValidationStep step : steps) {
            stepUserIds.add(step.getUserId());
        }

        // check if all users have validate
        boolean validationOK = true;
        for (int i = 0; validationOK && i < allValidators.size(); i++) {
            String validatorId = allValidators.get(i);
            validationOK = stepUserIds.contains(validatorId);
        }

        return validationOK;
    }

    @Override
    public boolean validatePublication(PublicationPK pubPK, String userId, boolean force,
            final boolean hasUserNoMoreValidationRight) {
        boolean validationComplete = false;
        try {
            CompletePublication currentPub = publicationService.getCompletePublication(pubPK);
            PublicationDetail currentPubDetail = currentPub.getPublicationDetail();
            PublicationDetail currentPubOrCloneDetail = currentPubDetail;
            boolean validationOnClone = currentPubDetail.haveGotClone();
            PublicationPK validatedPK = pubPK;
            if (validationOnClone) {
                validatedPK = currentPubDetail.getClonePK();
                currentPubOrCloneDetail = getPublicationDetail(validatedPK);
            }
            if (!hasUserNoMoreValidationRight && !isUserCanValidatePublication(validatedPK, userId)) {
                SilverLogger.getLogger(this).debug("user ''{0}'' is not allowed to validate publication {1}",
                        userId, pubPK.toString());
                return false;
            }

            String validatorUserId = userId;
            Date validationDate = new Date();

            if (force) {
                validationComplete = true;
            } else if (!hasUserNoMoreValidationRight) {
                int validationType = getValidationType(pubPK.getInstanceId());
                if (validationType == KmeliaHelper.VALIDATION_CLASSIC
                        || validationType == KmeliaHelper.VALIDATION_TARGET_1) {
                    validationComplete = true;
                } else {
                    if (validationType == KmeliaHelper.VALIDATION_TARGET_N) {
                        // check that validators are well defined
                        // If not, considering validation as classic one
                        PublicationDetail publi = publicationService.getDetail(validatedPK);
                        if (!isDefined(publi.getTargetValidatorId())) {
                            validationComplete = true;
                        }
                    }
                    if (!validationComplete) {
                        // get all users who have to validate
                        List<String> allValidators = getAllValidators(validatedPK);
                        if (allValidators.size() == 1) {
                            // special case : only once user is concerned by validation
                            validationComplete = true;
                        } else if (allValidators.size() > 1) {
                            // remove it for this user. His job is done !
                            removeTodoForPublication(validatedPK, userId);
                            if (validationOnClone) {
                                removeTodoForPublication(pubPK, userId);
                            }
                            // save his decision

                            ValidationStep validation = new ValidationStep(validatedPK, userId,
                                    PublicationDetail.VALID_STATUS);
                            publicationService.addValidationStep(validation);
                            // check if all validators have give their decision
                            validationComplete = isValidationComplete(validatedPK, allValidators);
                        }
                    }
                }

            } else {

                // User has no more validation right
                int validationType = getValidationType(pubPK.getInstanceId());

                boolean alertPublicationOwnerThereIsNoMoreValidator = false;

                switch (validationType) {
                case KmeliaHelper.VALIDATION_CLASSIC:
                case KmeliaHelper.VALIDATION_TARGET_1:
                    alertPublicationOwnerThereIsNoMoreValidator = true;
                    break;
                default:
                    // get all users who have to validate
                    List<String> allValidators = getAllValidators(validatedPK);

                    if (allValidators.isEmpty()) {
                        alertPublicationOwnerThereIsNoMoreValidator = true;
                    } else {

                        // check if all validators have give their decision
                        validationComplete = isValidationComplete(validatedPK, allValidators);
                        if (validationComplete) {

                            // taking the last effective validator for the state change.
                            validationDate = new Date(0);
                            for (ValidationStep validationStep : publicationService
                                    .getValidationSteps(validatedPK)) {
                                final String validationStepUserId = validationStep.getUserId();
                                if (!validationStepUserId.equals(userId)
                                        && validationStep.getValidationDate().compareTo(validationDate) > 0) {
                                    validationDate = validationStep.getValidationDate();
                                    validatorUserId = validationStepUserId;
                                }
                            }
                        } else if (validationType == KmeliaHelper.VALIDATION_TARGET_N
                                && StringUtil.isNotDefined(currentPubOrCloneDetail.getTargetValidatorId())) {
                            // Case of fallback solution when no more validator is defined, all publishers
                            // must validate (as collegiate method)
                            alertPublicationOwnerThereIsNoMoreValidator = true;
                        }
                    }
                }

                if (alertPublicationOwnerThereIsNoMoreValidator) {
                    Collection<NodePK> fatherPks = getPublicationFathers(currentPubDetail.getPK());
                    if (!fatherPks.isEmpty()) {
                        sendNoMoreValidatorNotification(fatherPks.iterator().next(), currentPubDetail);
                    }
                }
            }

            if (validationComplete) {
                removeAllTodosForPublication(validatedPK);
                if (validationOnClone) {
                    removeAllTodosForPublication(pubPK);
                }
                if (currentPubDetail.haveGotClone()) {
                    currentPubDetail = mergeClone(currentPub, validatorUserId, validationDate);
                } else if (currentPubDetail.isValidationRequired()) {
                    currentPubDetail.setValidatorId(validatorUserId);
                    currentPubDetail.setValidateDate(validationDate);
                    currentPubDetail.setStatus(PublicationDetail.VALID_STATUS);
                }
                KmeliaHelper.checkIndex(currentPubDetail);
                publicationService.setDetail(currentPubDetail);
                updateSilverContentVisibility(currentPubDetail);
                // index all publication's elements
                indexExternalElementsOfPublication(currentPubDetail);

                // the publication has been validated
                // all subscribers of the different topics must be alerted
                NodePK oneFather = sendSubscriptionsNotification(currentPubDetail, NotifAction.PUBLISHED, false);

                // publication's creator must be alerted
                sendValidationNotification(oneFather, currentPubDetail, null, validatorUserId);

                // alert supervisors
                sendAlertToSupervisors(oneFather, currentPubDetail);
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return validationComplete;
    }

    private PublicationDetail getClone(PublicationDetail refPub) {
        PublicationDetail clone = new PublicationDetail();
        if (refPub.getAuthor() != null) {
            clone.setAuthor(refPub.getAuthor());
        }
        if (refPub.getBeginDate() != null) {
            clone.setBeginDate(new Date(refPub.getBeginDate().getTime()));
        }
        if (refPub.getBeginHour() != null) {
            clone.setBeginHour(refPub.getBeginHour());
        }
        if (refPub.getContentPagePath() != null) {
            clone.setContentPagePath(refPub.getContentPagePath());
        }
        clone.setCreationDate(new Date(refPub.getCreationDate().getTime()));
        clone.setCreatorId(refPub.getCreatorId());
        if (refPub.getDescription() != null) {
            clone.setDescription(refPub.getDescription());
        }
        if (refPub.getEndDate() != null) {
            clone.setEndDate(new Date(refPub.getEndDate().getTime()));
        }
        if (refPub.getEndHour() != null) {
            clone.setEndHour(refPub.getEndHour());
        }
        clone.setImportance(refPub.getImportance());
        if (refPub.getInfoId() != null) {
            clone.setInfoId(refPub.getInfoId());
        }
        if (refPub.getKeywords() != null) {
            clone.setKeywords(refPub.getKeywords());
        }
        if (refPub.getName() != null) {
            clone.setName(refPub.getName());
        }
        clone.setPk(new PublicationPK(refPub.getPK().getId(), refPub.getPK().getInstanceId()));
        if (refPub.getStatus() != null) {
            clone.setStatus(refPub.getStatus());
        }
        if (refPub.getTargetValidatorId() != null) {
            clone.setTargetValidatorId(refPub.getTargetValidatorId());
        }
        if (refPub.getCloneId() != null) {
            clone.setCloneId(refPub.getCloneId());
        }
        if (refPub.getUpdateDate() != null) {
            clone.setUpdateDate(new Date(refPub.getUpdateDate().getTime()));
        }
        if (refPub.getUpdaterId() != null) {
            clone.setUpdaterId(refPub.getUpdaterId());
        }
        if (refPub.getValidateDate() != null) {
            clone.setValidateDate(new Date(refPub.getValidateDate().getTime()));
        }
        if (refPub.getValidatorId() != null) {
            clone.setValidatorId(refPub.getValidatorId());
        }
        if (refPub.getVersion() != null) {
            clone.setVersion(refPub.getVersion());
        }
        if (refPub.getLanguage() != null) {
            clone.setLanguage(refPub.getLanguage());
        }

        return clone;
    }

    /**
     * In charge of merging data from the clone with the stable one.
     * @param currentPub all the necessary data about a publication as {@link CompletePublication}.
     * @param validatorUserId the identifier of the last user validating the given publication.
     * @param validationDate the date of validation to register. Date of day is taken if null is
     * given.
     * @return the merged publication as {@link PublicationDetail}.
     * @throws FormException
     * @throws PublicationTemplateException
     * @throws AttachmentException
     */
    private PublicationDetail mergeClone(CompletePublication currentPub, String validatorUserId,
            final Date validationDate) throws FormException, PublicationTemplateException {
        PublicationDetail currentPubDetail = currentPub.getPublicationDetail();
        String memInfoId = currentPubDetail.getInfoId();
        PublicationPK pubPK = currentPubDetail.getPK();
        // merge du clone sur la publi de rfrence
        String cloneId = currentPubDetail.getCloneId();
        if (!"-1".equals(cloneId)) {
            currentPubDetail = clonePublication(cloneId, pubPK, validatorUserId, validationDate);
            // merge des fichiers joints
            ResourceReference pkFrom = new ResourceReference(pubPK.getId(), pubPK.getInstanceId());
            ResourceReference pkTo = new ResourceReference(cloneId, pubPK.getInstanceId());
            Map<String, String> attachmentIds = AttachmentServiceProvider.getAttachmentService()
                    .mergeDocuments(pkFrom, pkTo, DocumentType.attachment);
            // merge du contenu XMLModel
            String infoId = currentPubDetail.getInfoId();
            if (infoId != null && !"0".equals(infoId) && !isInteger(infoId)) {
                RecordSet set = getXMLFormFrom(infoId, pubPK);
                if (memInfoId != null && !"0".equals(memInfoId)) {
                    // il existait dj un contenu
                    set.merge(cloneId, pubPK.getInstanceId(), pubPK.getId(), pubPK.getInstanceId(), attachmentIds);
                } else {
                    // il n'y avait pas encore de contenu
                    PublicationTemplateManager publicationTemplateManager = PublicationTemplateManager
                            .getInstance();
                    publicationTemplateManager.addDynamicPublicationTemplate(pubPK.getInstanceId() + ":" + infoId,
                            infoId + ".xml");

                    set.clone(cloneId, pubPK.getInstanceId(), pubPK.getId(), pubPK.getInstanceId(), attachmentIds);
                }
            }
            // merge du contenu Wysiwyg
            boolean cloneWysiwyg = WysiwygController.haveGotWysiwyg(pubPK.getInstanceId(), cloneId,
                    currentPubDetail.getLanguage());
            if (cloneWysiwyg) {
                try {
                    // delete wysiwyg contents of public version
                    WysiwygController.deleteWysiwygAttachmentsOnly(pubPK.getInstanceId(), pubPK.getId());
                } catch (WysiwygException e) {
                    SilverLogger.getLogger(this).error(e.getMessage(), e);
                }
                // wysiwyg contents of work version become public version ones
                WysiwygController.copy(pubPK.getInstanceId(), cloneId, pubPK.getInstanceId(), pubPK.getId(),
                        currentPubDetail.getUpdaterId());
            }

            // suppression du clone
            deletePublication(new PublicationPK(cloneId, pubPK));
        }
        return currentPubDetail;
    }

    @Override
    public void unvalidatePublication(PublicationPK pubPK, String userId, String refusalMotive,
            int validationType) {
        try {
            switch (validationType) {
            case KmeliaHelper.VALIDATION_COLLEGIATE:
            case KmeliaHelper.VALIDATION_TARGET_N:
                // reset other decisions
                publicationService.removeValidationSteps(pubPK);
                break;
            case KmeliaHelper.VALIDATION_CLASSIC:
            case KmeliaHelper.VALIDATION_TARGET_1:
            default:
                break;// do nothing
            }

            PublicationDetail currentPubDetail = publicationService.getDetail(pubPK);

            if (currentPubDetail.haveGotClone()) {
                String cloneId = currentPubDetail.getCloneId();
                PublicationPK tempPK = new PublicationPK(cloneId, pubPK);
                PublicationDetail clone = publicationService.getDetail(tempPK);

                // change clone's status
                clone.setStatus("UnValidate");
                clone.setIndexOperation(IndexManager.NONE);
                publicationService.setDetail(clone);

                // Modification de la publication de reference
                currentPubDetail.setCloneStatus(PublicationDetail.REFUSED_STATUS);
                currentPubDetail.setUpdateDateMustBeSet(false);
                publicationService.setDetail(currentPubDetail);

                // we have to alert publication's last updater
                List<NodePK> fathers = (List<NodePK>) getPublicationFathers(pubPK);
                NodePK oneFather = null;
                if (fathers != null && !fathers.isEmpty()) {
                    oneFather = fathers.get(0);
                }
                sendValidationNotification(oneFather, clone, refusalMotive, userId);

                // remove tasks
                removeAllTodosForPublication(clone.getPK());
            } else {
                // change publication's status
                currentPubDetail.setStatus("UnValidate");

                KmeliaHelper.checkIndex(currentPubDetail);

                publicationService.setDetail(currentPubDetail);

                // change visibility over PDC
                updateSilverContentVisibility(currentPubDetail);

                // we have to alert publication's creator
                List<NodePK> fathers = (List<NodePK>) getPublicationFathers(pubPK);
                NodePK oneFather = null;
                if (fathers != null && !fathers.isEmpty()) {
                    oneFather = fathers.get(0);
                }
                sendValidationNotification(oneFather, currentPubDetail, refusalMotive, userId);

                //remove tasks
                removeAllTodosForPublication(currentPubDetail.getPK());
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void suspendPublication(PublicationPK pubPK, String defermentMotive, String userId) {
        try {
            PublicationDetail currentPubDetail = publicationService.getDetail(pubPK);

            // change publication's status
            currentPubDetail.setStatus(PublicationDetail.TO_VALIDATE_STATUS);

            KmeliaHelper.checkIndex(currentPubDetail);

            publicationService.setDetail(currentPubDetail);

            // change visibility over PDC
            updateSilverContentVisibility(currentPubDetail);

            unIndexExternalElementsOfPublication(currentPubDetail.getPK());

            // we have to alert publication's creator
            sendDefermentNotification(currentPubDetail, defermentMotive);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void sendDefermentNotification(final PublicationDetail pubDetail, final String defermentMotive) {
        try {
            UserNotificationHelper
                    .buildAndSend(new KmeliaDefermentPublicationUserNotification(pubDetail, defermentMotive));
        } catch (Exception e) {
            SilverLogger.getLogger(this).warn("User notification failure about publication ''{0}'' (id={1})",
                    pubDetail.getTitle(), pubDetail.getPK().getId());
        }
    }

    /**
     * Change publication status from draft to valid (for publisher) or toValidate (for redactor).
     * @param pubPK
     * @param topicPK
     * @param userProfile
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void draftOutPublication(PublicationPK pubPK, NodePK topicPK, String userProfile) {
        PublicationDetail pubDetail = draftOutPublicationWithoutNotifications(pubPK, topicPK, userProfile);
        indexExternalElementsOfPublication(pubDetail);
        sendTodosAndNotificationsOnDraftOut(pubDetail, topicPK, userProfile);
    }

    /**
     * This method is here to manage correctly transactional scope of EJB (conflict between EJB and
     * UserPreferences service)
     * @param pubPK
     * @return
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public PublicationDetail draftOutPublicationWithoutNotifications(PublicationPK pubPK, NodePK topicPK,
            String userProfile) {
        return draftOutPublication(pubPK, topicPK, userProfile, false, true);
    }

    @Override
    public PublicationDetail draftOutPublication(PublicationPK pubPK, NodePK topicPK, String userProfile,
            boolean forceUpdateDate) {
        return draftOutPublication(pubPK, topicPK, userProfile, forceUpdateDate, false);
    }

    private PublicationDetail draftOutPublication(PublicationPK pubPK, NodePK topicPK, String userProfile,
            boolean forceUpdateDate, boolean inTransaction) {
        try {
            PublicationDetail changedPublication = null;
            CompletePublication currentPub = publicationService.getCompletePublication(pubPK);
            PublicationDetail pubDetail = currentPub.getPublicationDetail();
            if (userProfile.equals("publisher") || userProfile.equals(ADMIN_ROLE)) {
                if (pubDetail.haveGotClone()) {
                    pubDetail = mergeClone(currentPub, null, null);
                }
                pubDetail.setStatus(PublicationDetail.VALID_STATUS);
                changedPublication = pubDetail;
            } else {
                if (pubDetail.haveGotClone()) {
                    // changement du statut du clone
                    PublicationDetail clone = publicationService.getDetail(pubDetail.getClonePK());
                    clone.setStatus(PublicationDetail.TO_VALIDATE_STATUS);
                    clone.setIndexOperation(IndexManager.NONE);
                    clone.setUpdateDateMustBeSet(false);
                    publicationService.setDetail(clone);
                    changedPublication = clone;
                    pubDetail.setCloneStatus(PublicationDetail.TO_VALIDATE_STATUS);
                } else {
                    pubDetail.setStatus(PublicationDetail.TO_VALIDATE_STATUS);
                    changedPublication = pubDetail;
                }
            }
            KmeliaHelper.checkIndex(pubDetail);
            publicationService.setDetail(pubDetail, forceUpdateDate);
            if (!KmeliaHelper.isKmax(pubDetail.getInstanceId())) {
                // update visibility attribute on PDC
                updateSilverContentVisibility(pubDetail);
            }
            if (!inTransaction) {
                // index all publication's elements
                indexExternalElementsOfPublication(changedPublication);
                sendTodosAndNotificationsOnDraftOut(changedPublication, topicPK, userProfile);
            }

            return changedPublication;
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void sendTodosAndNotificationsOnDraftOut(PublicationDetail pubDetail, NodePK topicPK,
            String userProfile) {
        if (SilverpeasRole.writer.isInRole(userProfile)) {
            createTodosForPublication(pubDetail, true);
        }
        // Subscriptions and supervisors are supported by kmelia and filebox only
        if (!KmeliaHelper.isKmax(pubDetail.getInstanceId())) {
            // alert subscribers
            sendSubscriptionsNotification(pubDetail, NotifAction.PUBLISHED, false);

            // alert supervisors
            if (topicPK != null) {
                sendAlertToSupervisors(topicPK, pubDetail);
            }
        }
    }

    /**
     * Change publication status from any state to draft.
     * @param pubPK
     */
    @Override
    public void draftInPublication(PublicationPK pubPK) {
        draftInPublication(pubPK, null);
    }

    @Override
    public void draftInPublication(PublicationPK pubPK, String userId) {
        try {
            PublicationDetail pubDetail = publicationService.getDetail(pubPK);
            if (pubDetail.isRefused() || pubDetail.isValid()) {
                pubDetail.setStatus(PublicationDetail.DRAFT_STATUS);
                pubDetail.setUpdaterId(userId);
                KmeliaHelper.checkIndex(pubDetail);
                publicationService.setDetail(pubDetail);
                updateSilverContentVisibility(pubDetail);
                unIndexExternalElementsOfPublication(pubDetail.getPK());
                removeAllTodosForPublication(pubPK);
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public UserNotification getUserNotification(PublicationPK pubPK, NodePK topicPK) {
        final PublicationDetail pubDetail = getPublicationDetail(pubPK);
        pubDetail.setAlias(isAlias(pubDetail, topicPK));

        return new KmeliaNotifyPublicationUserNotification(topicPK, pubDetail).build();
    }

    public boolean isAlias(PublicationDetail pubDetail, NodePK nodePK) {
        boolean result = false;
        Collection<Alias> aliases = getAlias(pubDetail.getPK());
        for (Alias alias : aliases) {
            if (!alias.getInstanceId().equals(pubDetail.getInstanceId()) && nodePK.equals(alias)) {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * @param pubPK
     * @param documentPk
     * @param topicPK
     * @return
     * @
     */
    @Override
    public UserNotification getUserNotification(PublicationPK pubPK, SimpleDocumentPK documentPk, NodePK topicPK) {
        final PublicationDetail pubDetail = getPublicationDetail(pubPK);
        // componentId of document is always the same than its publication (case of alias)
        documentPk.setComponentName(pubDetail.getInstanceId());
        final SimpleDocument document = AttachmentServiceProvider.getAttachmentService()
                .searchDocumentById(documentPk, null);
        SimpleDocument version = document.getLastPublicVersion();
        if (version == null) {
            version = document.getVersionMaster();
        }
        return new KmeliaDocumentSubscriptionPublicationUserNotification(topicPK, pubDetail, version).build();
    }

    /**
     * delete reading controls to a publication
     * @param pubPK the id of a publication
     * @since 1.0
     */
    @Override
    public void deleteAllReadingControlsByPublication(PublicationPK pubPK) {
        try {
            statisticService.deleteStats(new ResourceReference(pubPK.getId(), pubPK.getInstanceId()), PUBLICATION);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public List<HistoryObjectDetail> getLastAccess(PublicationPK pk, NodePK nodePK, String excludedUserId,
            final int maxResult) {
        final List<String> userIds = getUserIdsOfFolder(nodePK);
        final List<String> readerIds = new ArrayList<>();
        readerIds.add(excludedUserId);
        return new Pagination<HistoryObjectDetail, SilverpeasList<HistoryObjectDetail>>(
                new PaginationPage(1, maxResult))
                        .paginatedDataSource(p -> statisticService.getHistoryByAction(new ResourceReference(pk), 1,
                                PUBLICATION, readerIds, p.originalSizeIsNotRequired()))
                        .filter(r -> {
                            final SilverpeasList<HistoryObjectDetail> currentLastAccess = new SilverpeasArrayList<>();
                            for (HistoryObjectDetail access : r) {
                                final String readerId = access.getUserId();
                                if ((CollectionUtil.isEmpty(userIds) || userIds.contains(readerId))
                                        && !readerIds.contains(readerId)) {
                                    readerIds.add(readerId);
                                    if (!User.getById(readerId).isAnonymous()) {
                                        currentLastAccess.add(access);
                                    }
                                }
                            }
                            return currentLastAccess;
                        }).execute();
    }

    @Override
    public List<String> getUserIdsOfFolder(NodePK pk) {
        if (!isRightsOnTopicsEnabled(pk.getInstanceId())) {
            return Collections.emptyList();
        }

        NodeDetail node = getNodeHeader(pk);

        // check if we have to take care of topic's rights
        if (node != null && node.haveRights()) {
            int rightsDependsOn = node.getRightsDependsOn();
            List<String> profileNames = new ArrayList<>(4);
            profileNames.add(KmeliaHelper.ROLE_ADMIN);
            profileNames.add(KmeliaHelper.ROLE_PUBLISHER);
            profileNames.add(KmeliaHelper.ROLE_WRITER);
            profileNames.add(KmeliaHelper.ROLE_READER);
            String[] userIds = getOrganisationController().getUsersIdsByRoleNames(pk.getInstanceId(),
                    Integer.toString(rightsDependsOn), ObjectType.NODE, profileNames);
            return Arrays.asList(userIds);
        } else {
            return Collections.emptyList();
        }
    }

    @Override
    public void indexKmelia(String componentId) {
        indexTopics(new NodePK(USELESS, componentId));
        indexPublications(new PublicationPK(USELESS, componentId));
    }

    private void indexPublications(final PublicationPK pubPK) {
        final Collection<PublicationDetail> pubs;
        try {
            pubs = publicationService.getAllPublications(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }

        if (pubs != null) {
            for (PublicationDetail pub : pubs) {
                processPublicationIndexation(pub);
            }
        }
    }

    private void processPublicationIndexation(final PublicationDetail pub) {
        PublicationPK pk = pub.getPK();
        try {
            // index only valid publications
            if (pub.getStatus() != null && pub.isValid()) {
                List<NodePK> pubFathers = (List<NodePK>) publicationService.getAllFatherPK(pk);
                // index only valid publications which are not only in
                // dz or basket
                if (pubFathers.size() >= 2) {
                    indexPublication(pub);
                } else if (pubFathers.size() == 1) {
                    NodePK nodePK = pubFathers.get(0);
                    // index the valid publication if it is not in the
                    // basket
                    if (!nodePK.isTrash()) {
                        indexPublication(pub);
                    }
                } else {
                    // don't index publications in the dz
                }
            }
        } catch (Exception e) {
            SilverLogger.getLogger(this).error("Error during indexation of publication {0}", pk.getId(), e);
        }
    }

    private void indexPublication(PublicationDetail pub) {
        // index publication itself
        publicationService.createIndex(pub.getPK());

        // index external elements
        indexExternalElementsOfPublication(pub);
    }

    private void indexTopics(NodePK nodePK) {
        try {
            Collection<NodeDetail> nodes = nodeService.getAllNodes(nodePK);
            if (nodes != null) {
                for (NodeDetail node : nodes) {
                    if (!node.getNodePK().isRoot() && !node.getNodePK().isTrash()
                            && !node.getNodePK().getId().equals("2")) {
                        nodeService.createIndex(node);
                    }
                }
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    /*
     * Creates todos for all publishers of this kmelia instance
     * @param pubDetail publication to be validated
     * @param creation true if it's the creation of the publi
     */
    private void createTodosForPublication(PublicationDetail pubDetail, boolean creation) {
        if (!creation) {
            /* remove all todos attached to that publication */
            removeAllTodosForPublication(pubDetail.getPK());
        }
        if (pubDetail.isValidationRequired()
                || PublicationDetail.TO_VALIDATE_STATUS.equalsIgnoreCase(pubDetail.getCloneStatus())) {
            int validationType = getValidationType(pubDetail.getPK().getInstanceId());
            if (validationType == KmeliaHelper.VALIDATION_TARGET_N
                    || validationType == KmeliaHelper.VALIDATION_COLLEGIATE) {
                // removing potential older validation decision
                publicationService.removeValidationSteps(pubDetail.getPK());
            }
            List<String> validators = getAllValidators(pubDetail.getPK());
            addTodoAndSendNotificationToValidators(pubDetail, validators);
        }
    }

    private void addTodoAndSendNotificationToValidators(PublicationDetail pub, List<String> validators) {
        if (validators != null && !validators.isEmpty()) {
            String[] users = validators.toArray(new String[validators.size()]);
            addTodo(pub, users);
            // Send a notification to alert admins and publishers
            sendValidationAlert(pub, users);
        }
    }

    private String addTodo(PublicationDetail pubDetail, String[] users) {
        LocalizationBundle message = ResourceLocator.getLocalizationBundle(MESSAGES_PATH);

        TodoDetail todo = new TodoDetail();

        todo.setId(pubDetail.getPK().getId());
        todo.setSpaceId(pubDetail.getPK().getSpace());
        todo.setComponentId(pubDetail.getPK().getComponentName());
        todo.setName(message.getString("ToValidateShort") + " : " + pubDetail.getName());

        List<Attendee> attendees = new ArrayList<>();
        for (String user : users) {
            if (user != null) {
                attendees.add(new Attendee(user));
            }
        }
        todo.setAttendees(new ArrayList<>(attendees));
        if (isDefined(pubDetail.getUpdaterId())) {
            todo.setDelegatorId(pubDetail.getUpdaterId());
        } else {
            todo.setDelegatorId(pubDetail.getCreatorId());
        }
        todo.setExternalId(pubDetail.getPK().getId());

        return calendar.addToDo(todo);
    }

    /*
     * Remove todos for all pubishers of this kmelia instance
     * @param pubDetail corresponding publication
     */
    private void removeAllTodosForPublication(PublicationPK pubPK) {
        calendar.removeToDoFromExternal(USELESS, pubPK.getInstanceId(), pubPK.getId());
    }

    private void removeTodoForPublication(PublicationPK pubPK, List<String> userIds) {
        if (userIds != null) {
            for (String userId : userIds) {
                removeTodoForPublication(pubPK, userId);
            }
        }
    }

    private void removeTodoForPublication(PublicationPK pubPK, String userId) {
        calendar.removeAttendeeInToDoFromExternal(pubPK.getInstanceId(), pubPK.getId(), userId);
    }

    private void sendValidationAlert(final PublicationDetail pubDetail, final String[] users) {
        UserNotificationHelper
                .buildAndSend(new KmeliaPendingValidationPublicationUserNotification(pubDetail, users));
    }

    private void sendModificationAlert(final int modificationScope, final PublicationDetail pubDetail) {
        UserNotificationHelper
                .buildAndSend(new KmeliaModificationPublicationUserNotification(pubDetail, modificationScope));
    }

    @Override
    public void sendModificationAlert(int modificationScope, PublicationPK pubPK) {
        sendModificationAlert(modificationScope, getPublicationDetail(pubPK));
    }

    @Override
    public int getSilverObjectId(PublicationPK pubPK) {
        int silverObjectId = -1;
        PublicationDetail pubDetail;
        try {
            silverObjectId = kmeliaContentManager.getSilverContentId(pubPK.getId(), pubPK.getInstanceId());
            if (silverObjectId == -1) {
                pubDetail = getPublicationDetail(pubPK);
                silverObjectId = createSilverContent(pubDetail, pubDetail.getCreatorId());
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return silverObjectId;
    }

    private int createSilverContent(PublicationDetail pubDetail, String creatorId) {
        Connection con = null;
        try {
            con = getConnection();
            return kmeliaContentManager.createSilverContent(con, pubDetail, creatorId);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            freeConnection(con);
        }
    }

    @Override
    public void deleteSilverContent(PublicationPK pubPK) {
        Connection con = getConnection();
        try {
            kmeliaContentManager.deleteSilverContent(con, pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            freeConnection(con);
        }
    }

    private void updateSilverContentVisibility(PublicationDetail pubDetail) {
        try {
            kmeliaContentManager.updateSilverContentVisibility(pubDetail);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void updateSilverContentVisibility(PublicationPK pubPK, boolean isVisible) {
        PublicationDetail pubDetail = getPublicationDetail(pubPK);
        try {
            kmeliaContentManager.updateSilverContentVisibility(pubDetail, isVisible);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void indexExternalElementsOfPublication(PublicationDetail pubDetail) {
        if (KmeliaHelper.isIndexable(pubDetail)) {
            try {
                // index all files except Wysiwyg which are already indexed as publication content
                List<SimpleDocument> documents = AttachmentServiceProvider.getAttachmentService()
                        .listAllDocumentsByForeignKey(pubDetail.getPK().toResourceReference(), null);
                for (SimpleDocument doc : documents) {
                    if (doc.getDocumentType() != DocumentType.wysiwyg) {
                        AttachmentServiceProvider.getAttachmentService().createIndex(doc, pubDetail.getBeginDate(),
                                pubDetail.getEndDate());
                    }
                }
            } catch (Exception e) {
                SilverLogger.getLogger(this).error("Indexing versioning documents failed for publication {0}",
                        new String[] { pubDetail.getPK().getId() }, e);
            }
            try {
                // index comments
                getCommentService().indexAllCommentsOnPublication(pubDetail.getContributionType(),
                        pubDetail.getPK());
            } catch (Exception e) {
                SilverLogger.getLogger(this).error("Indexing comments failed for publication {0}",
                        new String[] { pubDetail.getPK().getId() }, e);
            }
        }
    }

    private void unIndexExternalElementsOfPublication(PublicationPK pubPK) {
        try {
            AttachmentServiceProvider.getAttachmentService()
                    .unindexAttachmentsOfExternalObject(pubPK.toResourceReference());
        } catch (Exception e) {
            SilverLogger.getLogger(this).error("Unindexing versioning documents failed for publication {0}",
                    new String[] { pubPK.getId() }, e);
        }
        try {
            // index comments
            getCommentService().unindexAllCommentsOnPublication(PublicationDetail.getResourceType(), pubPK);
        } catch (Exception e) {
            SilverLogger.getLogger(this).error("Unindexing versioning documents failed for publication {0}",
                    new String[] { pubPK.getId() }, e);
        }
    }

    @Override
    public void removeContentOfPublication(PublicationPK pubPK) {
        // remove XML content
        PublicationDetail publication = getPublicationDetail(pubPK);
        if (!StringUtil.isInteger(publication.getInfoId())) {
            removeXMLContentOfPublication(pubPK);
        }
        // reset reference to content
        publication.setInfoId("0");
        updatePublication(publication, KmeliaHelper.PUBLICATION_CONTENT, true);
    }

    private void removeXMLContentOfPublication(PublicationPK pubPK) {
        try {
            PublicationDetail pubDetail = getPublicationDetail(pubPK);
            String infoId = pubDetail.getInfoId();
            if (!isInteger(infoId)) {
                String xmlFormShortName = infoId;

                PublicationTemplate pubTemplate = PublicationTemplateManager.getInstance()
                        .getPublicationTemplate(pubDetail.getPK().getInstanceId() + ":" + xmlFormShortName);

                RecordSet set = pubTemplate.getRecordSet();
                set.delete(pubDetail.getPK().getId());
            }
        } catch (PublicationTemplateException | FormException e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private Connection getConnection() {
        try {
            return DBUtil.openConnection();
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    private void freeConnection(Connection con) {
        if (con != null) {
            try {
                con.close();
            } catch (Exception e) {
                SilverLogger.getLogger(this).error("Connection freed failure", e);
            }
        }
    }

    @Override
    public void setModelUsed(String[] models, String instanceId, String nodeId) {
        Connection con = getConnection();
        try {
            ModelDAO.deleteModel(con, instanceId, nodeId);
            if (models != null) {
                for (String modelId : models) {
                    ModelDAO.addModel(con, instanceId, modelId, nodeId);
                }
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            // fermer la connexion
            freeConnection(con);
        }
    }

    @Override
    public Collection<String> getModelUsed(String instanceId, String nodeId) {
        Connection con = getConnection();
        try {
            // get templates defined for the given node
            Collection<String> result = ModelDAO.getModelUsed(con, instanceId, nodeId);
            if (isDefined(nodeId) && result.isEmpty()) {
                // there is no templates defined for the given node, check the parent nodes
                Collection<NodeDetail> parents = nodeService.getPath(new NodePK(nodeId, instanceId));
                Iterator<NodeDetail> iter = parents.iterator();
                while (iter.hasNext() && result.isEmpty()) {
                    NodeDetail parent = iter.next();
                    result = ModelDAO.getModelUsed(con, instanceId, parent.getNodePK().getId());
                }
            }
            return result;
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            // fermer la connexion
            freeConnection(con);
        }
    }

    /**
     * Copy model used from a node to an other one.
     * @param from the node the models used are linked to.
     * @param to the node the models must be copied.
     */
    private void copyUsedModel(NodePK from, NodePK to) {
        Connection con = getConnection();
        try {
            // get templates defined for the 'from' node
            Collection<String> modelIds = ModelDAO.getModelUsed(con, from.getInstanceId(), from.getId());
            for (String modelId : modelIds) {
                // set template to 'to' node
                ModelDAO.addModel(con, to.getInstanceId(), modelId, to.getId());
            }
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            freeConnection(con);
        }
    }

    @Override
    public List<NodeDetail> getAxis(String componentId) {
        SettingBundle nodeSettings = ResourceLocator.getSettingBundle("org.silverpeas.node.nodeSettings");
        String sortField = nodeSettings.getString("sortField", "nodepath");
        String sortOrder = nodeSettings.getString("sortOrder", "asc");
        List<NodeDetail> axis = new ArrayList<>();
        try {
            List<NodeDetail> headers = getAxisHeaders(componentId);
            for (NodeDetail header : headers) {
                // Do not get hidden nodes (Basket and unclassified)
                if (!NodeDetail.STATUS_INVISIBLE.equals(header.getStatus())) {
                    // get content  of  this axis
                    axis.addAll(nodeService.getSubTree(header.getNodePK(), sortField + " " + sortOrder));
                }
            }
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return axis;
    }

    @Override
    public List<NodeDetail> getAxisHeaders(String componentId) {
        List<NodeDetail> axisHeaders;
        try {
            axisHeaders = nodeService.getHeadersByLevel(new NodePK(USELESS, componentId), 2);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return axisHeaders;
    }

    @Override
    public NodePK addAxis(NodeDetail axis, String componentId) {
        NodePK axisPK = new NodePK("toDefine", componentId);
        NodeDetail rootDetail = new NodeDetail("0", "Root", "desc", 1, "-1");
        rootDetail.setCreationDate(UNKNOWN);
        rootDetail.setCreatorId(UNKNOWN);
        rootDetail.setPath("/0");
        rootDetail.setStatus(NodeDetail.STATUS_VISIBLE);
        axis.setNodePK(axisPK);
        CoordinatePK coordinatePK = new CoordinatePK(USELESS, axisPK);
        try {
            // axis creation
            axisPK = nodeService.createNode(axis, rootDetail);
            // add this new axis to existing coordinates
            CoordinatePoint point = new CoordinatePoint(-1, Integer.parseInt(axisPK.getId()), true);
            coordinatesService.addPointToAllCoordinates(coordinatePK, point);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return axisPK;
    }

    @Override
    public void updateAxis(NodeDetail axis, String componentId) {
        axis.getNodePK().setComponentName(componentId);
        try {
            nodeService.setDetail(axis);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public void deleteAxis(String axisId, String componentId) {
        NodePK pkToDelete = new NodePK(axisId, componentId);
        PublicationPK pubPK = new PublicationPK(USELESS);

        CoordinatePK coordinatePK = new CoordinatePK(USELESS, pkToDelete);
        List<String> fatherIds = new ArrayList<>();
        Collection<String> coordinateIds;
        // Delete the axis
        try {
            // delete publicationFathers
            if (getAxisHeaders(componentId).size() == 1) {
                coordinateIds = coordinatesService.getCoordinateIdsByNodeId(coordinatePK, axisId);
                Iterator<String> coordinateIdsIt = coordinateIds.iterator();
                String coordinateId;
                while (coordinateIdsIt.hasNext()) {
                    coordinateId = coordinateIdsIt.next();
                    fatherIds.add(coordinateId);
                }
                if (!fatherIds.isEmpty()) {
                    publicationService.removeFathers(pubPK, fatherIds);
                }
            }
            // delete coordinate which contains subComponents of this component
            Collection<NodeDetail> subComponents = nodeService.getDescendantDetails(pkToDelete);
            Iterator<NodeDetail> it = subComponents.iterator();
            List<NodePK> points = new ArrayList<>();
            points.add(pkToDelete);
            while (it.hasNext()) {
                points.add((it.next()).getNodePK());
            }
            removeCoordinatesByPoints(points, componentId);
            // delete axis
            nodeService.removeNode(pkToDelete);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    private void removeCoordinatesByPoints(List<NodePK> nodePKs, String componentId) {
        Iterator<NodePK> it = nodePKs.iterator();
        List<String> coordinatePoints = new ArrayList<>();
        String nodeId;
        while (it.hasNext()) {
            nodeId = (it.next()).getId();
            coordinatePoints.add(nodeId);
        }
        CoordinatePK coordinatePK = new CoordinatePK(USELESS, USELESS, componentId);
        try {
            coordinatesService.deleteCoordinatesByPoints(coordinatePK, coordinatePoints);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public NodeDetail getNodeHeader(String id, String componentId) {
        NodePK pk = new NodePK(id, componentId);
        return getNodeHeader(pk);
    }

    private NodeDetail getNodeHeader(NodePK pk) {
        NodeDetail nodeDetail;
        try {
            nodeDetail = nodeService.getHeader(pk);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return nodeDetail;
    }

    @Override
    public NodePK addPosition(String fatherId, NodeDetail position, String componentId, String userId) {
        position.getNodePK().setComponentName(componentId);
        position.setCreationDate(DateUtil.today2SQLDate());
        position.setCreatorId(userId);
        NodeDetail fatherDetail;
        NodePK componentPK = null;

        fatherDetail = getNodeHeader(fatherId, componentId);
        try {
            componentPK = nodeService.createNode(position, fatherDetail);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return componentPK;
    }

    @Override
    public void updatePosition(NodeDetail position, String componentId) {
        position.getNodePK().setComponentName(componentId);
        try {
            nodeService.setDetail(position);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public void deletePosition(String positionId, String componentId) {
        NodePK pkToDelete = new NodePK(positionId, componentId);
        // Delete the axis
        try {
            // delete coordinate which contains subPositions of this position
            Collection<NodeDetail> subComponents = nodeService.getDescendantDetails(pkToDelete);
            Iterator<NodeDetail> it = subComponents.iterator();
            List<NodePK> points = new ArrayList<>();
            points.add(pkToDelete);
            while (it.hasNext()) {
                points.add((it.next()).getNodePK());
            }
            removeCoordinatesByPoints(points, componentId);
            // delete component
            nodeService.removeNode(pkToDelete);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public Collection<NodeDetail> getPath(String id, String componentId) {
        Collection<NodeDetail> newPath = new ArrayList<>();
        NodePK nodePK = new NodePK(id, componentId);
        // compute path from a to z

        try {
            List<NodeDetail> pathInReverse = nodeService.getPath(nodePK);
            // reverse the path from root to leaf
            for (int i = pathInReverse.size() - 1; i >= 0; i--) {
                newPath.add(pathInReverse.get(i));
            }
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return newPath;
    }

    public Collection<Coordinate> getKmaxPathList(PublicationPK pubPK) {
        Collection<Coordinate> coordinates = null;
        try {
            coordinates = getPublicationCoordinates(pubPK.getId(), pubPK.getInstanceId());
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
        return coordinates;
    }

    @Override
    public List<KmeliaPublication> search(List<String> combination, String componentId) {
        Collection<PublicationDetail> publications = searchPublications(combination, componentId);
        if (publications == null) {
            return new ArrayList<>();
        }
        return pubDetails2userPubs(publications);
    }

    @Override
    public List<KmeliaPublication> search(List<String> combination, int nbDays, String componentId) {
        Collection<PublicationDetail> publications = searchPublications(combination, componentId);
        return pubDetails2userPubs(filterPublicationsByBeginDate(publications, nbDays));
    }

    private Collection<PublicationDetail> searchPublications(List<String> combination, String componentId) {
        PublicationPK pk = new PublicationPK(USELESS, componentId);
        CoordinatePK coordinatePK = new CoordinatePK(UNKNOWN, pk);
        Collection<PublicationDetail> publications = null;
        Collection<String> coordinates = null;
        try {
            // Remove node "Toutes catgories" (level == 2) from combination
            int nodeLevel;
            String axisValue;
            int i = 0;
            while (i < combination.size()) {
                axisValue = combination.get(i);
                StringTokenizer st = new StringTokenizer(axisValue, "/");
                nodeLevel = st.countTokens();
                // if node is level 2, it represents "Toutes Catgories"
                // this axis is not used by the search
                if (nodeLevel == 2) {
                    combination.remove(i);
                    i--;
                }
                i++;
            }
            if (combination.isEmpty()) {
                // all criterias is "Toutes Catgories"
                // get all publications classified
                NodePK basketPK = new NodePK("1", componentId);
                publications = publicationService.getDetailsNotInFatherPK(basketPK);
            } else {
                coordinates = coordinatesService.getCoordinatesByFatherPaths((ArrayList<String>) combination,
                        coordinatePK);
                if (coordinates != null && !coordinates.isEmpty()) {
                    publications = publicationService.getDetailsByFatherIds((ArrayList<String>) coordinates, pk,
                            false);
                }
            }
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return publications;
    }

    @Override
    public Collection<KmeliaPublication> getUnbalancedPublications(String componentId) {
        PublicationPK pk = new PublicationPK(USELESS, componentId);
        Collection<PublicationDetail> publications = null;
        try {
            publications = publicationService.getOrphanPublications(pk);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return pubDetails2userPubs(publications);
    }

    private Collection<PublicationDetail> filterPublicationsByBeginDate(Collection<PublicationDetail> publications,
            int nbDays) {
        List<PublicationDetail> pubOK = new ArrayList<>();
        if (publications != null) {
            Calendar rightNow = Calendar.getInstance();
            if (nbDays == 0) {
                nbDays = 1;
            }
            rightNow.add(Calendar.DATE, 0 - nbDays);
            Date day = rightNow.getTime();
            Iterator<PublicationDetail> it = publications.iterator();
            PublicationDetail pub;
            Date dateToCompare;
            while (it.hasNext()) {
                pub = it.next();
                if (pub.getBeginDate() != null) {
                    dateToCompare = pub.getBeginDate();
                } else {
                    dateToCompare = pub.getCreationDate();
                }

                if (dateToCompare.compareTo(day) >= 0) {
                    pubOK.add(pub);
                }
            }
        }
        return pubOK;
    }

    @Override
    public void indexKmax(String componentId) {
        indexAxis(componentId);
        indexPublications(new PublicationPK(USELESS, componentId));
    }

    private void indexAxis(String componentId) {
        NodePK nodePK = new NodePK(USELESS, componentId);
        try {
            Collection<NodeDetail> nodes = nodeService.getAllNodes(nodePK);
            if (nodes != null) {
                for (NodeDetail nodeDetail : nodes) {
                    if ("corbeille".equalsIgnoreCase(nodeDetail.getName()) && nodeDetail.getNodePK().isTrash()) {
                        // do not index the bin
                    } else {
                        nodeService.createIndex(nodeDetail);
                    }
                }
            }
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public KmeliaPublication getKmaxPublication(String pubId, String currentUserId) {
        PublicationPK pubPK;
        CompletePublication completePublication = null;

        try {
            pubPK = new PublicationPK(pubId);
            completePublication = publicationService.getCompletePublication(pubPK);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
        return KmeliaPublication.aKmeliaPublicationFromCompleteDetail(completePublication);
    }

    @Override
    public Collection<Coordinate> getPublicationCoordinates(String pubId, String componentId) {
        try {
            return publicationService.getCoordinates(pubId, componentId);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    @Override
    public void addPublicationToCombination(String pubId, List<String> combination, String componentId) {
        PublicationPK pubPK = new PublicationPK(pubId, componentId);
        CoordinatePK coordinatePK = new CoordinatePK(UNKNOWN, pubPK);
        try {

            Collection<Coordinate> coordinates = getPublicationCoordinates(pubId, componentId);

            if (!checkCombination(coordinates, combination)) {
                return;
            }

            NodeDetail nodeDetail;
            // enrich combination by get ancestors
            Iterator<String> it = combination.iterator();
            List<CoordinatePoint> allnodes = new ArrayList<>();
            int i = 1;
            while (it.hasNext()) {
                String nodeId = it.next();
                NodePK nodePK = new NodePK(nodeId, componentId);
                Collection<NodeDetail> path = nodeService.getPath(nodePK);
                for (NodeDetail aPath : path) {
                    nodeDetail = aPath;
                    String anscestorId = nodeDetail.getNodePK().getId();
                    int nodeLevel = nodeDetail.getLevel();
                    if (!nodeDetail.getNodePK().isRoot()) {
                        CoordinatePoint point;
                        if (anscestorId.equals(nodeId)) {
                            point = new CoordinatePoint(-1, Integer.parseInt(anscestorId), true, nodeLevel, i);
                        } else {
                            point = new CoordinatePoint(-1, Integer.parseInt(anscestorId), false, nodeLevel, i);
                        }
                        allnodes.add(point);
                    }
                }
                i++;
            }
            int coordinateId = coordinatesService.addCoordinate(coordinatePK, allnodes);
            publicationService.addFather(pubPK, new NodePK(String.valueOf(coordinateId), pubPK));
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    protected boolean checkCombination(Collection<Coordinate> coordinates, List<String> combination) {
        for (Coordinate coordinate : coordinates) {
            Collection<CoordinatePoint> points = coordinate.getCoordinatePoints();
            if (points.isEmpty()) {
                continue;
            }

            boolean matchFound = false;

            for (CoordinatePoint point : points) {
                if (!checkPoint(point, combination)) {
                    matchFound = false;
                    break;
                }
                matchFound = true;
            }

            if (matchFound) {
                return false;
            }
        }
        return true;
    }

    protected boolean checkPoint(CoordinatePoint point, List<String> combination) {
        for (String intVal : combination) {
            if (Integer.parseInt(intVal) == point.getNodeId()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void deleteCoordinates(CoordinatePK coordinatePK, List<String> coordinates) {
        coordinatesService.deleteCoordinates(coordinatePK, coordinates);
    }

    @Override
    public void deletePublicationFromCombination(String pubId, String combinationId, String componentId) {
        PublicationPK pubPK = new PublicationPK(pubId, componentId);
        NodePK fatherPK = new NodePK(combinationId, componentId);
        CoordinatePK coordinatePK = new CoordinatePK(combinationId, pubPK);
        try {
            // remove publication fathers
            publicationService.removeFather(pubPK, fatherPK);
            // remove coordinate
            List<String> coordinateIds = new ArrayList<>(1);
            coordinateIds.add(combinationId);
            coordinatesService.deleteCoordinates(coordinatePK, coordinateIds);
        } catch (Exception e) {
            throw new KmaxRuntimeException(e);
        }
    }

    /**
     * Create a new Publication (only the header - parameters)
     * @param pubDetail a PublicationDetail
     * @return the id of the new publication
     * @see org.silverpeas.core.contribution.publication.model.PublicationDetail
     * @since 1.0
     */
    @Override
    public String createKmaxPublication(PublicationDetail pubDetail) {
        PublicationPK pubPK = null;
        Connection con = getConnection();
        try {
            // create the publication
            pubDetail = changePublicationStatusOnCreation(pubDetail, new NodePK(USELESS, pubDetail.getPK()));
            pubPK = publicationService.createPublication(pubDetail);
            pubDetail.getPK().setId(pubPK.getId());

            // creates todos for publishers
            this.createTodosForPublication(pubDetail, true);

            // register the new publication as a new content to content manager
            createSilverContent(pubDetail, pubDetail.getCreatorId());
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        } finally {
            freeConnection(con);
        }
        return pubPK.getId();
    }

    @Override
    public Collection<Alias> getAlias(PublicationPK pubPK) {
        try {
            return publicationService.getAlias(pubPK);
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public void setAlias(PublicationPK pubPK, List<Alias> alias) {

        publicationService.setAlias(pubPK, alias);

        // Send subscriptions to aliases subscribers
        PublicationDetail pubDetail = getPublicationDetail(pubPK);
        sendSubscriptionsNotification(pubDetail, NotifAction.PUBLISHED, true);
    }

    @Override
    @Transactional(Transactional.TxType.NOT_SUPPORTED)
    public void addAttachmentToPublication(PublicationPK pubPK, String userId, String filename, String description,
            byte[] contents) {
        try {
            Date creationDate = new Date();
            SimpleAttachment file = new SimpleAttachment(FileUtil.getFilename(filename), I18NHelper.defaultLanguage,
                    filename, "", contents.length, FileUtil.getMimeType(filename), userId, creationDate, null);
            boolean versioningActive = getBooleanValue(
                    getOrganisationController().getComponentParameterValue(pubPK.getComponentName(), VERSION_MODE));
            SimpleDocument document;
            if (versioningActive) {
                document = new HistorisedDocument(new SimpleDocumentPK(null, pubPK.getComponentName()),
                        pubPK.getId(), 0, file);
                document.setPublicDocument(true);
                document.setDocumentType(DocumentType.attachment);
            } else {
                document = new SimpleDocument(new SimpleDocumentPK(null, pubPK.getComponentName()), pubPK.getId(),
                        0, false, file);
            }
            AttachmentServiceProvider.getAttachmentService().createAttachment(document,
                    new ByteArrayInputStream(contents));
        } catch (org.silverpeas.core.contribution.attachment.AttachmentException fnfe) {
            throw new KmeliaRuntimeException(fnfe);
        }
    }

    /**
     * Creates or updates a publication.
     * @param componentId The id of the component containing the publication.
     * @param topicId The id of the topic containing the publication.
     * @param spaceId The id of the space containing the publication.
     * @param userId The id of the user creating or updating the publication.
     * @param publiParams The publication's parameters.
     * @param formParams The parameters of the publication's form.
     * @param language The language of the publication.
     * @param xmlFormName The name of the publication's form.
     * @param discrimatingParameterName The name of the field included in the form which allowes to
     * retrieve the eventually existing publication to update.
     * @param userProfile The user's profile used to draft out the publication.
     * @return True if the publication is created, false if it is updated.
     * @
     */
    @Override
    public boolean importPublication(String componentId, String topicId, String spaceId, String userId,
            Map<String, String> publiParams, Map<String, String> formParams, String language, String xmlFormName,
            String discrimatingParameterName, String userProfile) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, spaceId, userId);
        return publicationImport.importPublication(publiParams, formParams, language, xmlFormName,
                discrimatingParameterName, userProfile);
    }

    @Override
    public boolean importPublication(String componentId, String topicId, String userId,
            Map<String, String> publiParams, Map<String, String> formParams, String language, String xmlFormName,
            String discriminantParameterName, String userProfile, boolean ignoreMissingFormFields) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, null, userId);
        publicationImport.setIgnoreMissingFormFields(ignoreMissingFormFields);
        return publicationImport.importPublication(publiParams, formParams, language, xmlFormName,
                discriminantParameterName, userProfile);
    }

    /**
     * Creates or updates a publication.
     * @param publicationId The id of the publication to update.
     * @param componentId The id of the component containing the publication.
     * @param topicId The id of the topic containing the publication.
     * @param spaceId The id of the space containing the publication.
     * @param userId The id of the user creating or updating the publication.
     * @param publiParams The publication's parameters.
     * @param formParams The parameters of the publication's form.
     * @param language The language of the publication.
     * @param xmlFormName The name of the publication's form.
     * @param userProfile The user's profile used to draft out the publication.
     * @return True if the publication is created, false if it is updated.
     * @
     */
    @Override
    public boolean importPublication(String publicationId, String componentId, String topicId, String spaceId,
            String userId, Map<String, String> publiParams, Map<String, String> formParams, String language,
            String xmlFormName, String userProfile) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, spaceId, userId);
        return publicationImport.importPublication(publicationId, publiParams, formParams, language, xmlFormName,
                userProfile);
    }

    @Override
    public void importPublications(String componentId, String topicId, String spaceId, String userId,
            List<Map<String, String>> publiParamsList, List<Map<String, String>> formParamsList, String language,
            String xmlFormName, String discrimatingParameterName, String userProfile) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, spaceId, userId);
        publicationImport.importPublications(publiParamsList, formParamsList, language, xmlFormName,
                discrimatingParameterName, userProfile);
    }

    @Override
    public List<XMLField> getPublicationXmlFields(String publicationId, String componentId, String spaceId,
            String userId) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, null, spaceId, userId);
        return publicationImport.getPublicationXmlFields(publicationId);
    }

    @Override
    public List<XMLField> getPublicationXmlFields(String publicationId, String componentId, String spaceId,
            String userId, String language) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, null, spaceId, userId);
        return publicationImport.getPublicationXmlFields(publicationId, language);
    }

    @Override
    public String createTopic(String componentId, String topicId, String spaceId, String userId, String name,
            String description) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, spaceId, userId);
        return publicationImport.createTopic(name, description);
    }

    @Override
    public Collection<String> getPublicationsSpecificValues(String componentId, String xmlFormName,
            String fieldName) {
        PublicationImport publicationImport = new PublicationImport(this, componentId);
        return publicationImport.getPublicationsSpecificValues(componentId, xmlFormName, fieldName);
    }

    @Override
    public void draftInPublication(String componentId, String xmlFormName, String fieldName, String fieldValue) {
        PublicationImport publicationImport = new PublicationImport(this, componentId);
        publicationImport.draftInPublication(xmlFormName, fieldName, fieldValue);
    }

    @Override
    public void updatePublicationEndDate(String componentId, String spaceId, String userId, String xmlFormName,
            String fieldName, String fieldValue, Date endDate) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, null, spaceId, userId);
        publicationImport.updatePublicationEndDate(xmlFormName, fieldName, fieldValue, endDate);
    }

    /**
     * Find a publication imported only by a xml field (old id for example)
     * @param componentId
     * @param xmlFormName
     * @param fieldName
     * @param fieldValue
     * @param topicId
     * @param spaceId
     * @param userId
     * @return pubId
     * @
     */
    @Override
    public String findPublicationIdBySpecificValue(String componentId, String xmlFormName, String fieldName,
            String fieldValue, String topicId, String spaceId, String userId) {
        PublicationImport publicationImport = new PublicationImport(this, componentId, topicId, spaceId, userId);
        return publicationImport.getPublicationId(xmlFormName, fieldName, fieldValue);
    }

    @Override
    public void doAutomaticDraftOut() {
        // get all clones with draftoutdate <= current date
        // pubCloneId <> -1 AND pubCloneStatus == 'Draft'
        Collection<PublicationDetail> pubs = publicationService.getPublicationsToDraftOut(true);
        // for each clone, call draftOutPublication method
        for (PublicationDetail pub : pubs) {
            draftOutPublication(pub.getClonePK(), null, ADMIN_ROLE, true);
        }
    }

    @Override
    public String clonePublication(CompletePublication refPubComplete, PublicationDetail pubDetail,
            String nextStatus) {
        String cloneId;
        try {
            // rcupration de la publi de rfrence
            PublicationDetail refPub = refPubComplete.getPublicationDetail();

            String fromId = refPub.getPK().getId();
            String fromComponentId = refPub.getPK().getInstanceId();

            PublicationDetail clone = getClone(refPub);

            SettingBundle publicationSettings = ResourceLocator
                    .getSettingBundle("org.silverpeas.publication.publicationSettings");
            String absolutePath = FileRepositoryManager.getAbsolutePath(fromComponentId);

            if (pubDetail != null) {
                clone.setAuthor(pubDetail.getAuthor());
                clone.setBeginDate(pubDetail.getBeginDate());
                clone.setBeginHour(pubDetail.getBeginHour());
                clone.setDescription(pubDetail.getDescription());
                clone.setEndDate(pubDetail.getEndDate());
                clone.setEndHour(pubDetail.getEndHour());
                clone.setImportance(pubDetail.getImportance());
                clone.setKeywords(pubDetail.getKeywords());
                clone.setName(pubDetail.getName());
                clone.setTargetValidatorId(pubDetail.getTargetValidatorId());
            }
            if (isInteger(refPub.getInfoId())) {
                // Case content = DB
                clone.setInfoId(null);
            }
            clone.setStatus(nextStatus);
            clone.setCloneId(fromId);
            clone.setIndexOperation(IndexManager.NONE);

            PublicationPK clonePK = publicationService.createPublication(clone);
            clonePK.setComponentName(fromComponentId);
            cloneId = clonePK.getId();

            // clone attachments
            List<SimpleDocument> documents = AttachmentServiceProvider.getAttachmentService()
                    .listDocumentsByForeignKey(new ResourceReference(fromId, fromComponentId), null);
            Map<String, String> attachmentIds = new HashMap<>(documents.size());
            for (SimpleDocument document : documents) {
                AttachmentServiceProvider.getAttachmentService().cloneDocument(document, cloneId);
            }

            // eventually, paste the form content
            String xmlFormShortName = refPub.getInfoId();
            if (xmlFormShortName != null && !"0".equals(xmlFormShortName) && !isInteger(xmlFormShortName)) {
                PublicationTemplateManager templateManager = PublicationTemplateManager.getInstance();
                // Content = XMLForm
                // register xmlForm to publication
                templateManager.addDynamicPublicationTemplate(fromComponentId + ":" + xmlFormShortName,
                        xmlFormShortName + ".xml");

                PublicationTemplate pubTemplate = templateManager
                        .getPublicationTemplate(fromComponentId + ":" + xmlFormShortName);

                RecordSet set = pubTemplate.getRecordSet();

                // clone dataRecord
                set.clone(fromId, fromComponentId, cloneId, fromComponentId, attachmentIds);
            }

            // paste wysiwyg
            WysiwygController.copy(fromComponentId, fromId, fromComponentId, cloneId, clone.getCreatorId());

            // affectation de l'id du clone  la publication de rfrence
            refPub.setCloneId(cloneId);
            refPub.setCloneStatus(nextStatus);
            refPub.setStatusMustBeChecked(false);
            refPub.setUpdateDateMustBeSet(false);
            updatePublication(refPub);

            // paste vignette
            String vignette = refPub.getImage();
            if (vignette != null) {
                ThumbnailDetail thumbDetail = new ThumbnailDetail(clone.getPK().getInstanceId(),
                        Integer.valueOf(clone.getPK().getId()),
                        ThumbnailDetail.THUMBNAIL_OBJECTTYPE_PUBLICATION_VIGNETTE);
                thumbDetail.setMimeType(refPub.getImageMimeType());
                if (vignette.startsWith("/")) {
                    thumbDetail.setOriginalFileName(vignette);
                } else {
                    String thumbnailsSubDirectory = publicationSettings.getString("imagesSubDirectory");
                    String from = absolutePath + thumbnailsSubDirectory + File.separator + vignette;
                    String type = FilenameUtils.getExtension(vignette);
                    String newVignette = Long.toString(System.currentTimeMillis()) + "." + type;
                    String to = absolutePath + thumbnailsSubDirectory + File.separator + newVignette;
                    FileRepositoryManager.copyFile(from, to);
                    thumbDetail.setOriginalFileName(newVignette);
                }
                ThumbnailServiceProvider.getThumbnailService().createThumbnail(thumbDetail);
            }
        } catch (IOException | ThumbnailException | FormException | PublicationTemplateException e) {
            throw new KmeliaRuntimeException(e);
        }
        return cloneId;
    }

    /**
     * Gets a service object on the comments.
     * @return a DefaultCommentService instance.
     */
    private CommentService getCommentService() {
        return commentService;
    }

    private LocalizationBundle getMultilang() {
        return ResourceLocator.getLocalizationBundle(MESSAGES_PATH);
    }

    @Override
    public NodeDetail getRoot(String componentId, String userId) {
        return getRoot(componentId, userId, null);
    }

    private NodeDetail getRoot(String componentId, String userId, List<NodeDetail> treeview) {
        NodePK rootPK = new NodePK(NodePK.ROOT_NODE_ID, componentId);
        NodeDetail root = nodeService.getDetail(rootPK);
        setRole(root, userId);
        root.setChildrenDetails(getRootChildren(root, userId, treeview));
        return root;
    }

    private List<NodeDetail> getRootChildren(NodeDetail root, String userId, List<NodeDetail> treeview) {
        String instanceId = root.getNodePK().getInstanceId();
        List<NodeDetail> children = new ArrayList<>();
        try {
            setAllowedSubfolders(root, userId);
            List<NodeDetail> nodes = (List<NodeDetail>) root.getChildrenDetails();

            // set nb objects in nodes
            setNbItemsOfSubfolders(root, treeview, userId);

            NodeDetail trash = null;
            for (NodeDetail node : nodes) {
                if (node.getNodePK().isTrash()) {
                    trash = node;
                } else if (node.getNodePK().isUnclassed()) {
                    // do not return it cause it is useless
                } else {
                    children.add(node);
                }
            }

            // adding special folder "to validate"
            if (isUserCanValidate(instanceId, userId)) {
                NodeDetail temp = new NodeDetail();
                temp.getNodePK().setId("tovalidate");
                temp.setName(getMultilang().getString("ToValidateShort"));
                if (isNbItemsDisplayed(instanceId)) {
                    int nbPublisToValidate = getPublicationsToValidate(instanceId, userId).size();
                    temp.setNbObjects(nbPublisToValidate);
                }
                children.add(temp);
            }

            // adding special folder "trash"
            if (isUserCanWrite(instanceId, userId) && trash != null) {
                children.add(trash);
            }

            root.setChildrenDetails(children);
        } catch (Exception e) {
            throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
        }
        return children;
    }

    @Override
    public Collection<NodeDetail> getFolderChildren(NodePK nodePK, String userId) {
        NodeDetail node = nodeService.getDetail(nodePK);
        if (node.getNodePK().isRoot()) {
            node.setChildrenDetails(getRootChildren(node, userId, null));
        } else {
            setAllowedSubfolders(node, userId);
        }

        // set nb objects in nodes
        setNbItemsOfSubfolders(node, null, userId);

        return node.getChildrenDetails();
    }

    private void setNbItemsOfSubfolders(NodeDetail node, List<NodeDetail> treeview, String userId) {
        String instanceId = node.getNodePK().getInstanceId();
        if (isNbItemsDisplayed(instanceId)) {
            if (treeview == null) {
                treeview = getTreeview(node.getNodePK(), userId);
            }
            // set nb objects in each nodes
            setNbItemsOfFolders(instanceId, node.getChildrenDetails(), treeview);
        }
    }

    private List<NodeDetail> getTreeview(NodePK pk, String userId) {
        String instanceId = pk.getInstanceId();
        if (isUserComponentAdmin(instanceId, userId)) {
            return getTreeview(pk, ADMIN_ROLE, isCoWritingEnable(instanceId), isDraftVisibleWithCoWriting(), userId,
                    isNbItemsDisplayed(instanceId), false);
        } else {
            return getTreeview(pk, getUserTopicProfile(pk, userId), isCoWritingEnable(instanceId),
                    isDraftVisibleWithCoWriting(), userId, isNbItemsDisplayed(instanceId),
                    isRightsOnTopicsEnabled(instanceId));
        }
    }

    private void setNbItemsOfFolders(String componentId, Collection<NodeDetail> nodes, List<NodeDetail> treeview) {
        if (isNbItemsDisplayed(componentId)) {
            for (NodeDetail child : nodes) {
                int index = treeview.indexOf(child);
                if (index != -1) {
                    child.setNbObjects(treeview.get(index).getNbObjects());
                }
            }
        }
    }

    private void setAllowedSubfolders(NodeDetail node, String userId) {
        String instanceId = node.getNodePK().getInstanceId();
        if (isRightsOnTopicsEnabled(instanceId)) {
            if (isUserComponentAdmin(instanceId, userId)) {
                // user is admin of application, all folders must be shown
                setRole(node.getChildrenDetails(), userId);
            } else {
                Collection<NodeDetail> allowedChildren = getAllowedSubfolders(node, userId);
                setRole(allowedChildren, userId);
                node.setChildrenDetails(allowedChildren);
            }
        } else {
            // no rights are used
            // keep children as they are
        }
    }

    private boolean isUserComponentAdmin(String componentId, String userId) {
        return ADMIN_ROLE.equalsIgnoreCase(KmeliaHelper.getProfile(getUserRoles(componentId, userId)));
    }

    private void setRole(NodeDetail node, String userId) {
        if (isRightsOnTopicsEnabled(node.getNodePK().getInstanceId())) {
            node.setUserRole(getUserTopicProfile(node.getNodePK(), userId));
        }
    }

    private void setRole(Collection<NodeDetail> nodes, String userId) {
        for (NodeDetail node : nodes) {
            setRole(node, userId);
        }
    }

    private boolean isRightsOnTopicsEnabled(String componentId) {
        return StringUtil.getBooleanValue(getOrganisationController().getComponentParameterValue(componentId,
                InstanceParameters.rightsOnFolders));
    }

    private boolean isNbItemsDisplayed(String componentId) {
        return StringUtil.getBooleanValue(getOrganisationController().getComponentParameterValue(componentId,
                InstanceParameters.displayNbItemsOnFolders));
    }

    private boolean isCoWritingEnable(String componentId) {
        return StringUtil.getBooleanValue(
                getOrganisationController().getComponentParameterValue(componentId, InstanceParameters.coWriting));
    }

    private boolean isDraftVisibleWithCoWriting() {
        return getComponentSettings().getBoolean("draftVisibleWithCoWriting", false);
    }

    @Override
    public String getUserTopicProfile(NodePK pk, String userId) {
        if (!isRightsOnTopicsEnabled(pk.getInstanceId()) || KmeliaHelper.isToValidateFolder(pk.getId())) {
            return KmeliaHelper.getProfile(getUserRoles(pk.getInstanceId(), userId));
        }

        NodeDetail node = getNodeHeader(pk.getId(), pk.getInstanceId());

        // check if we have to take care of topic's rights
        if (node != null && node.haveRights()) {
            int rightsDependsOn = node.getRightsDependsOn();
            return KmeliaHelper.getProfile(getOrganisationController().getUserProfiles(userId, pk.getInstanceId(),
                    rightsDependsOn, ObjectType.NODE));
        } else {
            return KmeliaHelper.getProfile(getUserRoles(pk.getInstanceId(), userId));
        }
    }

    private String[] getUserRoles(String componentId, String userId) {
        final OrganizationController oc = getOrganisationController();
        String[] profiles = oc.getUserProfiles(userId, componentId);
        if (ArrayUtil.isEmpty(profiles) && oc.getComponentInstLight(componentId).isPublic()) {
            profiles = new String[] { KmeliaHelper.ROLE_READER };
        }
        return profiles;
    }

    private NodePK getRootPK(String componentId) {
        return new NodePK(NodePK.ROOT_NODE_ID, componentId);
    }

    /**
     * This method verifies if the user behind the given user identifier can validate the publication
     * represented by the given primary key.
     * The verification is strictly applied on the given primary key, that is to say that no
     * publication clone information are retrieved.
     * To perform a verification on a publication clone, the primary key of the clone must be given.
     * @param pubPK the primary key of the publication or of the clone of a publication.
     * @param userId the identifier of the user fo which rights must be verified.
     * @return true if the user can validate, false otherwise.
     */
    @Override
    public boolean isUserCanValidatePublication(PublicationPK pubPK, String userId) {
        PublicationDetail publi = getPublicationDetail(pubPK);
        if (!publi.isValidationRequired()) {
            // publication is not in a state which allow a validation
            return false;
        }
        final List<String> validatorIds = getAllValidators(pubPK);
        if (!validatorIds.contains(userId)) {
            // current user is not part of users who are able to validate this publication
            return false;
        }

        if (getValidationType(pubPK.getInstanceId()) == KmeliaHelper.VALIDATION_TARGET_N) {
            ValidationStep validationStep = publicationService.getValidationStepByUser(pubPK, userId);
            // user has not yet validated publication, so validation is allowed
            return validationStep == null;
        }
        return true;
    }

    @Override
    public boolean isUserCanValidate(String componentId, String userId) {
        if (KmeliaHelper.isToolbox(componentId)) {
            return false;
        }
        return isUserCanPublish(componentId, userId);
    }

    @Override
    public boolean isUserCanWrite(String componentId, String userId) {
        String[] grantedRoles = new String[] { SilverpeasRole.admin.name(), SilverpeasRole.publisher.name(),
                SilverpeasRole.writer.name() };
        return checkUserRoles(componentId, userId, grantedRoles);
    }

    @Override
    public boolean isUserCanPublish(String componentId, String userId) {
        String[] grantedRoles = new String[] { SilverpeasRole.admin.name(), SilverpeasRole.publisher.name() };
        return checkUserRoles(componentId, userId, grantedRoles);
    }

    private boolean checkUserRoles(String componentId, String userId, String... roles) {
        SilverpeasRole userProfile = SilverpeasRole
                .from(KmeliaHelper.getProfile(getUserRoles(componentId, userId)));
        boolean checked = userProfile.isInRole(roles);

        if (!checked && isRightsOnTopicsEnabled(componentId)) {
            // check if current user is publisher or admin on at least one descendant
            Iterator<NodeDetail> descendants = nodeService.getDescendantDetails(getRootPK(componentId)).iterator();
            while (!checked && descendants.hasNext()) {
                NodeDetail descendant = descendants.next();
                if (descendant.haveLocalRights()) {
                    // check if user is admin, publisher or writer on this topic
                    String[] profiles = adminController.getProfilesByObjectAndUserId(descendant.getId(),
                            ObjectType.NODE.getCode(), componentId, userId);
                    if (profiles != null && profiles.length > 0) {
                        userProfile = SilverpeasRole.from(KmeliaHelper.getProfile(profiles));
                        checked = userProfile.isInRole(roles);
                    }
                }
            }
        }
        return checked;
    }

    @Override
    public NodeDetail getExpandedPathToNode(NodePK pk, String userId) {
        String instanceId = pk.getInstanceId();
        List<NodeDetail> nodes = new ArrayList<>(nodeService.getPath(pk));
        Collections.reverse(nodes);
        nodes.remove(0);

        List<NodeDetail> treeview = null;
        if (isNbItemsDisplayed(instanceId)) {
            treeview = getTreeview(getRootPK(instanceId), userId);
        }

        NodeDetail root = getRoot(instanceId, userId, treeview);

        // set nb objects in nodes
        if (treeview != null) {
            // set nb objects on root
            root.setNbObjects(treeview.get(0).getNbObjects());
            // set nb objects in each allowed nodes
            setNbItemsOfFolders(instanceId, nodes, treeview);
        }

        NodeDetail currentNode = root;
        for (NodeDetail node : nodes) {
            // get children of each node on path to target node
            Collection<NodeDetail> children = nodeService.getChildrenDetails(node.getNodePK());
            node.setChildrenDetails(children);
            setAllowedSubfolders(node, userId);
            if (treeview != null) {
                setNbItemsOfSubfolders(node, treeview, userId);
            }
            if (currentNode != null) {
                currentNode = find(currentNode.getChildrenDetails(), node);
            }
            if (currentNode != null) {
                currentNode.setChildrenDetails(node.getChildrenDetails());
            }
        }
        return root;

    }

    private NodeDetail find(Collection<NodeDetail> nodes, NodeDetail toFind) {
        for (NodeDetail node : nodes) {
            if (node.getNodePK().getId().equals(toFind.getNodePK().getId())) {
                return node;
            }
        }
        return null;
    }

    /**
     * Removes publications according to given ids. Before a publication is removed, user priviledges
     * are controlled. If node defines the trash, publications are definitively deleted. Otherwise,
     * publications move into trash.
     * @param ids the ids of publications to delete
     * @param nodePK the node where the publications are
     * @param userId the user who wants to perform deletion
     * @return the list of publication ids which has been really deleted
     * @
     */
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public List<String> deletePublications(List<String> ids, NodePK nodePK, String userId) {
        List<String> removedIds = new ArrayList<>();
        String profile = getProfile(userId, nodePK);
        for (String id : ids) {
            PublicationPK pk = new PublicationPK(id, nodePK);
            if (isUserCanDeletePublication(new PublicationPK(id, nodePK), profile, userId)) {
                try {
                    if (nodePK.isTrash()) {
                        deletePublication(pk);
                    } else {
                        sendPublicationToBasket(pk);
                    }
                    removedIds.add(id);
                } catch (Exception e) {
                    SilverLogger.getLogger(this).error("Deletion of publication {0} failed",
                            new String[] { pk.getId() }, e);
                }
            }
        }
        return removedIds;
    }

    private boolean isUserCanDeletePublication(PublicationPK pubPK, String profile, String userId) {
        User owner = getPublication(pubPK).getCreator();
        return KmeliaPublicationHelper.isRemovable(pubPK.getInstanceId(), userId, profile, owner);
    }

    @Override
    public String getWysiwyg(PublicationPK pubPK, String language) {
        try {
            return WysiwygController.load(pubPK.getInstanceId(), pubPK.getId(), I18NHelper.checkLanguage(language));
        } catch (Exception e) {
            throw new KmeliaRuntimeException(e);
        }
    }

    @Override
    public KmeliaPublication getContentById(String contentId) {
        return getPublication(new PublicationPK(contentId));
    }

    @Override
    public SettingBundle getComponentSettings() {
        return settings;
    }

    @Override
    public LocalizationBundle getComponentMessages(String language) {
        return ResourceLocator.getLocalizationBundle(MESSAGES_PATH, language);
    }

    @Override
    public boolean isRelatedTo(final String instanceId) {
        return instanceId.startsWith("kmelia") || instanceId.startsWith("kmax") || instanceId.startsWith("toolbox");
    }

    @SimulationActionProcess(elementLister = KmeliaNodeSimulationElementLister.class)
    @Action(ActionType.MOVE)
    @Override
    public NodeDetail moveNode(@SourcePK NodePK nodePK, @TargetPK NodePK to, KmeliaPasteDetail pasteContext) {
        List<NodeDetail> treeToPaste = nodeService.getSubTree(nodePK);

        // move node and subtree
        nodeService.moveNode(nodePK, to);

        for (NodeDetail fromNode : treeToPaste) {
            if (fromNode != null) {
                NodePK toNodePK = new NodePK(fromNode.getNodePK().getId(), to);
                removeNodeRights(fromNode);
                // move rich description of node
                if (!nodePK.getInstanceId().equals(to.getInstanceId())) {
                    WysiwygController.move(fromNode.getNodePK().getInstanceId(), NODE_PREFIX + fromNode.getId(),
                            to.getInstanceId(), NODE_PREFIX + toNodePK.getId());
                }

                // move publications of node
                movePublicationsOfTopic(fromNode.getNodePK(), toNodePK, pasteContext);
            }
        }

        nodePK.setComponentName(to.getInstanceId());
        return getNodeHeader(nodePK);
    }

    private void removeNodeRights(final NodeDetail node) {
        if (node.haveLocalRights()) {
            List<ProfileInst> profiles = adminController.getProfilesByObject(node.getNodePK().getId(),
                    ObjectType.NODE.getCode(), node.getNodePK().getInstanceId());
            for (ProfileInst profile : profiles) {
                if (profile != null && StringUtil.isDefined(profile.getId())) {
                    adminController.deleteProfileInst(profile.getId());
                }
            }
        }
    }

    private void movePublicationsOfTopic(NodePK fromPK, NodePK toPK, KmeliaPasteDetail pasteContext) {
        Collection<PublicationDetail> publications = publicationService.getDetailsByFatherPK(fromPK);
        for (PublicationDetail publi : publications) {
            movePublication(publi.getPK(), toPK, pasteContext);
        }
    }

    @SimulationActionProcess(elementLister = KmeliaNodeSimulationElementLister.class)
    @Action(ActionType.COPY)
    @Override
    public NodeDetail copyNode(@SourcePK @TargetPK KmeliaCopyDetail copyDetail) {
        HashMap<Integer, Integer> oldAndNewIds = new HashMap<>();
        return copyNode(copyDetail, oldAndNewIds);
    }

    private NodeDetail copyNode(KmeliaCopyDetail copyDetail, HashMap<Integer, Integer> oldAndNewIds) {
        NodePK nodePKToCopy = copyDetail.getFromNodePK();
        NodePK targetPK = copyDetail.getToNodePK();
        String userId = copyDetail.getUserId();
        NodeDetail nodeToCopy = nodeService.getDetail(nodePKToCopy);
        NodeDetail father = getNodeHeader(targetPK);

        // paste topic
        NodePK nodePK = new NodePK(UNKNOWN, targetPK);
        NodeDetail node = new NodeDetail(nodeToCopy);
        node.setNodePK(nodePK);
        node.setCreatorId(userId);
        node.setRightsDependsOn(father.getRightsDependsOn());
        node.setCreationDate(DateUtil.today2SQLDate());
        nodePK = nodeService.createNode(node, father);

        // duplicate rights
        if (copyDetail.isNodeRightsMustBeCopied()) {
            oldAndNewIds.put(Integer.parseInt(nodePKToCopy.getId()), Integer.parseInt(nodePK.getId()));
            setNodeRightDependency(oldAndNewIds, nodeToCopy, nodePK, node);
            // Set topic rights if necessary
            if (nodeToCopy.haveLocalRights()) {
                List<ProfileInst> topicProfiles = adminController.getProfilesByObject(
                        nodeToCopy.getNodePK().getId(), ObjectType.NODE.getCode(),
                        nodeToCopy.getNodePK().getInstanceId());
                for (ProfileInst nodeToPasteProfile : topicProfiles) {
                    if (nodeToPasteProfile != null) {
                        ProfileInst nodeProfileInst = (ProfileInst) nodeToPasteProfile.clone();
                        nodeProfileInst.setId("-1");
                        nodeProfileInst.setComponentFatherId(nodePK.getInstanceId());
                        nodeProfileInst.setObjectId(Integer.parseInt(nodePK.getId()));
                        nodeProfileInst.setObjectFatherId(father.getId());
                        // Add the profile
                        adminController.addProfileInst(nodeProfileInst, userId);
                    }
                }
            }
        }

        // paste wysiwyg attached to node
        WysiwygController.copy(nodePKToCopy.getInstanceId(), NODE_PREFIX + nodePKToCopy.getId(),
                nodePK.getInstanceId(), NODE_PREFIX + nodePK.getId(), userId);

        // associate model used by copied folder to new folder
        copyUsedModel(nodePKToCopy, nodePK);

        // paste publications of topics
        KmeliaCopyDetail folderContentCopy = new KmeliaCopyDetail(copyDetail);
        folderContentCopy.setFromNodePK(nodePKToCopy);
        folderContentCopy.setToNodePK(nodePK);

        if (copyDetail.isPublicationHeaderMustBeCopied()) {
            copyPublications(folderContentCopy);
        }

        // paste subtopics
        Collection<NodeDetail> subtopics = nodeToCopy.getChildrenDetails();
        for (NodeDetail subTopic : subtopics) {
            if (subTopic != null) {
                folderContentCopy.setFromNodePK(subTopic.getNodePK());
                copyNode(folderContentCopy, oldAndNewIds);
            }
        }
        return node;
    }

    private void setNodeRightDependency(final HashMap<Integer, Integer> oldAndNewIds, final NodeDetail nodeToCopy,
            final NodePK nodePK, final NodeDetail node) {
        if (nodeToCopy.haveRights()) {
            if (nodeToCopy.haveLocalRights()) {
                node.setRightsDependsOn(Integer.parseInt(nodePK.getId()));
            } else {
                int oldRightsDependsOn = nodeToCopy.getRightsDependsOn();
                Integer newRightsDependsOn = oldAndNewIds.get(Integer.valueOf(oldRightsDependsOn));
                node.setRightsDependsOn(newRightsDependsOn);
            }
            nodeService.updateRightsDependency(node);
        }
    }

    @SimulationActionProcess(elementLister = KmeliaPublicationSimulationElementLister.class)
    @Action(ActionType.COPY)
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public void copyPublications(@SourcePK @TargetPK KmeliaCopyDetail copyDetail) {
        Collection<PublicationDetail> publications = publicationService
                .getDetailsByFatherPK(copyDetail.getFromNodePK());
        for (PublicationDetail publi : publications) {
            copyPublication(publi, copyDetail);
        }
    }

    @SimulationActionProcess(elementLister = KmeliaPublicationSimulationElementLister.class)
    @Action(ActionType.COPY)
    @Override
    @Transactional(Transactional.TxType.REQUIRED)
    public PublicationPK copyPublication(@SourcePK PublicationDetail publiToCopy,
            @TargetPK KmeliaCopyDetail copyDetail) {
        NodePK nodePK = copyDetail.getToNodePK();
        String userId = copyDetail.getUserId();
        try {
            ResourceReference toResourceReference = new ResourceReference(ResourceReference.UNKNOWN_ID,
                    nodePK.getInstanceId());
            PublicationPK toPubPK = new PublicationPK(UNKNOWN, nodePK);
            String toComponentId = nodePK.getInstanceId();

            // Handle duplication as a creation, ignore initial parameters
            PublicationDetail newPubli = new PublicationDetail();
            newPubli.setPk(toPubPK);
            newPubli.setLanguage(publiToCopy.getLanguage());
            newPubli.setName(publiToCopy.getName());
            newPubli.setDescription(publiToCopy.getDescription());
            newPubli.setKeywords(publiToCopy.getKeywords());
            newPubli.setTranslations(publiToCopy.getClonedTranslations());
            newPubli.setAuthor(publiToCopy.getAuthor());
            newPubli.setCreatorId(userId);
            newPubli.setBeginDate(publiToCopy.getBeginDate());
            newPubli.setBeginHour(publiToCopy.getBeginHour());
            newPubli.setEndDate(publiToCopy.getEndDate());
            newPubli.setEndHour(publiToCopy.getEndHour());
            newPubli.setImportance(publiToCopy.getImportance());
            if (copyDetail.isPublicationContentMustBeCopied()) {
                newPubli.setInfoId(publiToCopy.getInfoId());
            }
            // use validators selected via UI
            newPubli.setTargetValidatorId(copyDetail.getPublicationValidatorIds());

            // manage status explicitly to bypass Draft mode
            setToByPassDraftMode(copyDetail, nodePK, newPubli, userId);

            String fromId = publiToCopy.getPK().getId();
            String fromComponentId = publiToCopy.getPK().getInstanceId();
            ResourceReference fromResourceReference = new ResourceReference(publiToCopy.getPK().getId(),
                    fromComponentId);
            PublicationPK fromPubPK = new PublicationPK(publiToCopy.getPK().getId(), fromComponentId);

            if (copyDetail.isAdministrativeOperation()) {
                newPubli.setCreatorId(publiToCopy.getCreatorId());
                newPubli.setCreationDate(publiToCopy.getCreationDate());
                newPubli.setUpdaterId(publiToCopy.getUpdaterId());
                newPubli.setUpdateDate(publiToCopy.getUpdateDate());
                newPubli.setStatus(publiToCopy.getStatus());
            }

            String id = createPublicationIntoTopic(newPubli, nodePK);
            // update id cause new publication is created
            toPubPK.setId(id);
            toResourceReference.setId(id);

            // Copy vignette
            ThumbnailController.copyThumbnail(fromResourceReference, toResourceReference);

            // Copy positions on Pdc
            if (copyDetail.isPublicationPositionsMustBeCopied()) {
                copyPdcPositions(fromPubPK, toPubPK);
            }

            // Copy files
            Map<String, String> fileIds = new HashMap<>();
            if (copyDetail.isPublicationFilesMustBeCopied()) {
                fileIds.putAll(copyFiles(fromPubPK, toPubPK));
            }

            // Copy content
            if (copyDetail.isPublicationContentMustBeCopied()) {
                String xmlFormShortName = newPubli.getInfoId();
                if (xmlFormShortName != null && !"0".equals(xmlFormShortName)) {
                    registerXmlForm(fromComponentId, fromResourceReference, toComponentId, toResourceReference,
                            xmlFormShortName, fileIds);
                } else {
                    // paste wysiwyg
                    WysiwygController.copy(fromComponentId, fromId, toPubPK.getInstanceId(), id, userId);
                }
            }

            // Index publication to index its files and content
            publicationService.createIndex(toPubPK);

            return newPubli.getPK();
        } catch (Exception ex) {
            SilverLogger.getLogger(this).error("Publication copy failure", ex);
        }
        return null;
    }

    private void setToByPassDraftMode(@TargetPK final KmeliaCopyDetail copyDetail, final NodePK nodePK,
            final PublicationDetail newPubli, final String userId) {
        if (StringUtil.isDefined(copyDetail.getPublicationStatus())) {
            String profile = getProfile(userId, nodePK);
            if (!copyDetail.getPublicationStatus().equals(PublicationDetail.DRAFT_STATUS)) {
                if (SilverpeasRole.from(profile).isGreaterThanOrEquals(SilverpeasRole.publisher)) {
                    newPubli.setStatus(PublicationDetail.VALID_STATUS);
                } else {
                    // case of writer
                    newPubli.setStatus(PublicationDetail.TO_VALIDATE_STATUS);
                }
            }
        }
    }

    private void registerXmlForm(final String fromComponentId, final ResourceReference fromResourceReference,
            final String toComponentId, final ResourceReference toResourceReference, final String xmlFormShortName,
            final Map<String, String> fileIds) throws PublicationTemplateException, FormException {
        // Content = XMLForm
        // register xmlForm to publication
        PublicationTemplateManager publicationTemplateManager = PublicationTemplateManager.getInstance();
        GenericRecordSet toRecordset = publicationTemplateManager
                .addDynamicPublicationTemplate(toComponentId + ":" + xmlFormShortName, xmlFormShortName + ".xml");

        PublicationTemplate pubTemplate = publicationTemplateManager
                .getPublicationTemplate(fromComponentId + ":" + xmlFormShortName);
        RecordSet set = pubTemplate.getRecordSet();

        set.copy(fromResourceReference, toResourceReference, toRecordset.getRecordTemplate(), fileIds);
    }

    private Map<String, String> copyFiles(PublicationPK fromPK, PublicationPK toPK) {
        Map<String, String> fileIds = new HashMap<>();
        List<SimpleDocument> origins = AttachmentServiceProvider.getAttachmentService()
                .listDocumentsByForeignKeyAndType(fromPK.toResourceReference(), DocumentType.attachment, null);
        for (SimpleDocument origin : origins) {
            SimpleDocumentPK copyPk = AttachmentServiceProvider.getAttachmentService().copyDocument(origin,
                    new ResourceReference(toPK));
            fileIds.put(origin.getId(), copyPk.getId());
        }
        return fileIds;
    }

    private void copyPdcPositions(PublicationPK fromPK, PublicationPK toPK) throws PdcException {
        int fromSilverObjectId = getSilverObjectId(fromPK);
        int toSilverObjectId = getSilverObjectId(toPK);

        pdcManager.copyPositions(fromSilverObjectId, fromPK.getInstanceId(), toSilverObjectId,
                toPK.getInstanceId());
    }

    public List<KmeliaPublication> filterPublications(List<KmeliaPublication> publications, String instanceId,
            SilverpeasRole profile, String userId) {
        boolean coWriting = isCoWritingEnable(instanceId);
        List<KmeliaPublication> filteredPublications = new ArrayList<>();
        for (KmeliaPublication userPub : publications) {
            if (isPublicationVisible(userPub.getDetail(), profile, userId, coWriting)) {
                filteredPublications.add(userPub);
            }
        }
        return filteredPublications;
    }

    public boolean isPublicationVisible(PublicationDetail detail, SilverpeasRole profile, String userId) {
        boolean coWriting = isCoWritingEnable(detail.getInstanceId());
        return isPublicationVisible(detail, profile, userId, coWriting);
    }

    @Override
    public void userHaveBeenDeleted(String userId) {
        List<PublicationDetail> publications = publicationService.removeUserFromTargetValidators(userId);
        SilverLogger.getLogger(this).debug(
                "User ''{0}'' have been removed from {1} publications as target validator", userId,
                publications.size());

        // Validation process is performed, maybe some must be validated.
        KmeliaValidation.by(userId).validatorHasNoMoreRight().validate(publications);
    }

    private boolean isPublicationVisible(PublicationDetail detail, SilverpeasRole profile, String userId,
            boolean coWriting) {
        if (detail.getStatus() != null) {
            if (detail.isValid()) {
                if (isVisible(detail, profile, userId, coWriting)) {
                    return true;
                }
            } else {
                if (detail.isDraft()) {
                    // si le theme est en co-rdaction et si on autorise le mode brouillon visible par tous
                    // toutes les publications en mode brouillon sont visibles par tous, sauf les lecteurs
                    // sinon, seule les publications brouillon de l'utilisateur sont visibles
                    if (isVisibleInDraft(detail, profile, userId, coWriting)) {
                        return true;
                    }
                } else if (isVisibleInPublished(detail, profile, userId, coWriting)) {
                    // si le thme est en co-rdaction, toutes les publications sont visibles par tous,
                    // sauf les lecteurs
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isVisibleInDraft(final PublicationDetail detail, final SilverpeasRole profile,
            final String userId, final boolean coWriting) {
        return userId.equals(detail.getCreatorId()) || userId.equals(detail.getUpdaterId())
                || (coWriting && isDraftVisibleWithCoWriting() && profile != SilverpeasRole.user);
    }

    private boolean isVisibleInPublished(final PublicationDetail detail, final SilverpeasRole profile,
            final String userId, final boolean coWriting) {
        return profile == SilverpeasRole.admin || profile == SilverpeasRole.publisher
                || userId.equals(detail.getCreatorId()) || userId.equals(detail.getUpdaterId())
                || (profile != SilverpeasRole.user && coWriting);
    }

    private boolean isVisible(final PublicationDetail detail, final SilverpeasRole profile, final String userId,
            final boolean coWriting) {
        if (detail.isVisible()) {
            return true;
        } else {
            if (profile == SilverpeasRole.admin || userId.equals(detail.getUpdaterId())
                    || (profile != SilverpeasRole.user && coWriting)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets a business service of dateReminder.
     *
     * @return a DefaultDateReminderService instance.
     */
    private PersistentDateReminderService getDateReminderService() {
        return dateReminderService;
    }

    private PublicationDetail clonePublication(String cloneId, PublicationPK pubPK, String validatorUserId,
            Date validationDate) {
        PublicationPK tempPK = new PublicationPK(cloneId, pubPK);
        CompletePublication publication = publicationService.getCompletePublication(tempPK);
        PublicationDetail clone = getClone(publication.getPublicationDetail());
        clone.setPk(pubPK);
        if (validatorUserId != null) {
            clone.setValidatorId(validatorUserId);
            clone.setValidateDate(validationDate != null ? validationDate : new Date());
        }
        clone.setStatus(PublicationDetail.VALID_STATUS);
        clone.setCloneId("-1");
        clone.setCloneStatus(null);
        return clone;
    }

    private RecordSet getXMLFormFrom(String infoId, PublicationPK pubPK) throws PublicationTemplateException {
        // register xmlForm to publication
        String xmlFormShortName = infoId;

        // get xmlContent to paste
        PublicationTemplateManager publicationTemplateManager = PublicationTemplateManager.getInstance();
        PublicationTemplate pubTemplate = publicationTemplateManager
                .getPublicationTemplate(pubPK.getInstanceId() + ":" + xmlFormShortName);

        return pubTemplate.getRecordSet();
    }

    void onDocumentDeletion(AttachmentRef attachment) {
        Optional<KmeliaOperationContext> context = KmeliaOperationContext.current();
        if (!context.isPresent() || !context.get().isAbout(DELETION)) {
            PublicationPK pubPK = new PublicationPK(attachment.getForeignId(), attachment.getInstanceId());
            externalElementsOfPublicationHaveChanged(pubPK, attachment.getUserId(), false);
        }
    }

    @Override
    public UserNotification getUserNotification(NodePK pk) {
        NodeDetail node = getNodeHeader(pk);
        return new KmeliaNotifyTopicUserNotification(node).build();
    }

    @Override
    public List<String> getActiveValidatorIds(PublicationPK pk) {
        PublicationDetail pub = getPublicationDetail(pk);
        List<String> activeValidatorIds = new ArrayList<>();
        String[] validatorIds = pub.getTargetValidatorIds();
        if (validatorIds == null) {
            return activeValidatorIds;
        }
        for (String userId : validatorIds) {
            String profile = getProfileOnPublication(userId, pk);
            if (profile != null && SilverpeasRole.from(profile).isGreaterThanOrEquals(SilverpeasRole.publisher)) {
                activeValidatorIds.add(userId);
            }
        }
        return activeValidatorIds;
    }

    private class PublicationConcernedByUpdate {
        private final PublicationPK pubPK;
        private PublicationDetail pubDetail;
        private boolean isPublicationInBasketBeforeUpdate;

        public PublicationConcernedByUpdate(final PublicationPK pubPK) {
            this.pubPK = pubPK;
        }

        public PublicationDetail getPubDetail() {
            return pubDetail;
        }

        public boolean isPublicationInBasketBeforeUpdate() {
            return isPublicationInBasketBeforeUpdate;
        }

        public PublicationConcernedByUpdate invoke() {
            pubDetail = null;
            isPublicationInBasketBeforeUpdate = false;
            try {
                isPublicationInBasketBeforeUpdate = isPublicationInBasket(pubPK);
                pubDetail = getPublicationDetail(pubPK);
            } catch (Exception e) {
                // publication no longer exists do not throw exception because this method is called by JMS
                // layer
                // if exception is throw, JMS will attempt to execute it again and again...
                SilverLogger.getLogger(DefaultKmeliaService.this).error("Impossible to get the publication {0}",
                        pubPK.getId());
            }
            return this;
        }

        public boolean isPublicationNotDefined() {
            return pubDetail == null || (StringUtil.isDefined(pubPK.getInstanceId())
                    && !pubDetail.getInstanceId().equals(pubPK.getInstanceId()));
        }
    }
}