001/* 002 * (C) Copyright 2012 Nuxeo SA (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 * Olivier Grisel <ogrisel@nuxeo.com> 018 */ 019package org.nuxeo.drive.listener; 020 021import java.security.Principal; 022import java.util.Calendar; 023import java.util.HashMap; 024import java.util.Map; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028import org.nuxeo.drive.adapter.FileSystemItem; 029import org.nuxeo.drive.adapter.NuxeoDriveContribException; 030import org.nuxeo.drive.adapter.RootlessItemException; 031import org.nuxeo.drive.service.FileSystemItemAdapterService; 032import org.nuxeo.drive.service.NuxeoDriveEvents; 033import org.nuxeo.drive.service.impl.NuxeoDriveManagerImpl; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.LifeCycleConstants; 036import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 037import org.nuxeo.ecm.core.api.event.CoreEventConstants; 038import org.nuxeo.ecm.core.api.event.DocumentEventTypes; 039import org.nuxeo.ecm.core.event.Event; 040import org.nuxeo.ecm.core.event.EventContext; 041import org.nuxeo.ecm.core.event.EventListener; 042import org.nuxeo.ecm.core.event.EventProducer; 043import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 044import org.nuxeo.ecm.core.event.impl.EventContextImpl; 045import org.nuxeo.ecm.core.schema.FacetNames; 046import org.nuxeo.ecm.platform.audit.api.AuditLogger; 047import org.nuxeo.ecm.platform.audit.api.ExtendedInfo; 048import org.nuxeo.ecm.platform.audit.api.LogEntry; 049import org.nuxeo.runtime.api.Framework; 050 051/** 052 * Event listener to track events that should be mapped to file system item deletions in the the ChangeSummary 053 * computation. In particular this includes <li>Synchronization root unregistration (user specific)</li> <li>Simple 054 * document or root document lifecycle change to the 'deleted' state</li> <li>Simple document or root physical removal 055 * from the directory.</li> 056 */ 057public class NuxeoDriveFileSystemDeletionListener implements EventListener { 058 059 private static final Log log = LogFactory.getLog(NuxeoDriveFileSystemDeletionListener.class); 060 061 @Override 062 public void handleEvent(Event event) { 063 DocumentEventContext ctx; 064 if (event.getContext() instanceof DocumentEventContext) { 065 ctx = (DocumentEventContext) event.getContext(); 066 } else { 067 // Not interested in events that are not related to documents 068 return; 069 } 070 DocumentModel doc = ctx.getSourceDocument(); 071 if (doc.hasFacet(FacetNames.SYSTEM_DOCUMENT)) { 072 // Not interested in system documents 073 return; 074 } 075 DocumentModel docForLogEntry = doc; 076 if (DocumentEventTypes.BEFORE_DOC_UPDATE.equals(event.getName())) { 077 docForLogEntry = handleBeforeDocUpdate(ctx, doc); 078 if (docForLogEntry == null) { 079 return; 080 } 081 } 082 if (LifeCycleConstants.TRANSITION_EVENT.equals(event.getName()) && !handleLifeCycleTransition(ctx)) { 083 return; 084 } 085 if (DocumentEventTypes.ABOUT_TO_REMOVE.equals(event.getName()) && !handleAboutToRemove(doc)) { 086 return; 087 } 088 // Virtual event name 089 String virtualEventName; 090 if (DocumentEventTypes.BEFORE_DOC_SECU_UPDATE.equals(event.getName())) { 091 virtualEventName = NuxeoDriveEvents.SECURITY_UPDATED_EVENT; 092 } else if (DocumentEventTypes.ABOUT_TO_MOVE.equals(event.getName())) { 093 virtualEventName = NuxeoDriveEvents.MOVED_EVENT; 094 } else { 095 virtualEventName = NuxeoDriveEvents.DELETED_EVENT; 096 } 097 // Some events will only impact a specific user (e.g. root 098 // unregistration) 099 String impactedUserName = (String) ctx.getProperty(NuxeoDriveEvents.IMPACTED_USERNAME_PROPERTY); 100 fireVirtualEventLogEntry(docForLogEntry, virtualEventName, ctx.getPrincipal(), impactedUserName); 101 } 102 103 protected DocumentModel handleBeforeDocUpdate(DocumentEventContext ctx, DocumentModel doc) { 104 // Interested in update of a BlobHolder whose blob has been removed 105 boolean blobRemoved = false; 106 DocumentModel previousDoc = (DocumentModel) ctx.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); 107 if (previousDoc != null) { 108 BlobHolder previousBh = previousDoc.getAdapter(BlobHolder.class); 109 if (previousBh != null) { 110 BlobHolder bh = doc.getAdapter(BlobHolder.class); 111 if (bh != null) { 112 blobRemoved = previousBh.getBlob() != null && bh.getBlob() == null; 113 } 114 } 115 } 116 if (blobRemoved) { 117 // Use previous doc holding a Blob for it to be adaptable as a 118 // FileSystemItem 119 return previousDoc; 120 } else { 121 return null; 122 } 123 } 124 125 protected boolean handleLifeCycleTransition(DocumentEventContext ctx) { 126 String transition = (String) ctx.getProperty(LifeCycleConstants.TRANSTION_EVENT_OPTION_TRANSITION); 127 // Interested in 'deleted' life cycle transition only 128 return transition != null && LifeCycleConstants.DELETE_TRANSITION.equals(transition); 129 130 } 131 132 protected boolean handleAboutToRemove(DocumentModel doc) { 133 // Document deletion of document that are already in deleted 134 // state should not be marked as FS deletion to avoid duplicates 135 return !LifeCycleConstants.DELETED_STATE.equals(doc.getCurrentLifeCycleState()); 136 } 137 138 protected void fireVirtualEventLogEntry(DocumentModel doc, String eventName, Principal principal, 139 String impactedUserName) { 140 141 AuditLogger logger = Framework.getLocalService(AuditLogger.class); 142 if (logger == null) { 143 // The log is not deployed (probably in unittest) 144 return; 145 } 146 FileSystemItem fsItem = null; 147 try { 148 // NXP-19442: Avoid useless and costly call to DocumentModel#getLockInfo 149 fsItem = Framework.getLocalService(FileSystemItemAdapterService.class).getFileSystemItem(doc, true, true, 150 false); 151 } catch (RootlessItemException e) { 152 // can happen when deleting a folder under and unregistered root: 153 // nothing to do 154 return; 155 } catch (NuxeoDriveContribException e) { 156 // Nuxeo Drive contributions missing or component not ready 157 if (log.isDebugEnabled()) { 158 log.debug(String.format( 159 "Either Nuxeo Drive contributions are missing or the FileSystemItemAdapterService component is not ready (application has nor started yet) => ignoring event '%s'.", 160 eventName)); 161 } 162 return; 163 } 164 if (fsItem == null) { 165 return; 166 } 167 168 LogEntry entry = logger.newLogEntry(); 169 entry.setEventId(eventName); 170 // XXX: shall we use the server local for the event date or UTC? 171 entry.setEventDate(Calendar.getInstance(NuxeoDriveManagerImpl.UTC).getTime()); 172 entry.setCategory((String) NuxeoDriveEvents.EVENT_CATEGORY); 173 entry.setDocUUID(doc.getId()); 174 entry.setDocPath(doc.getPathAsString()); 175 entry.setPrincipalName(principal.getName()); 176 entry.setDocType(doc.getType()); 177 entry.setRepositoryId(doc.getRepositoryName()); 178 entry.setDocLifeCycle(doc.getCurrentLifeCycleState()); 179 180 Map<String, ExtendedInfo> extendedInfos = new HashMap<String, ExtendedInfo>(); 181 if (impactedUserName != null) { 182 extendedInfos.put("impactedUserName", logger.newExtendedInfo(impactedUserName)); 183 } 184 // We do not serialize the whole object as it's too big to fit in a 185 // StringInfo column and 186 extendedInfos.put("fileSystemItemId", logger.newExtendedInfo(fsItem.getId())); 187 extendedInfos.put("fileSystemItemName", logger.newExtendedInfo(fsItem.getName())); 188 entry.setExtendedInfos(extendedInfos); 189 190 EventContext eventContext = new EventContextImpl(entry); 191 Event event = eventContext.newEvent(NuxeoDriveEvents.VIRTUAL_EVENT_CREATED); 192 Framework.getService(EventProducer.class).fireEvent(event); 193 } 194 195}