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}