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