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 static org.nuxeo.ecm.core.trash.BulkTrashedStateChangeListener.SKIP_CHILDREN_PROCESSING_KEY; 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; 033 034/** 035 * A {@link TrashService} implementation relying on {@code ecm:isTrashed}. 036 * 037 * @since 10.1 038 */ 039public class PropertyTrashService extends AbstractTrashService { 040 041 private static final Log log = LogFactory.getLog(PropertyTrashService.class); 042 043 public static final String SYSPROP_IS_TRASHED = "isTrashed"; 044 045 @Override 046 public boolean isTrashed(CoreSession session, DocumentRef docRef) { 047 Boolean isTrashed = session.getDocumentSystemProp(docRef, SYSPROP_IS_TRASHED, Boolean.class); 048 return Boolean.TRUE.equals(isTrashed); 049 } 050 051 @Override 052 public void trashDocuments(List<DocumentModel> docs) { 053 docs.forEach(this::doTrashDocument); 054 docs.stream().map(DocumentModel::getCoreSession).findFirst().ifPresent(CoreSession::save); 055 } 056 057 protected void doTrashDocument(DocumentModel doc) { 058 CoreSession session = doc.getCoreSession(); 059 DocumentRef docRef = doc.getRef(); 060 if (doc.isProxy()) { 061 log.warn("Document " + doc.getId() + " of type " + doc.getType() 062 + " will be deleted immediately because it's a proxy"); 063 session.removeDocument(docRef); 064 } else if (!session.canRemoveDocument(docRef)) { 065 throw new DocumentSecurityException( 066 "User " + session.getPrincipal().getName() + " does not have the permission to remove the document " 067 + doc.getId() + " (" + doc.getPath() + ")"); 068 } else if (session.isTrashed(docRef)) { 069 log.warn("Document " + doc.getId() + " of type " + doc.getType() 070 + " is already in the trash, nothing to do"); 071 } else { 072 if (doc.getParentRef() == null) { 073 // handle placeless document 074 session.removeDocument(doc.getRef()); 075 } else { 076 DocumentModel docForEvent = doc; 077 if (!Boolean.parseBoolean(String.valueOf(doc.getContextData(DISABLE_TRASH_RENAMING)))) { 078 String newName = mangleName(doc); 079 session.move(docRef, doc.getParentRef(), newName); 080 docForEvent = session.getDocument(docRef); 081 docForEvent.copyContextData(doc); 082 } 083 session.setDocumentSystemProp(docRef, SYSPROP_IS_TRASHED, Boolean.TRUE); 084 notifyEvent(session, DOCUMENT_TRASHED, docForEvent); 085 } 086 } 087 } 088 089 @Override 090 public Set<DocumentRef> undeleteDocuments(List<DocumentModel> docs) { 091 Set<DocumentRef> docRefs = new HashSet<>(); 092 for (DocumentModel doc : docs) { 093 docRefs.addAll(doUntrashDocument(doc)); 094 } 095 docs.stream().map(DocumentModel::getCoreSession).findFirst().ifPresent(CoreSession::save); 096 return docRefs; 097 } 098 099 protected Set<DocumentRef> doUntrashDocument(DocumentModel doc) { 100 CoreSession session = doc.getCoreSession(); 101 DocumentRef docRef = doc.getRef(); 102 103 DocumentModel docForEvent = doc; 104 // move only non placeless document 105 // currently we don't trash placeless document 106 DocumentRef parentRef = doc.getParentRef(); 107 if (!Boolean.TRUE.equals(doc.getContextData(DISABLE_TRASH_RENAMING)) && parentRef != null) { 108 String newName = unmangleName(doc); 109 if (!newName.equals(doc.getName())) { 110 session.move(docRef, parentRef, newName); 111 docForEvent = session.getDocument(docRef); 112 docForEvent.copyContextData(doc); 113 } 114 } 115 session.setDocumentSystemProp(docRef, SYSPROP_IS_TRASHED, Boolean.FALSE); 116 notifyEvent(session, DOCUMENT_UNTRASHED, docForEvent); 117 118 Set<DocumentRef> docRefs = new HashSet<>(); 119 docRefs.add(docRef); 120 121 // now untrash the parent if needed 122 if (parentRef != null && session.isTrashed(parentRef)) { 123 DocumentModel parent = session.getDocument(parentRef); 124 parent.putContextData(SKIP_CHILDREN_PROCESSING_KEY, Boolean.TRUE); 125 Set<DocumentRef> ancestorDocRefs = doUntrashDocument(parent); 126 docRefs.addAll(ancestorDocRefs); 127 } 128 return docRefs; 129 } 130 131 @Override 132 public boolean hasFeature(Feature feature) { 133 switch (feature) { 134 case TRASHED_STATE_IS_DEDUCED_FROM_LIFECYCLE: 135 case TRASHED_STATE_IN_MIGRATION: 136 return false; 137 case TRASHED_STATE_IS_DEDICATED_PROPERTY: 138 return true; 139 default: 140 throw new UnsupportedOperationException(feature.name()); 141 } 142 } 143 144}