001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:grenard@nuxeo.com">Guillaume</a>
018 */
019package org.nuxeo.ecm.collections.core.listener;
020
021import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.nuxeo.ecm.collections.api.CollectionConstants;
026import org.nuxeo.ecm.collections.api.CollectionManager;
027import org.nuxeo.ecm.collections.core.adapter.CollectionMember;
028import org.nuxeo.ecm.core.api.CoreSession;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.DocumentModelList;
031import org.nuxeo.ecm.core.api.DocumentRef;
032import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
033import org.nuxeo.ecm.core.event.Event;
034import org.nuxeo.ecm.core.event.EventContext;
035import org.nuxeo.ecm.core.event.EventListener;
036import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
037import org.nuxeo.ecm.core.query.sql.NXQL;
038import org.nuxeo.runtime.api.Framework;
039
040/**
041 * Event handler to duplicate the collection members of a duplicated collection. The handler is synchronous because it
042 * is important to capture the collection member ids of the duplicated collection at the exact moment of duplication. We
043 * don't want to duplicate a collection member that was indeed added to the duplicated collection after the duplication.
044 * The handler will then launch asynchronous tasks to duplicate the collection members.
045 *
046 * @since 5.9.3
047 */
048public class DuplicatedCollectionListener implements EventListener {
049
050    private static final Log log = LogFactory.getLog(DuplicatedCollectionListener.class);
051
052    @Override
053    public void handleEvent(Event event) {
054        EventContext ctx = event.getContext();
055        if (!(ctx instanceof DocumentEventContext)) {
056            return;
057        }
058
059        final String eventId = event.getName();
060
061        final DocumentEventContext docCxt = (DocumentEventContext) event.getContext();
062
063        DocumentModel doc = null;
064        if (eventId.equals(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY)) {
065            doc = docCxt.getSourceDocument();
066        } else if (eventId.equals(DocumentEventTypes.DOCUMENT_CHECKEDIN)) {
067            DocumentRef checkedInVersionRef = (DocumentRef) ctx.getProperties().get("checkedInVersionRef");
068            doc = ctx.getCoreSession().getDocument(checkedInVersionRef);
069            if (!doc.isVersion()) {
070                return;
071            }
072        } else {
073            return;
074        }
075
076        final CollectionManager collectionManager = Framework.getLocalService(CollectionManager.class);
077
078        if (collectionManager.isCollection(doc)) {
079
080            if (eventId.equals(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY)) {
081                log.trace(String.format("Collection %s copied", doc.getId()));
082            } else if (eventId.equals(DocumentEventTypes.DOCUMENT_CHECKEDIN)) {
083                log.trace(String.format("Collection %s checked in", doc.getId()));
084            }
085
086            collectionManager.processCopiedCollection(doc);
087
088        }
089
090        if (collectionManager.isCollected(doc)) {
091            processCopiedMember(doc, ctx.getCoreSession());
092        }
093
094        if (doc.isFolder()) {
095            // We just copied a folder, maybe among the descendants there are collections that have been copied too
096            // proceed to the deep collection copy
097            int offset = 0;
098            DocumentModelList deepCopiedCollections;
099            CoreSession session = ctx.getCoreSession();
100            do {
101                deepCopiedCollections = session.query(
102                        "SELECT * FROM Document WHERE ecm:mixinType = 'Collection' AND ecm:path STARTSWITH "
103                                + NXQL.escapeString(doc.getPathAsString()) + " ORDER BY ecm:uuid",
104                        null, CollectionAsynchrnonousQuery.MAX_RESULT, offset, false);
105                offset += deepCopiedCollections.size();
106                for (DocumentModel deepCopiedCollection : deepCopiedCollections) {
107                    collectionManager.processCopiedCollection(deepCopiedCollection);
108                }
109            } while (deepCopiedCollections.size() >= CollectionAsynchrnonousQuery.MAX_RESULT);
110
111            // Maybe among the descendants there are collection members that have been copied too
112            // Let's make sure they don't belong to their original document's collections
113            offset = 0;
114            DocumentModelList deepCopiedMembers;
115            do {
116                // CollectionMember is a dynamically added facet. Using it in where clause does not scale.
117                // Better check existence of collectionMember:collectionIds property to detect copied members
118                deepCopiedMembers = session.query(
119                        "SELECT * FROM Document WHERE " + CollectionConstants.DOCUMENT_COLLECTION_IDS_PROPERTY_NAME
120                                + "/* IS NOT NULL AND ecm:path STARTSWITH " + NXQL.escapeString(doc.getPathAsString())
121                                + " ORDER BY ecm:uuid",
122                        null, CollectionAsynchrnonousQuery.MAX_RESULT, offset, false);
123                offset += deepCopiedMembers.size();
124                for (DocumentModel deepCopiedMember : deepCopiedMembers) {
125                    processCopiedMember(deepCopiedMember, session);
126                }
127            } while (deepCopiedMembers.size() >= CollectionAsynchrnonousQuery.MAX_RESULT);
128        }
129    }
130
131    /**
132     * @since 8.4
133     */
134    private void processCopiedMember(DocumentModel doc, CoreSession session) {
135        if (!Framework.getLocalService(CollectionManager.class).isCollected(doc)) {
136            // should never happen but we may have dirty members which have no longer the CollectionMember facet but
137            // sill collectionMember:collectionIds valued
138            doc.setPropertyValue(CollectionConstants.DOCUMENT_COLLECTION_IDS_PROPERTY_NAME, null);
139        } else {
140            doc.getAdapter(CollectionMember.class).setCollectionIds(null);
141        }
142        if (doc.isVersion()) {
143            doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
144        }
145        doc = session.saveDocument(doc);
146        doc.removeFacet(CollectionConstants.COLLECTABLE_FACET);
147        doc = session.saveDocument(doc);
148    }
149
150}