001/* 002 * (C) Copyright 2018 Nuxeo (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 * Kevin Leturc <kleturc@nuxeo.com> 018 */ 019package org.nuxeo.ecm.core.trash; 020 021import java.util.HashSet; 022import java.util.List; 023import java.util.Set; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.nuxeo.ecm.core.api.CoreSession; 028import org.nuxeo.ecm.core.api.DocumentModel; 029import org.nuxeo.ecm.core.api.DocumentRef; 030import org.nuxeo.ecm.core.api.DocumentSecurityException; 031import org.nuxeo.ecm.core.api.LifeCycleConstants; 032import org.nuxeo.ecm.core.api.security.SecurityConstants; 033 034/** 035 * @deprecated since 10.1, use {@link PropertyTrashService} instead. 036 */ 037public class LifeCycleTrashService extends AbstractTrashService { 038 039 private static final Log log = LogFactory.getLog(LifeCycleTrashService.class); 040 041 @Override 042 public boolean isTrashed(CoreSession session, DocumentRef docRef) { 043 return LifeCycleConstants.DELETED_STATE.equals(session.getCurrentLifeCycleState(docRef)); 044 } 045 046 @Override 047 public void trashDocuments(List<DocumentModel> docs) { 048 if (docs.isEmpty()) { 049 return; 050 } 051 CoreSession session = docs.get(0).getCoreSession(); 052 for (DocumentModel doc : docs) { 053 // add context data for backward compatibility mechanism 054 doc.putContextData(IS_ALREADY_CALLED, Boolean.TRUE); 055 DocumentRef docRef = doc.getRef(); 056 if (session.getAllowedStateTransitions(docRef).contains(LifeCycleConstants.DELETE_TRANSITION) 057 && !doc.isProxy()) { 058 if (!session.canRemoveDocument(docRef)) { 059 throw new DocumentSecurityException("User " + session.getPrincipal().getName() 060 + " does not have the permission to remove the document " + doc.getId() + " (" 061 + doc.getPath() + ")"); 062 } 063 trashDocument(session, doc); 064 } else if (session.isTrashed(docRef)) { 065 log.warn("Document " + doc.getId() + " of type " + doc.getType() 066 + " is already in the trash, nothing to do"); 067 return; 068 } else { 069 log.warn("Document " + doc.getId() + " of type " + doc.getType() + " in state " 070 + doc.getCurrentLifeCycleState() + " does not support transition " 071 + LifeCycleConstants.DELETE_TRANSITION + ", it will be deleted immediately"); 072 session.removeDocument(docRef); 073 } 074 } 075 session.save(); 076 } 077 078 protected void trashDocument(CoreSession session, DocumentModel doc) { 079 if (doc.getParentRef() == null) { 080 // handle placeless document 081 session.removeDocument(doc.getRef()); 082 } else { 083 if (!Boolean.TRUE.equals(doc.getContextData(DISABLE_TRASH_RENAMING))) { 084 String name = mangleName(doc); 085 session.move(doc.getRef(), doc.getParentRef(), name); 086 } 087 session.followTransition(doc, LifeCycleConstants.DELETE_TRANSITION); 088 } 089 } 090 091 @Override 092 public Set<DocumentRef> undeleteDocuments(List<DocumentModel> docs) { 093 Set<DocumentRef> undeleted = new HashSet<>(); 094 if (docs.isEmpty()) { 095 return undeleted; 096 } 097 CoreSession session = docs.get(0).getCoreSession(); 098 Set<DocumentRef> docRefs = undeleteDocumentList(session, docs); 099 undeleted.addAll(docRefs); 100 // undeleted ancestors 101 for (DocumentRef docRef : docRefs) { 102 undeleteAncestors(session, docRef, undeleted); 103 } 104 session.save(); 105 // find parents of undeleted docs (for notification); 106 Set<DocumentRef> parentRefs = new HashSet<>(); 107 for (DocumentRef docRef : undeleted) { 108 parentRefs.add(session.getParentDocumentRef(docRef)); 109 } 110 // launch async action on folderish to undelete all children recursively 111 for (DocumentModel doc : docs) { 112 if (doc.isFolder()) { 113 notifyEvent(session, LifeCycleConstants.DOCUMENT_UNDELETED, doc); 114 } 115 } 116 return parentRefs; 117 } 118 119 /** 120 * Undeletes a list of documents. Session is not saved. Log about non-deletable documents. 121 */ 122 protected Set<DocumentRef> undeleteDocumentList(CoreSession session, List<DocumentModel> docs) { 123 Set<DocumentRef> undeleted = new HashSet<>(); 124 for (DocumentModel doc : docs) { 125 DocumentRef docRef = doc.getRef(); 126 if (session.getAllowedStateTransitions(docRef).contains(LifeCycleConstants.UNDELETE_TRANSITION)) { 127 undeleteDocument(session, doc); 128 undeleted.add(docRef); 129 } else { 130 log.debug("Impossible to undelete document " + docRef + " as it does not support transition " 131 + LifeCycleConstants.UNDELETE_TRANSITION); 132 } 133 } 134 return undeleted; 135 } 136 137 /** 138 * Undeletes ancestors of a document. Session is not saved. Stops as soon as an ancestor is not undeletable. 139 */ 140 protected void undeleteAncestors(CoreSession session, DocumentRef docRef, Set<DocumentRef> undeleted) { 141 for (DocumentRef ancestorRef : session.getParentDocumentRefs(docRef)) { 142 // getting allowed state transitions and following a transition need 143 // ReadLifeCycle and WriteLifeCycle 144 if (session.hasPermission(ancestorRef, SecurityConstants.READ_LIFE_CYCLE) 145 && session.hasPermission(ancestorRef, SecurityConstants.WRITE_LIFE_CYCLE)) { 146 if (session.getAllowedStateTransitions(ancestorRef).contains(LifeCycleConstants.UNDELETE_TRANSITION)) { 147 DocumentModel ancestor = session.getDocument(ancestorRef); 148 undeleteDocument(session, ancestor); 149 undeleted.add(ancestorRef); 150 } else { 151 break; 152 } 153 } else { 154 // stop if lifecycle properties can't be read on an ancestor 155 log.debug("Stopping to restore ancestors because " + ancestorRef.toString() + " is not readable"); 156 break; 157 } 158 } 159 } 160 161 protected void undeleteDocument(CoreSession session, DocumentModel doc) { 162 String name = doc.getName(); 163 if (!Boolean.TRUE.equals(doc.getContextData(DISABLE_TRASH_RENAMING))) { 164 String newName = unmangleName(doc); 165 if (!newName.equals(name)) { 166 session.move(doc.getRef(), doc.getParentRef(), newName); 167 } 168 } 169 // add context data for backward compatibility mechanism 170 doc.putContextData(IS_ALREADY_CALLED, Boolean.TRUE); 171 session.followTransition(doc, LifeCycleConstants.UNDELETE_TRANSITION); 172 } 173 174 @Override 175 public boolean hasFeature(Feature feature) { 176 switch (feature) { 177 case TRASHED_STATE_IS_DEDICATED_PROPERTY: 178 return false; 179 default: 180 throw new UnsupportedOperationException(feature.name()); 181 } 182 } 183 184}