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}