org.silverpeas.core.contribution.attachment.SimpleDocumentService.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.core.contribution.attachment.SimpleDocumentService.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:
 * "https://www.silverpeas.org/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.core.contribution.attachment;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.silverpeas.core.ActionType;
import org.silverpeas.core.ResourceReference;
import org.silverpeas.core.admin.component.ComponentInstanceDeletion;
import org.silverpeas.core.admin.user.model.SilverpeasRole;
import org.silverpeas.core.cache.VolatileResourceCleaner;
import org.silverpeas.core.contribution.attachment.model.DocumentType;
import org.silverpeas.core.contribution.attachment.model.HistorisedDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocument;
import org.silverpeas.core.contribution.attachment.model.SimpleDocumentPK;
import org.silverpeas.core.contribution.attachment.model.UnlockContext;
import org.silverpeas.core.contribution.attachment.notification.AttachmentEventNotifier;
import org.silverpeas.core.contribution.attachment.process.AttachmentSimulationElementLister;
import org.silverpeas.core.contribution.attachment.repository.DocumentRepository;
import org.silverpeas.core.contribution.attachment.util.SimpleDocumentList;
import org.silverpeas.core.contribution.attachment.webdav.WebdavRepository;
import org.silverpeas.core.contribution.content.form.FormException;
import org.silverpeas.core.contribution.content.form.RecordSet;
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.i18n.I18NHelper;
import org.silverpeas.core.index.indexing.model.FullIndexEntry;
import org.silverpeas.core.index.indexing.model.IndexEngineProxy;
import org.silverpeas.core.index.indexing.model.IndexEntryKey;
import org.silverpeas.core.notification.system.ResourceEvent;
import org.silverpeas.core.persistence.jcr.JcrDatastoreManager;
import org.silverpeas.core.persistence.jcr.JcrSession;
import org.silverpeas.core.process.annotation.SimulationActionProcess;
import org.silverpeas.core.util.Charsets;
import org.silverpeas.core.util.ResourceLocator;
import org.silverpeas.core.util.SettingBundle;
import org.silverpeas.core.util.StringUtil;
import org.silverpeas.core.util.annotation.Action;
import org.silverpeas.core.util.annotation.SourceObject;
import org.silverpeas.core.util.annotation.SourcePK;
import org.silverpeas.core.util.annotation.TargetPK;
import org.silverpeas.core.util.file.FileUtil;
import org.silverpeas.core.util.logging.SilverLogger;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.silverpeas.core.persistence.jcr.JcrRepositoryConnector.openSystemSession;

/**
 * @author ehugonnet
 */
@Singleton
public class SimpleDocumentService
        implements AttachmentService, ComponentInstanceDeletion, VolatileResourceCleaner {

    private static final int STEP = 5;
    private static final String ATTACHMENT_TYPE = "Attachment";
    @Inject
    private WebdavRepository webdavRepository;
    @Inject
    private DocumentRepository repository;
    @Inject
    private AttachmentEventNotifier notificationService;

    private final SettingBundle settings = ResourceLocator
            .getSettingBundle("org.silverpeas.util.attachment.Attachment");

    @Override
    public void deleteAllAttachments(final String componentInstanceId) {
        try (JcrSession session = openSystemSession()) {
            final String componentInstanceNodePath = '/' + componentInstanceId;
            if (session.nodeExists(componentInstanceNodePath)) {
                List<SimpleDocument> documentsToDelete = repository.listAllDocumentsByComponentId(session,
                        componentInstanceId, null);
                for (SimpleDocument documentToDelete : documentsToDelete) {
                    deleteAttachment(session, documentToDelete, true);
                }
                session.getNode(componentInstanceNodePath).remove();
                session.save();
            } else {
                SilverLogger.getLogger(this).warn("Non existing node in JCR matching the component instance {0}",
                        componentInstanceId);
            }
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public void cleanVolatileResources(final String volatileResourceId, final String componentInstanceIdentifier) {
        deleteAllAttachments(volatileResourceId, componentInstanceIdentifier);
    }

    @Override
    public void createIndex(SimpleDocument document) {
        createIndex(document, null, null);
    }

    @Override
    public void deleteIndex(SimpleDocument document) {
        for (String lang : I18NHelper.getAllSupportedLanguages()) {
            deleteIndex(document, lang);
        }
    }

    @Override
    public void createIndex(SimpleDocument document, Date startOfVisibility, Date endOfVisibility) {
        if (settings.getBoolean("attachment.index.separately", true)) {
            String language = I18NHelper.checkLanguage(document.getLanguage());
            String objectType = ATTACHMENT_TYPE + document.getId() + "_" + language;
            FullIndexEntry indexEntry = new FullIndexEntry(document.getInstanceId(), objectType,
                    document.getForeignId());
            indexEntry.setLang(language);
            indexEntry.setCreationDate(document.getCreated());
            indexEntry.setCreationUser(document.getCreatedBy());
            if (startOfVisibility != null) {
                indexEntry.setStartDate(startOfVisibility);
            }
            if (endOfVisibility != null) {
                indexEntry.setEndDate(endOfVisibility);
            }

            indexEntry.setTitle(document.getTitle(), language);
            indexEntry.setPreview(document.getDescription(), language);
            indexEntry.setFilename(document.getFilename());
            indexEntry.addFileContent(document.getAttachmentPath(), Charsets.UTF_8.name(),
                    document.getContentType(), language);
            if (StringUtil.isDefined(document.getXmlFormId())) {
                updateIndexEntryWithXMLFormContent(document.getPk(), document.getXmlFormId(), indexEntry);
            }
            IndexEngineProxy.addIndexEntry(indexEntry);
        }
    }

    private void updateIndexEntryWithXMLFormContent(SimpleDocumentPK pk, String xmlFormName,
            FullIndexEntry indexEntry) {
        try {
            String objectType = ATTACHMENT_TYPE;
            PublicationTemplate pub = PublicationTemplateManager.getInstance()
                    .getPublicationTemplate(indexEntry.getComponent() + ":" + objectType + ":" + xmlFormName);
            RecordSet set = pub.getRecordSet();
            set.indexRecord(pk.getId(), xmlFormName, indexEntry);
        } catch (PublicationTemplateException | FormException e) {
            SilverLogger.getLogger(this).error(e);
        }
    }

    /**
     * @param document
     * @param lang
     */
    private void deleteIndex(SimpleDocument document, String lang) {
        String language = lang;
        if (language == null) {
            language = I18NHelper.defaultLanguage;
        }
        String objectType = ATTACHMENT_TYPE + document.getId() + '_' + language;
        IndexEntryKey indexEntry = new IndexEntryKey(document.getInstanceId(), objectType, document.getForeignId());
        IndexEngineProxy.removeIndexEntry(indexEntry);
    }

    @Override
    public void unindexAttachmentsOfExternalObject(ResourceReference foreignKey) {
        try (JcrSession session = openSystemSession()) {
            List<SimpleDocument> docs = repository.listDocumentsByForeignId(session, foreignKey.getInstanceId(),
                    foreignKey.getId(), I18NHelper.defaultLanguage);
            for (SimpleDocument doc : docs) {
                deleteIndex(doc, I18NHelper.defaultLanguage);
            }
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public void addXmlForm(SimpleDocumentPK pk, String language, String xmlFormName) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocument doc = repository.findDocumentById(session, pk, language);
            doc.setXmlFormId(xmlFormName);
            repository.updateDocument(session, doc, true);
            session.save();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Create file attached to an object who is identified by the foreignId.
     * @param document the document to be created.
     * @param content the binary content of the document.
     * @return the stored document.
     * @throws AttachmentException
     */
    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, InputStream content) {
        return createAttachment(document, content, true);
    }

    /**
     * Create file attached to an object who is identified by the foreignId.
     * @param document the document to be created.
     * @param content the binary content of the document.
     * @param indexIt <code>true</code> if the document is to be indexed,  <code>false</code>
     * otherwhise.
     * @return the stored document.
     */
    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, InputStream content,
            boolean indexIt) {
        return createAttachment(document, content, indexIt, true);
    }

    /**
     * Create file attached to an object who is identified by the foreignId.
     * @param document the document to be created.
     * @param content the binary content of the document.
     * @param indexIt <code>true</code> if the document is to be indexed,  <code>false</code>
     * otherwhise.
     * @param notify <code>true</code> to notify about the creation of an attachment,
     * <code>false</code> otherwise.
     * @return the stored document.
     */
    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, InputStream content,
            boolean indexIt, boolean notify) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocumentPK docPk = repository.createDocument(session, document);
            session.save();
            SimpleDocument createdDocument = repository.findDocumentById(session, docPk, document.getLanguage());
            createdDocument.setPublicDocument(document.isPublic());
            SimpleDocument finalDocument = repository.unlock(session, createdDocument, false);
            repository.storeContent(finalDocument, content, false);
            if (reallyNotifying(document, notify) && StringUtil.isDefined(document.getCreatedBy())) {
                notificationService.notifyEventOn(ResourceEvent.Type.CREATION, document);
            }
            if (indexIt) {
                createIndex(finalDocument);
            }
            return finalDocument;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Delete a given attachment.
     * @param document the document to deleted.
     */
    @Override
    public void deleteAttachment(SimpleDocument document) {
        deleteAttachment(document, true);
    }

    @Override
    public void deleteAllAttachments(final String resourceId, final String componentInstanceId) {
        List<SimpleDocument> documentsToDelete = listAllDocumentsByForeignKey(
                new ResourceReference(resourceId, componentInstanceId), null);
        for (SimpleDocument documentToDelete : documentsToDelete) {
            deleteAttachment(documentToDelete);
        }
    }

    /**
     * Delete a given attachment.
     * @param document the attachmentDetail object to deleted.
     * @param notify <code>true</code> to notify about the deletion of an attachment,
     * <code>false</code> otherwise.</code>
     */
    @Override
    public void deleteAttachment(SimpleDocument document, boolean notify) {
        try (JcrSession session = openSystemSession()) {
            deleteAttachment(session, document, notify);
            session.save();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    private void deleteAttachment(Session session, SimpleDocument document, boolean notify)
            throws RepositoryException {
        repository.fillNodeName(session, document);
        repository.deleteDocument(session, document.getPk());
        for (String lang : I18NHelper.getAllSupportedLanguages()) {
            deleteIndex(document, lang);
        }
        if (document.isOpenOfficeCompatible()) {
            webdavRepository.deleteAttachmentNode(session, document);
        }
        if (reallyNotifying(document, notify)) {
            notificationService.notifyEventOn(ResourceEvent.Type.DELETION, document);
        }
    }

    @Override
    public SimpleDocument searchDocumentById(SimpleDocumentPK primaryKey, String lang) {
        try (JcrSession session = openSystemSession()) {
            if (StringUtil.isDefined(primaryKey.getId()) && !StringUtil.isLong(primaryKey.getId())) {
                return repository.findDocumentById(session, primaryKey, lang);
            }
            SimpleDocument doc = repository.findDocumentByOldSilverpeasId(session, primaryKey.getComponentName(),
                    primaryKey.getOldSilverpeasId(), false, lang);
            if (doc == null) {
                doc = repository.findDocumentByOldSilverpeasId(session, primaryKey.getComponentName(),
                        primaryKey.getOldSilverpeasId(), true, lang);
            }
            return doc;
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public SimpleDocumentList<SimpleDocument> listAllDocumentsByForeignKey(ResourceReference foreignKey,
            String lang) {
        try (JcrSession session = openSystemSession()) {
            return repository.listAllDocumentsByForeignId(session, foreignKey.getInstanceId(), foreignKey.getId(),
                    lang);
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public SimpleDocumentList<SimpleDocument> listDocumentsByForeignKey(ResourceReference foreignKey, String lang) {
        try (JcrSession session = openSystemSession()) {
            final SimpleDocumentList<SimpleDocument> documents = repository.listDocumentsByForeignId(session,
                    foreignKey.getInstanceId(), foreignKey.getId(), lang);
            documents.sortYoungestToOldestAddIfEnabled();
            return documents;
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.UPDATE)
    @Override
    public void updateAttachment(@SourceObject @TargetPK SimpleDocument document, boolean indexIt, boolean notify) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocument oldAttachment = repository.findDocumentById(session, document.getPk(),
                    document.getLanguage());
            repository.fillNodeName(session, document);
            repository.updateDocument(session, document, true);
            if (!oldAttachment.isVersioned() && document.isOpenOfficeCompatible() && document.isReadOnly()) {
                // le fichier est renomm
                if (!oldAttachment.getFilename().equals(document.getFilename())) {
                    webdavRepository.deleteAttachmentNode(session, oldAttachment);
                    webdavRepository.createAttachmentNode(session, document);
                } else {
                    webdavRepository.updateAttachmentBinaryContent(session, document);
                }
            }
            session.save();

            String userId = document.getUpdatedBy();
            if (StringUtil.isDefined(userId) && reallyNotifying(document, notify)) {
                notificationService.notifyEventOn(ResourceEvent.Type.UPDATE, oldAttachment, document);
            }
            if (indexIt) {
                createIndex(document);
            }
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.UPDATE)
    @Override
    public void updateAttachment(@SourceObject @TargetPK SimpleDocument document, InputStream in, boolean indexIt,
            boolean notify) {
        try (JcrSession session = openSystemSession()) {
            String owner = document.getEditedBy();
            if (!StringUtil.isDefined(owner)) {
                owner = document.getUpdatedBy();
            }
            boolean checkinRequired = repository.lock(session, document, owner);
            SimpleDocument docBeforeUpdate = repository.findDocumentById(session, document.getPk(),
                    document.getLanguage());
            repository.updateDocument(session, document, true);
            repository.addContent(session, document.getPk(), document.getAttachment());
            repository.fillNodeName(session, document);
            SimpleDocument finalDocument = document;
            if (checkinRequired) {
                finalDocument = repository.unlock(session, document, false);
            }
            repository.storeContent(finalDocument, in, true);
            if (document.isOpenOfficeCompatible() && finalDocument.isReadOnly()) {
                webdavRepository.updateNodeAttachment(session, finalDocument);
            }
            repository.duplicateContent(document, finalDocument);

            session.save();

            String userId = finalDocument.getUpdatedBy();
            if (StringUtil.isDefined(userId) && reallyNotifying(finalDocument, notify)
                    && finalDocument.isPublic()) {
                notificationService.notifyEventOn(ResourceEvent.Type.UPDATE, docBeforeUpdate, document);
            }
            if (indexIt) {
                createIndex(finalDocument);
            }
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public void removeContent(SimpleDocument document, String lang, boolean notify) {
        try (JcrSession session = openSystemSession()) {
            boolean requireLock = repository.lock(session, document, document.getEditedBy());
            boolean existsOtherContents = repository.removeContent(session, document.getPk(), lang);
            if (document.isOpenOfficeCompatible() && document.isReadOnly()) {
                webdavRepository.deleteAttachmentContentNode(session, document, lang);
            }
            deleteIndex(document, document.getLanguage());

            session.save();

            String userId = document.getCreatedBy();
            if (StringUtil.isDefined(userId) && reallyNotifying(document, notify)) {
                if (existsOtherContents) {
                    notificationService.notifyEventOn(ResourceEvent.Type.UPDATE, document, document);
                } else {
                    notificationService.notifyEventOn(ResourceEvent.Type.DELETION, document);
                }
            }
            SimpleDocument finalDocument = document;
            if (requireLock) {
                finalDocument = repository.unlockFromContentDeletion(session, document);
                if (existsOtherContents) {
                    repository.duplicateContent(document, finalDocument);
                }
            }
            finalDocument.setLanguage(lang);
            final File fileToDelete;
            if (!existsOtherContents) {
                fileToDelete = new File(finalDocument.getDirectoryPath(null)).getParentFile().getParentFile();
            } else {
                fileToDelete = new File(finalDocument.getAttachmentPath()).getParentFile();
            }
            FileUtils.deleteQuietly(fileToDelete);
            FileUtil.deleteEmptyDir(fileToDelete.getParentFile());
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Clone the attachment.
     * @param original
     * @param foreignCloneId
     * @return
     */
    @Override
    public SimpleDocumentPK cloneDocument(SimpleDocument original, String foreignCloneId) {
        try (JcrSession session = openSystemSession();
                InputStream in = new FileInputStream(original.getAttachmentPath())) {
            SimpleDocumentPK clonePk = repository.copyDocument(session, original,
                    new ResourceReference(foreignCloneId, original.getInstanceId()));
            SimpleDocument clone = repository.findDocumentById(session, clonePk, null);
            repository.copyMultilangContent(original, clone);
            repository.setClone(session, original, clone);
            session.save();
            return clonePk;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Clone the attachment.
     * @param original
     * @param targetPk
     * @return
     */
    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.COPY)
    @Override
    public SimpleDocumentPK copyDocument(@SourceObject SimpleDocument original,
            @TargetPK ResourceReference targetPk) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocumentPK copyPk;
            if (original instanceof HistorisedDocument) {
                copyPk = repository.copyDocument(session, (HistorisedDocument) original, targetPk);
            } else {
                copyPk = repository.copyDocument(session, original, targetPk);
            }
            session.save();
            SimpleDocument copy = repository.findDocumentById(session, copyPk, null);
            if (original.isVersioned()) {
                repository.copyFullContent(original, copy);
            } else {
                repository.copyMultilangContent(original, copy);
            }

            return copyPk;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.COPY)
    @Override
    public List<SimpleDocumentPK> copyAllDocuments(@SourcePK ResourceReference resourceSourcePk,
            @TargetPK ResourceReference targetDestinationPk) {
        List<SimpleDocumentPK> copiedDocumentKeys = new ArrayList<>();
        List<SimpleDocument> documentsToCopy = listAllDocumentsByForeignKey(resourceSourcePk, null);
        for (SimpleDocument documentToCopy : documentsToCopy) {
            copiedDocumentKeys.add(copyDocument(documentToCopy, new ResourceReference(targetDestinationPk)));
        }
        return copiedDocumentKeys;
    }

    /**
     * Reorder the attachments according to the order in the list.
     * @param pks
     * @throws AttachmentException
     */
    @Override
    public void reorderAttachments(List<SimpleDocumentPK> pks) {
        try (JcrSession session = openSystemSession()) {
            final List<SimpleDocument> list = new ArrayList<>();
            for (SimpleDocumentPK pk : pks) {
                list.add(repository.findDocumentById(session, pk, null));
            }
            reorderDocuments(session, list);
            session.save();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Reorder the attachments according to the order in the list.
     * @param documents
     * @throws AttachmentException
     */
    @Override
    public void reorderDocuments(List<SimpleDocument> documents) {
        try (JcrSession session = openSystemSession()) {
            reorderDocuments(session, documents);
            session.save();
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    private void reorderDocuments(final JcrSession session, final List<SimpleDocument> documents)
            throws RepositoryException {
        int i = STEP;
        for (SimpleDocument doc : documents) {
            doc.setOrder(i);
            repository.setOrder(session, doc);
            i += STEP;
        }
    }

    @Override
    public void getBinaryContent(OutputStream output, SimpleDocumentPK pk, String lang) {
        getBinaryContent(output, pk, lang, 0, -1);
    }

    @Override
    public void getBinaryContent(final OutputStream output, final SimpleDocumentPK pk, final String lang,
            final long contentOffset, final long contentLength) {
        try (JcrSession session = openSystemSession(); InputStream in = repository.getContent(session, pk, lang)) {
            IOUtils.copyLarge(in, output, contentOffset, contentLength);
        } catch (IOException | RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public List<SimpleDocument> listDocumentsRequiringWarning(Date alertDate, String language) {
        try (JcrSession session = openSystemSession()) {
            return repository.listDocumentsRequiringWarning(session, alertDate, language);
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public List<SimpleDocument> listExpiringDocuments(Date expiryDate, String language) {
        try (JcrSession session = openSystemSession()) {
            return repository.listExpiringDocuments(session, expiryDate, language);
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public List<SimpleDocument> listDocumentsToUnlock(Date expiryDate, String language) {
        try (JcrSession session = openSystemSession()) {
            return repository.listDocumentsToUnlock(session, expiryDate, language);
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.UPDATE)
    @Override
    public void updateAttachment(@SourceObject @TargetPK SimpleDocument document, File content, boolean indexIt,
            boolean notify) {
        try (InputStream in = new BufferedInputStream(new FileInputStream(content))) {
            updateAttachment(document, in, indexIt, notify);
        } catch (IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public void getBinaryContent(File file, SimpleDocumentPK pk, String lang) {
        try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
            getBinaryContent(out, pk, lang);
        } catch (IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, File content) {
        return createAttachment(document, content, true);
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, File content,
            boolean indexIt) {
        return createAttachment(document, content, indexIt, true);
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.CREATE)
    @Override
    public SimpleDocument createAttachment(@SourceObject @TargetPK SimpleDocument document, File content,
            boolean indexIt, boolean notify) {
        try (InputStream in = new BufferedInputStream(new FileInputStream(content))) {
            return createAttachment(document, in, indexIt, notify);
        } catch (IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    /**
     * Release a locked file.
     * @param context : the unlock parameters.
     * @return false if the file is locked - true if the unlock succeeded.
     * @throws AttachmentException
     */
    @Override
    public boolean unlock(UnlockContext context) {
        try (JcrSession session = openSystemSession()) {
            boolean restorePreviousVersion = context.isForce();
            String contentLanguage = I18NHelper.checkLanguage(context.getLang());
            SimpleDocument document = repository.findDocumentById(session,
                    new SimpleDocumentPK(context.getAttachmentId()), contentLanguage);
            SimpleDocument docBeforeUpdate = repository.findDocumentById(session, document.getPk(),
                    contentLanguage);
            contentLanguage = document.getLanguage();
            boolean updateOfficeContentFromWebDav = document.isOpenOfficeCompatible() && !context.isUpload()
                    && context.isWebdav();
            if (updateOfficeContentFromWebDav && !contentLanguage.equals(StringUtil
                    .defaultStringIfNotDefined(document.getWebdavContentEditionLanguage(), contentLanguage))) {
                // Verifying if the content language handled in WEBDAV repository is the same as the
                // content language took from the context.
                // The language handled into WEVDAV is different, SimpleDocument must be reloaded with
                // the right content language.
                contentLanguage = document.getWebdavContentEditionLanguage();
                document = repository.findDocumentById(session, new SimpleDocumentPK(context.getAttachmentId()),
                        contentLanguage);
            }

            if (!canBeUnlocked(context, session, document)) {
                return false;
            }

            boolean notify = prepareDocumentForUnlocking(context, document, updateOfficeContentFromWebDav);
            unlockDocumentInRepo(session, context, document, restorePreviousVersion, updateOfficeContentFromWebDav);
            if (document.isPublic()) {
                String userId = context.getUserId();
                if (StringUtil.isDefined(userId) && reallyNotifying(document, notify)) {
                    notificationService.notifyEventOn(ResourceEvent.Type.UPDATE, docBeforeUpdate, document);
                }
            }
        } catch (IOException | RepositoryException e) {
            throw new AttachmentException("Check-in failed", e);
        }
        return true;
    }

    private void unlockDocumentInRepo(final JcrSession session, final UnlockContext context,
            final SimpleDocument document, final boolean restorePreviousVersion,
            final boolean updateOfficeContentFromWebDav) throws RepositoryException, IOException {
        SimpleDocument finalDocument = repository.unlock(session, document, restorePreviousVersion);
        if (updateOfficeContentFromWebDav) {
            webdavRepository.updateAttachmentBinaryContent(session, finalDocument);
            webdavRepository.deleteAttachmentNode(session, finalDocument);
            repository.duplicateContent(document, finalDocument);
            JcrDatastoreManager.get().notifyDataSave();
        } else if (finalDocument.isOpenOfficeCompatible() && (context.isUpload() || !context.isWebdav())) {
            webdavRepository.deleteAttachmentNode(session, finalDocument);
            JcrDatastoreManager.get().notifyDataSave();
        } else {
            File file = new File(finalDocument.getAttachmentPath());
            if (!file.exists() && !context.isForce()) {
                repository.duplicateContent(document, finalDocument);
            }
        }
        session.save();
    }

    private boolean prepareDocumentForUnlocking(final UnlockContext context, final SimpleDocument document,
            final boolean updateOfficeContentFromWebDav) {
        boolean notify = false;
        if (context.isWebdav() || context.isUpload()) {
            String workerId = document.getEditedBy();
            document.setUpdated(new Date());
            document.setUpdatedBy(workerId);
            notify = true;
        }
        document.setPublicDocument(context.isPublicVersion());
        document.setComment(context.getComment());
        if (updateOfficeContentFromWebDav) {
            document.setSize(document.getWebdavContentEditionSize());
        }
        return notify;
    }

    private boolean canBeUnlocked(final UnlockContext context, final JcrSession session,
            final SimpleDocument document) throws RepositoryException {
        if (document.isOpenOfficeCompatible() && !context.isForce()
                && webdavRepository.isNodeLocked(session, document)) {
            return false;
        }
        return context.isForce() || !document.isReadOnly() || document.getEditedBy().equals(context.getUserId());
    }

    /**
     * Lock a file so it can be edited by an user.
     * @param attachmentId
     * @param userId
     * @param language
     * @return false if the attachment is already checkout - true if the attachment was successfully
     * checked out.
     */
    @Override
    public boolean lock(String attachmentId, String userId, String language) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocumentPK pk = new SimpleDocumentPK(attachmentId);
            SimpleDocument document = repository.findDocumentById(session, pk, language);
            if (document.isReadOnly()) {
                return document.getEditedBy().equals(userId);
            }
            repository.lock(session, document, document.getEditedBy());
            document.edit(userId);
            if (document.isOpenOfficeCompatible()) {
                webdavRepository.createAttachmentNode(session, document);
            }

            SimpleDocument oldAttachment = repository.findDocumentById(session, document.getPk(),
                    document.getLanguage());
            repository.updateDocument(session, document, false);
            if (document.isOpenOfficeCompatible() && document.isReadOnly()) {
                if (!oldAttachment.getFilename().equals(document.getFilename())) {
                    webdavRepository.deleteAttachmentNode(session, oldAttachment);
                    webdavRepository.createAttachmentNode(session, document);
                } else {
                    webdavRepository.updateNodeAttachment(session, document);
                }
            }

            session.save();
            return true;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public SimpleDocumentPK changeVersionState(SimpleDocumentPK pk, String comment) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocumentPK updatedPk = repository.changeVersionState(session, pk, comment);
            session.save();
            return updatedPk;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public SimpleDocument findExistingDocument(SimpleDocumentPK pk, String fileName, ResourceReference foreign,
            String lang) {
        List<SimpleDocument> existingDocuments = listDocumentsByForeignKey(foreign, lang);
        SimpleDocument document = searchDocumentById(pk, lang);
        if (document == null) {
            for (SimpleDocument doc : existingDocuments) {
                if (doc.getFilename().equalsIgnoreCase(fileName)) {
                    return doc;
                }
            }
        }
        return document;
    }

    @Override
    public SimpleDocumentList<SimpleDocument> listDocumentsByForeignKeyAndType(ResourceReference foreignKey,
            DocumentType type, String lang) {
        try (JcrSession session = openSystemSession()) {
            final SimpleDocumentList<SimpleDocument> documents = repository.listDocumentsByForeignIdAndType(session,
                    foreignKey.getInstanceId(), foreignKey.getId(), type, lang);
            documents.sortYoungestToOldestAddIfEnabled();
            return documents;
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public List<SimpleDocument> listDocumentsLockedByUser(String usedId, String language) {
        try (JcrSession session = openSystemSession()) {
            final List<SimpleDocument> documents = repository.listDocumentsLockedByUser(session, usedId, language);
            documents.sort((o1, o2) -> o2.getUpdated().compareTo(o1.getUpdated()));
            return documents;
        } catch (RepositoryException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.MOVE)
    @Override
    public SimpleDocumentPK moveDocument(@SourceObject SimpleDocument document,
            @TargetPK ResourceReference destination) {
        try (JcrSession session = openSystemSession()) {
            SimpleDocumentPK pk = repository.moveDocument(session, document, destination);
            SimpleDocument moveDoc = repository.findDocumentById(session, pk, null);
            repository.moveFullContent(document, moveDoc);
            if (moveDoc.isOpenOfficeCompatible()) {
                webdavRepository.moveNodeAttachment(session, document, destination.getInstanceId());
            }
            session.save();
            return pk;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @SimulationActionProcess(elementLister = AttachmentSimulationElementLister.class)
    @Action(ActionType.MOVE)
    @Override
    public List<SimpleDocumentPK> moveAllDocuments(@SourcePK ResourceReference resourceSourcePk,
            @TargetPK ResourceReference targetDestinationPk) {
        List<SimpleDocumentPK> movedDocumentKeys = new ArrayList<>();
        List<SimpleDocument> documentsToMove = listAllDocumentsByForeignKey(resourceSourcePk, null);
        for (SimpleDocument documentToMove : documentsToMove) {
            movedDocumentKeys.add(moveDocument(documentToMove, new ResourceReference(targetDestinationPk)));
        }
        return movedDocumentKeys;
    }

    @Override
    public void updateIndexEntryWithDocuments(FullIndexEntry indexEntry) {
        if (settings.getBoolean("attachment.index.incorporated", true)
                && !indexEntry.getObjectType().startsWith(ATTACHMENT_TYPE)) {
            ResourceReference pk = new ResourceReference(indexEntry.getObjectId(), indexEntry.getComponent());
            List<SimpleDocument> documents = listDocumentsByForeignKey(pk, indexEntry.getLang());
            for (SimpleDocument currentDocument : documents) {
                SimpleDocument version = currentDocument.getLastPublicVersion();
                if (version != null) {
                    indexEntry.addFileContent(version.getAttachmentPath(), Charsets.UTF_8.name(),
                            version.getContentType(), indexEntry.getLang());
                }
            }
        }
    }

    @Override
    public void indexAllDocuments(ResourceReference fk, Date startOfVisibilityPeriod, Date endOfVisibilityPeriod) {
        List<SimpleDocument> documents = listAllDocumentsByForeignKey(fk, null);
        for (SimpleDocument currentDocument : documents) {
            createIndex(currentDocument, startOfVisibilityPeriod, endOfVisibilityPeriod);
        }
    }

    @Override
    public Map<String, String> mergeDocuments(ResourceReference originalForeignKey,
            ResourceReference cloneForeignKey, DocumentType type) {
        try (JcrSession session = openSystemSession()) {
            // On part des fichiers d'origine
            List<SimpleDocument> attachments = listDocumentsByForeignKeyAndType(originalForeignKey, type, null);
            Map<String, SimpleDocument> clones = listDocumentsOfClone(cloneForeignKey, type, null);
            Map<String, String> ids = new HashMap<>(clones.size());
            // looking for updates and deletions
            for (SimpleDocument attachment : attachments) {
                if (clones.containsKey(attachment.getId())) {
                    SimpleDocument clone = clones.get(attachment.getId());
                    // the file already exists
                    // elements of clone must be merged on original
                    repository.mergeAttachment(session, attachment, clone);
                    repository.copyMultilangContent(clone, attachment);
                    repository.deleteDocument(session, clone.getPk());
                    ids.put(clone.getId(), attachment.getId());
                    // remove it from clones list
                    clones.remove(attachment.getId());
                } else {
                    // the file have been removed
                    deleteAttachment(session, attachment, true);
                }
            }

            if (!clones.isEmpty()) {
                for (SimpleDocument clone : clones.values()) {
                    clone.setCloneId(null);
                    clone.setForeignId(originalForeignKey.getId());
                    updateAttachment(clone, false, false);
                }
            }
            session.save();
            return ids;
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }

    }

    private Map<String, SimpleDocument> listDocumentsOfClone(ResourceReference resourceReference, DocumentType type,
            String lang) {
        List<SimpleDocument> documents = listDocumentsByForeignKeyAndType(resourceReference, type, lang);
        Map<String, SimpleDocument> result = new HashMap<>(documents.size());
        for (SimpleDocument doc : documents) {
            if (StringUtil.isDefined(doc.getCloneId())) {
                result.put(doc.getCloneId(), doc);
            } else {
                result.put(doc.getId(), doc);
            }
        }
        return result;
    }

    @Override
    public void switchComponentBehaviour(String componentId, boolean toVersionning) {
        try (JcrSession session = openSystemSession()) {
            // On part des fichiers d'origine
            List<SimpleDocument> attachments = repository.listDocumentsByComponentIdAndType(session, componentId,
                    DocumentType.attachment, I18NHelper.defaultLanguage);
            for (SimpleDocument attachment : attachments) {
                if (attachment.isVersioned() != toVersionning) {
                    repository.changeVersionState(session, attachment.getPk(), "");
                }
            }
            session.save();
        } catch (RepositoryException | IOException ex) {
            throw new AttachmentException(ex);
        }
    }

    @Override
    public void switchAllowingDownloadForReaders(final SimpleDocumentPK pk, final boolean allowing) {
        SimpleDocument document = searchDocumentById(pk, null);
        final Boolean documentUpdateRequired;
        if (allowing) {
            documentUpdateRequired = document.addRolesForWhichDownloadIsAllowed(SilverpeasRole.READER_ROLES);
        } else {
            documentUpdateRequired = document.addRolesForWhichDownloadIsForbidden(SilverpeasRole.READER_ROLES);
        }

        // Updating JCR if required
        if (documentUpdateRequired) {
            try (JcrSession session = openSystemSession()) {
                repository.saveForbiddenDownloadForRoles(session, document);
                session.save();
            } catch (RepositoryException ex) {
                throw new AttachmentException(ex);
            }
        }
    }

    @Override
    public void switchEnableDisplayAsContent(final SimpleDocumentPK pk, final boolean enable) {
        SimpleDocument document = searchDocumentById(pk, null);
        final boolean documentUpdateRequired = enable != document.isDisplayableAsContent();

        // Updating JCR if required
        if (documentUpdateRequired) {
            document.setDisplayableAsContent(enable);
            try (JcrSession session = openSystemSession()) {
                repository.saveDisplayableAsContent(session, document);
                session.save();
            } catch (RepositoryException ex) {
                throw new AttachmentException(ex);
            }
        }
    }

    /**
     * Check if notification must be really performed
     */
    private boolean reallyNotifying(SimpleDocument document, boolean requestedNotify) {
        if (document.getDocumentType() == DocumentType.image) {
            // adding an image (to an existing WYSIWYG) must never trigger the notification.
            // This must be done by WYSIWYG service itself (if needed).
            return false;
        }
        return requestedNotify;
    }

    /**
     * Deletes the resources belonging to the specified component instance. This method is invoked
     * by Silverpeas when a component instance is being deleted.
     * @param componentInstanceId the unique identifier of a component instance.
     */
    @Override
    public void delete(final String componentInstanceId) {
        deleteAllAttachments(componentInstanceId);
    }
}