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