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