001/* 002 * (C) Copyright 2011 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 * Thomas Roger <troger@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.activity; 019 020import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED; 021import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_REMOVED; 022import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_UPDATED; 023import static org.nuxeo.ecm.core.schema.FacetNames.HIDDEN_IN_NAVIGATION; 024import static org.nuxeo.ecm.core.schema.FacetNames.SUPER_SPACE; 025import static org.nuxeo.ecm.core.schema.FacetNames.SYSTEM_DOCUMENT; 026 027import java.security.Principal; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Iterator; 031import java.util.List; 032 033import org.nuxeo.ecm.core.api.CoreSession; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.DocumentNotFoundException; 036import org.nuxeo.ecm.core.api.DocumentRef; 037import org.nuxeo.ecm.core.api.SystemPrincipal; 038import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 039import org.nuxeo.ecm.core.event.Event; 040import org.nuxeo.ecm.core.event.EventBundle; 041import org.nuxeo.ecm.core.event.EventContext; 042import org.nuxeo.ecm.core.event.PostCommitEventListener; 043import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 044import org.nuxeo.ecm.core.event.impl.ShallowDocumentModel; 045import org.nuxeo.runtime.api.Framework; 046 047/** 048 * Listener called asynchronously to save events as activities through the {@link ActivityStreamService}. 049 * 050 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a> 051 * @since 5.5 052 */ 053public class ActivityStreamListener implements PostCommitEventListener { 054 055 @Override 056 public void handleEvent(EventBundle events) { 057 if (isEventBundleHandled(events)) { 058 List<Event> filteredEvents = filterDuplicateEvents(events); 059 for (Event event : filteredEvents) { 060 handleEvent(event); 061 } 062 } 063 } 064 065 protected List<Event> filterDuplicateEvents(EventBundle events) { 066 List<Event> filteredEvents = new ArrayList<Event>(); 067 068 for (Event event : events) { 069 filteredEvents = removeEventIfExist(filteredEvents, event); 070 filteredEvents.add(event); 071 } 072 073 return filteredEvents; 074 } 075 076 protected List<Event> removeEventIfExist(List<Event> events, Event event) { 077 EventContext eventContext = event.getContext(); 078 if (eventContext instanceof DocumentEventContext) { 079 DocumentModel doc = ((DocumentEventContext) eventContext).getSourceDocument(); 080 for (Iterator<Event> it = events.iterator(); it.hasNext();) { 081 Event filteredEvent = it.next(); 082 EventContext filteredEventContext = filteredEvent.getContext(); 083 if (filteredEventContext instanceof DocumentEventContext) { 084 DocumentModel filteredEventDoc = ((DocumentEventContext) filteredEventContext).getSourceDocument(); 085 if (event.getName().equals(filteredEvent.getName()) 086 && doc.getRef().equals(filteredEventDoc.getRef())) { 087 it.remove(); 088 break; 089 } 090 } 091 } 092 } 093 return events; 094 } 095 096 protected void handleEvent(Event event) { 097 EventContext eventContext = event.getContext(); 098 if (eventContext instanceof DocumentEventContext) { 099 if (isEventHandled(event)) { 100 DocumentEventContext docEventContext = (DocumentEventContext) eventContext; 101 DocumentModel doc = docEventContext.getSourceDocument(); 102 if (isSkippedDocument(doc)) { 103 return; 104 } 105 106 if (docEventContext.getPrincipal() instanceof SystemPrincipal) { 107 // do not log activity for system principal 108 return; 109 } 110 111 // add activity without context 112 ActivityStreamService activityStreamService = Framework.getLocalService(ActivityStreamService.class); 113 Activity activity = toActivity(docEventContext, event); 114 activityStreamService.addActivity(activity); 115 116 CoreSession session = docEventContext.getCoreSession(); 117 for (DocumentRef ref : getParentSuperSpaceRefs(session, doc)) { 118 String context = ActivityHelper.createDocumentActivityObject(session.getRepositoryName(), 119 ref.toString()); 120 activity = toActivity(docEventContext, event, context); 121 activityStreamService.addActivity(activity); 122 } 123 } 124 } 125 } 126 127 protected boolean isSkippedDocument(DocumentModel doc) { 128 // Not really interested in non live document or if document 129 // cannot be reconnected 130 // or if not visible 131 return doc instanceof ShallowDocumentModel || doc.hasFacet(HIDDEN_IN_NAVIGATION) // 132 || doc.hasFacet(SYSTEM_DOCUMENT) // 133 || doc.isProxy() // 134 || doc.isVersion(); 135 } 136 137 protected boolean isEventHandled(Event event) { 138 for (String eventName : getHandledEventsName()) { 139 if (eventName.equals(event.getName())) { 140 return true; 141 } 142 } 143 return false; 144 } 145 146 protected boolean isEventBundleHandled(EventBundle events) { 147 for (String eventName : getHandledEventsName()) { 148 if (events.containsEventName(eventName)) { 149 return true; 150 } 151 } 152 return false; 153 } 154 155 protected List<String> getHandledEventsName() { 156 return Arrays.asList(DOCUMENT_CREATED, DOCUMENT_UPDATED, DOCUMENT_REMOVED); 157 } 158 159 protected Activity toActivity(DocumentEventContext docEventContext, Event event) { 160 return toActivity(docEventContext, event, null); 161 } 162 163 protected Activity toActivity(DocumentEventContext docEventContext, Event event, String context) { 164 Principal principal = docEventContext.getPrincipal(); 165 DocumentModel doc = docEventContext.getSourceDocument(); 166 return new ActivityBuilder().actor(ActivityHelper.createUserActivityObject(principal)).displayActor( 167 ActivityHelper.generateDisplayName(principal)).verb(event.getName()).object( 168 ActivityHelper.createDocumentActivityObject(doc)).displayObject(ActivityHelper.getDocumentTitle(doc)).target( 169 ActivityHelper.createDocumentActivityObject(doc.getRepositoryName(), doc.getParentRef().toString())).displayTarget( 170 getDocumentTitle(docEventContext.getCoreSession(), doc.getParentRef())).context(context).build(); 171 } 172 173 protected String getDocumentTitle(CoreSession session, DocumentRef docRef) { 174 try { 175 DocumentModel doc = session.getDocument(docRef); 176 return ActivityHelper.getDocumentTitle(doc); 177 } catch (DocumentNotFoundException e) { 178 return docRef.toString(); 179 } 180 } 181 182 protected List<DocumentRef> getParentSuperSpaceRefs(CoreSession session, final DocumentModel doc) 183 { 184 final List<DocumentRef> parents = new ArrayList<DocumentRef>(); 185 new UnrestrictedSessionRunner(session) { 186 @Override 187 public void run() { 188 List<DocumentModel> parentDocuments = session.getParentDocuments(doc.getRef()); 189 for (DocumentModel parent : parentDocuments) { 190 if (parent.hasFacet(SUPER_SPACE)) { 191 parents.add(parent.getRef()); 192 } 193 } 194 } 195 }.runUnrestricted(); 196 return parents; 197 } 198 199}