/* * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation */ package org.nuxeo.ecm.core.lifecycle.event; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.NXCore; import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.LifeCycleConstants; import org.nuxeo.ecm.core.api.event.CoreEventConstants; import org.nuxeo.ecm.core.api.event.DocumentEventTypes; import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; import org.nuxeo.ecm.core.event.Event; import org.nuxeo.ecm.core.event.EventBundle; import org.nuxeo.ecm.core.event.EventContext; import org.nuxeo.ecm.core.event.PostCommitEventListener; import org.nuxeo.ecm.core.event.impl.DocumentEventContext; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.services.config.ConfigurationService; /** * Listener for life cycle change events. * <p> * If event occurs on a folder, it will recurse on children to perform the same transition if possible. * <p> * If the transition event is about marking documents as "deleted", and a child cannot perform the transition, it will * be removed. * <p> * Undelete transitions are not processed, but this listener instead looks for a specific documentUndeleted event. This * is because we want to undelete documents (parents) under which we don't want to recurse. * <p> * Reinit document copy lifeCycle (BulkLifeCycleChangeListener is bound to the event documentCreatedByCopy) */ public class BulkLifeCycleChangeListener implements PostCommitEventListener { /** * @since 8.10-HF05 9.2 */ public static final String PAGINATE_GET_CHILDREN_PROPERTY = "nuxeo.bulkLifeCycleChangeListener.paginate-get-children"; /** * @since 8.10-HF05 9.2 */ public static final String GET_CHILDREN_PAGE_SIZE_PROPERTY = "nuxeo.bulkLifeCycleChangeListener.get-children-page-size"; /** * @since 8.10-HF05 9.2 */ public static final long GET_CHILDREN_PAGINATION_DISABLED_FLAG = -1; private static final Log log = LogFactory.getLog(BulkLifeCycleChangeListener.class); @Override public void handleEvent(EventBundle events) { if (!events.containsEventName(LifeCycleConstants.TRANSITION_EVENT) && !events.containsEventName(LifeCycleConstants.DOCUMENT_UNDELETED) && !events.containsEventName(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY)) { return; } for (Event event : events) { String name = event.getName(); if (LifeCycleConstants.TRANSITION_EVENT.equals(name) || LifeCycleConstants.DOCUMENT_UNDELETED.equals(name) || DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(name)) { processTransition(event); } } } protected void processTransition(Event event) { log.debug("Processing lifecycle change in async listener"); EventContext ctx = event.getContext(); if (!(ctx instanceof DocumentEventContext)) { return; } DocumentEventContext docCtx = (DocumentEventContext) ctx; DocumentModel doc = docCtx.getSourceDocument(); if (!doc.isFolder() && !DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(event.getName())) { return; } CoreSession session = docCtx.getCoreSession(); if (session == null) { log.error("Can not process lifeCycle change since session is null"); return; } String transition; String targetState; if (DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(event.getName())) { if (!Boolean.TRUE.equals(event.getContext().getProperties().get(CoreEventConstants.RESET_LIFECYCLE))) { return; } DocumentModelList docs = new DocumentModelListImpl(); docs.add(doc); if (session.exists(doc.getRef())) { reinitDocumentsLifeCyle(session, docs); session.save(); } } else { if (LifeCycleConstants.TRANSITION_EVENT.equals(event.getName())) { transition = (String) docCtx.getProperty(LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION); if (isNonRecursiveTransition(transition, doc.getType())) { // transition should not recurse into children return; } if (LifeCycleConstants.UNDELETE_TRANSITION.equals(transition)) { // not processed (as we can undelete also parents) // a specific event documentUndeleted will be used instead return; } targetState = (String) docCtx.getProperty(LifeCycleConstants.TRANSTION_EVENT_OPTION_TO); } else { // LifeCycleConstants.DOCUMENT_UNDELETED transition = LifeCycleConstants.UNDELETE_TRANSITION; targetState = ""; // unused } ConfigurationService confService = Framework.getService(ConfigurationService.class); boolean paginate = confService.isBooleanPropertyTrue(PAGINATE_GET_CHILDREN_PROPERTY); long pageSize = paginate ? Long.parseLong(confService.getProperty(GET_CHILDREN_PAGE_SIZE_PROPERTY, "500")) : GET_CHILDREN_PAGINATION_DISABLED_FLAG; changeChildrenState(session, pageSize, transition, targetState, doc); session.save(); } } protected void reinitDocumentsLifeCyle(CoreSession documentManager, DocumentModelList docs) { for (DocumentModel docMod : docs) { documentManager.reinitLifeCycleState(docMod.getRef()); if (docMod.isFolder()) { DocumentModelList children = documentManager.query(String.format( "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted' AND ecm:parentId = '%s'", docMod.getRef())); reinitDocumentsLifeCyle(documentManager, children); } } } protected boolean isNonRecursiveTransition(String transition, String type) { List<String> nonRecursiveTransitions = NXCore.getLifeCycleService().getNonRecursiveTransitionForDocType(type); return nonRecursiveTransitions.contains(transition); } /** * @since 8.10-HF05 9.2 */ protected void changeChildrenState(CoreSession session, long pageSize, String transition, String targetState, DocumentModel doc) { if (pageSize == GET_CHILDREN_PAGINATION_DISABLED_FLAG) { DocumentModelList docs = session.getChildren(doc.getRef()); changeDocumentsState(session, pageSize, transition, targetState, docs); } else { // execute a first query to know total size String query = String.format("SELECT * FROM Document where parentId ='%s'", doc.getId()); DocumentModelList docs = session.query(query, null, pageSize, 0, true); changeDocumentsState(session, pageSize, transition, targetState, docs); // loop on other children long nbChildren = docs.totalSize(); for (long i = 1; i < nbChildren / pageSize; i++) { docs = session.query(query, null, pageSize, pageSize * i, false); changeDocumentsState(session, pageSize, transition, targetState, docs); } } } // change doc state and recurse in children /** * @since 8.10-HF05 9.2 */ protected void changeDocumentsState(CoreSession session, long pageSize, String transition, String targetState, DocumentModelList docs) { for (DocumentModel doc : docs) { boolean removed = false; if (doc.getCurrentLifeCycleState() == null) { if (LifeCycleConstants.DELETED_STATE.equals(targetState)) { log.debug("Doc has no lifecycle, deleting ..."); session.removeDocument(doc.getRef()); removed = true; } } else if (doc.getAllowedStateTransitions().contains(transition) && !doc.isProxy()) { doc.followTransition(transition); } else { if (targetState.equals(doc.getCurrentLifeCycleState())) { log.debug("Document" + doc.getRef() + " is already in the target LifeCycle state"); } else if (LifeCycleConstants.DELETED_STATE.equals(targetState)) { log.debug("Impossible to change state of " + doc.getRef() + " :removing"); session.removeDocument(doc.getRef()); removed = true; } else { log.debug("Document" + doc.getRef() + " has no transition to the target LifeCycle state"); } } if (doc.isFolder() && !removed) { changeChildrenState(session, pageSize, transition, targetState, doc); } } } // change doc state and recurse in children /** * @deprecated since 9.2 use {@link #changeDocumentsState(CoreSession, long, String, String, DocumentModelList)} * instead to allow paginating children fetch (depending on configuration). */ @Deprecated protected void changeDocumentsState(CoreSession session, DocumentModelList docs, String transition, String targetState) { ConfigurationService confService = Framework.getService(ConfigurationService.class); boolean paginate = confService.isBooleanPropertyTrue(PAGINATE_GET_CHILDREN_PROPERTY); long pageSize = paginate ? Long.parseLong(confService.getProperty(GET_CHILDREN_PAGE_SIZE_PROPERTY, "500")) : GET_CHILDREN_PAGINATION_DISABLED_FLAG; changeDocumentsState(session, pageSize, transition, targetState, docs); } }