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