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