001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Nuxeo - initial API and implementation
011 *
012 * $Id$
013 */
014
015package org.nuxeo.ecm.core.lifecycle.event;
016
017import java.util.List;
018
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021import org.nuxeo.ecm.core.NXCore;
022import org.nuxeo.ecm.core.api.CoreSession;
023import org.nuxeo.ecm.core.api.DocumentModel;
024import org.nuxeo.ecm.core.api.DocumentModelList;
025import org.nuxeo.ecm.core.api.LifeCycleConstants;
026import org.nuxeo.ecm.core.api.event.CoreEventConstants;
027import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
028import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
029import org.nuxeo.ecm.core.event.Event;
030import org.nuxeo.ecm.core.event.EventBundle;
031import org.nuxeo.ecm.core.event.EventContext;
032import org.nuxeo.ecm.core.event.PostCommitEventListener;
033import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
034
035/**
036 * Listener for life cycle change events.
037 * <p>
038 * If event occurs on a folder, it will recurse on children to perform the same transition if possible.
039 * <p>
040 * If the transition event is about marking documents as "deleted", and a child cannot perform the transition, it will
041 * be removed.
042 * <p>
043 * Undelete transitions are not processed, but this listener instead looks for a specific documentUndeleted event. This
044 * is because we want to undelete documents (parents) under which we don't want to recurse.
045 * <p>
046 * Reinit document copy lifeCycle (BulkLifeCycleChangeListener is bound to the event documentCreatedByCopy)
047 */
048public class BulkLifeCycleChangeListener implements PostCommitEventListener {
049
050    private static final Log log = LogFactory.getLog(BulkLifeCycleChangeListener.class);
051
052    @Override
053    public void handleEvent(EventBundle events) {
054        if (!events.containsEventName(LifeCycleConstants.TRANSITION_EVENT)
055                && !events.containsEventName(LifeCycleConstants.DOCUMENT_UNDELETED)
056                && !events.containsEventName(DocumentEventTypes.DOCUMENT_CREATED_BY_COPY)) {
057            return;
058        }
059        for (Event event : events) {
060            String name = event.getName();
061            if (LifeCycleConstants.TRANSITION_EVENT.equals(name) || LifeCycleConstants.DOCUMENT_UNDELETED.equals(name)
062                    || DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(name)) {
063                processTransition(event);
064            }
065        }
066    }
067
068    protected void processTransition(Event event) {
069        log.debug("Processing lifecycle change in async listener");
070        EventContext ctx = event.getContext();
071        if (!(ctx instanceof DocumentEventContext)) {
072            return;
073        }
074        DocumentEventContext docCtx = (DocumentEventContext) ctx;
075        DocumentModel doc = docCtx.getSourceDocument();
076        if (!doc.isFolder() && !DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(event.getName())) {
077            return;
078        }
079        CoreSession session = docCtx.getCoreSession();
080        if (session == null) {
081            log.error("Can not process lifeCycle change since session is null");
082            return;
083        }
084        String transition;
085        String targetState;
086        if (DocumentEventTypes.DOCUMENT_CREATED_BY_COPY.equals(event.getName())) {
087            if (!Boolean.TRUE.equals(event.getContext().getProperties().get(CoreEventConstants.RESET_LIFECYCLE))) {
088                return;
089            }
090            DocumentModelList docs = new DocumentModelListImpl();
091            docs.add(doc);
092            if (session.exists(doc.getRef())) {
093                reinitDocumentsLifeCyle(session, docs);
094                session.save();
095            }
096        } else {
097            if (LifeCycleConstants.TRANSITION_EVENT.equals(event.getName())) {
098                transition = (String) docCtx.getProperty(LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION);
099                if (isNonRecursiveTransition(transition, doc.getType())) {
100                    // transition should not recurse into children
101                    return;
102                }
103                if (LifeCycleConstants.UNDELETE_TRANSITION.equals(transition)) {
104                    // not processed (as we can undelete also parents)
105                    // a specific event documentUndeleted will be used instead
106                    return;
107                }
108                targetState = (String) docCtx.getProperty(LifeCycleConstants.TRANSTION_EVENT_OPTION_TO);
109            } else { // LifeCycleConstants.DOCUMENT_UNDELETED
110                transition = LifeCycleConstants.UNDELETE_TRANSITION;
111                targetState = ""; // unused
112            }
113            DocumentModelList docs = session.getChildren(doc.getRef());
114            changeDocumentsState(session, docs, transition, targetState);
115            session.save();
116        }
117    }
118
119    protected void reinitDocumentsLifeCyle(CoreSession documentManager, DocumentModelList docs) {
120        for (DocumentModel docMod : docs) {
121            documentManager.reinitLifeCycleState(docMod.getRef());
122            if (docMod.isFolder()) {
123                DocumentModelList children = documentManager.query(String.format(
124                        "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'deleted' AND ecm:parentId = '%s'",
125                        docMod.getRef()));
126                reinitDocumentsLifeCyle(documentManager, children);
127            }
128        }
129    }
130
131    protected boolean isNonRecursiveTransition(String transition, String type) {
132        List<String> nonRecursiveTransitions = NXCore.getLifeCycleService().getNonRecursiveTransitionForDocType(type);
133        return nonRecursiveTransitions.contains(transition);
134    }
135
136    // change doc state and recurse in children
137    protected void changeDocumentsState(CoreSession documentManager, DocumentModelList docModelList, String transition,
138            String targetState) {
139        for (DocumentModel docMod : docModelList) {
140            boolean removed = false;
141            if (docMod.getCurrentLifeCycleState() == null) {
142                if (LifeCycleConstants.DELETED_STATE.equals(targetState)) {
143                    log.debug("Doc has no lifecycle, deleting ...");
144                    documentManager.removeDocument(docMod.getRef());
145                    removed = true;
146                }
147            } else if (docMod.getAllowedStateTransitions().contains(transition) && !docMod.isProxy()) {
148                docMod.followTransition(transition);
149            } else {
150                if (targetState.equals(docMod.getCurrentLifeCycleState())) {
151                    log.debug("Document" + docMod.getRef() + " is already in the target LifeCycle state");
152                } else if (LifeCycleConstants.DELETED_STATE.equals(targetState)) {
153                    log.debug("Impossible to change state of " + docMod.getRef() + " :removing");
154                    documentManager.removeDocument(docMod.getRef());
155                    removed = true;
156                } else {
157                    log.debug("Document" + docMod.getRef() + " has no transition to the target LifeCycle state");
158                }
159            }
160            if (docMod.isFolder() && !removed) {
161                changeDocumentsState(documentManager, documentManager.getChildren(docMod.getRef()), transition,
162                        targetState);
163            }
164        }
165    }
166}