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.getService(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.getService(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}