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 DocumentRef docRef = doc.getRef(); 054 if (session.getAllowedStateTransitions(docRef).contains(LifeCycleConstants.DELETE_TRANSITION) 055 && !doc.isProxy()) { 056 if (!session.canRemoveDocument(docRef)) { 057 throw new DocumentSecurityException("User " + session.getPrincipal().getName() 058 + " does not have the permission to remove the document " + doc.getId() + " (" 059 + doc.getPath() + ")"); 060 } 061 trashDocument(session, doc); 062 } else if (session.isTrashed(docRef)) { 063 log.warn("Document " + doc.getId() + " of type " + doc.getType() 064 + " is already in the trash, nothing to do"); 065 return; 066 } else { 067 log.warn("Document " + doc.getId() + " of type " + doc.getType() + " in state " 068 + doc.getCurrentLifeCycleState() + " does not support transition " 069 + LifeCycleConstants.DELETE_TRANSITION + ", it will be deleted immediately"); 070 session.removeDocument(docRef); 071 } 072 } 073 session.save(); 074 } 075 076 protected void trashDocument(CoreSession session, DocumentModel doc) { 077 if (doc.getParentRef() == null) { 078 // handle placeless document 079 session.removeDocument(doc.getRef()); 080 } else { 081 if (!Boolean.TRUE.equals(doc.getContextData(DISABLE_TRASH_RENAMING))) { 082 String name = mangleName(doc); 083 session.move(doc.getRef(), doc.getParentRef(), name); 084 } 085 session.followTransition(doc, LifeCycleConstants.DELETE_TRANSITION); 086 } 087 } 088 089 @Override 090 public Set<DocumentRef> undeleteDocuments(List<DocumentModel> docs) { 091 Set<DocumentRef> undeleted = new HashSet<>(); 092 if (docs.isEmpty()) { 093 return undeleted; 094 } 095 CoreSession session = docs.get(0).getCoreSession(); 096 Set<DocumentRef> docRefs = undeleteDocumentList(session, docs); 097 undeleted.addAll(docRefs); 098 // undeleted ancestors 099 for (DocumentRef docRef : docRefs) { 100 undeleteAncestors(session, docRef, undeleted); 101 } 102 session.save(); 103 // find parents of undeleted docs (for notification); 104 Set<DocumentRef> parentRefs = new HashSet<>(); 105 for (DocumentRef docRef : undeleted) { 106 parentRefs.add(session.getParentDocumentRef(docRef)); 107 } 108 // launch async action on folderish to undelete all children recursively 109 for (DocumentModel doc : docs) { 110 if (doc.isFolder()) { 111 notifyEvent(session, LifeCycleConstants.DOCUMENT_UNDELETED, doc, true); 112 } 113 } 114 return parentRefs; 115 } 116 117 /** 118 * Undeletes a list of documents. Session is not saved. Log about non-deletable documents. 119 */ 120 protected Set<DocumentRef> undeleteDocumentList(CoreSession session, List<DocumentModel> docs) { 121 Set<DocumentRef> undeleted = new HashSet<>(); 122 for (DocumentModel doc : docs) { 123 DocumentRef docRef = doc.getRef(); 124 if (session.getAllowedStateTransitions(docRef).contains(LifeCycleConstants.UNDELETE_TRANSITION)) { 125 undeleteDocument(session, doc); 126 undeleted.add(docRef); 127 } else { 128 log.debug("Impossible to undelete document " + docRef + " as it does not support transition " 129 + LifeCycleConstants.UNDELETE_TRANSITION); 130 } 131 } 132 return undeleted; 133 } 134 135 /** 136 * Undeletes ancestors of a document. Session is not saved. Stops as soon as an ancestor is not undeletable. 137 */ 138 protected void undeleteAncestors(CoreSession session, DocumentRef docRef, Set<DocumentRef> undeleted) { 139 for (DocumentRef ancestorRef : session.getParentDocumentRefs(docRef)) { 140 // getting allowed state transitions and following a transition need 141 // ReadLifeCycle and WriteLifeCycle 142 if (session.hasPermission(ancestorRef, SecurityConstants.READ_LIFE_CYCLE) 143 && session.hasPermission(ancestorRef, SecurityConstants.WRITE_LIFE_CYCLE)) { 144 if (session.getAllowedStateTransitions(ancestorRef).contains(LifeCycleConstants.UNDELETE_TRANSITION)) { 145 DocumentModel ancestor = session.getDocument(ancestorRef); 146 undeleteDocument(session, ancestor); 147 undeleted.add(ancestorRef); 148 } else { 149 break; 150 } 151 } else { 152 // stop if lifecycle properties can't be read on an ancestor 153 log.debug("Stopping to restore ancestors because " + ancestorRef.toString() + " is not readable"); 154 break; 155 } 156 } 157 } 158 159 protected void undeleteDocument(CoreSession session, DocumentModel doc) { 160 String name = doc.getName(); 161 if (!Boolean.TRUE.equals(doc.getContextData(DISABLE_TRASH_RENAMING))) { 162 String newName = unmangleName(doc); 163 if (!newName.equals(name)) { 164 session.move(doc.getRef(), doc.getParentRef(), newName); 165 } 166 } 167 session.followTransition(doc, LifeCycleConstants.UNDELETE_TRANSITION); 168 } 169 170 @Override 171 public boolean hasFeature(Feature feature) { 172 switch (feature) { 173 case TRASHED_STATE_IS_DEDUCED_FROM_LIFECYCLE: 174 return true; 175 case TRASHED_STATE_IN_MIGRATION: 176 case TRASHED_STATE_IS_DEDICATED_PROPERTY: 177 return false; 178 default: 179 throw new UnsupportedOperationException(feature.name()); 180 } 181 } 182 183}