1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.plugin.activitystream.impl

File ActivityStreamImpl.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart4.png
78% of files have more coverage

Code metrics

122
396
53
1
1,053
808
132
0.33
7.47
53
2.49

Classes

Class Line # Actions
ActivityStreamImpl 82 396 0% 132 337
0.4098073541%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.plugin.activitystream.impl;
21   
22    import java.io.StringWriter;
23    import java.net.MalformedURLException;
24    import java.net.URL;
25    import java.util.ArrayList;
26    import java.util.Arrays;
27    import java.util.Date;
28    import java.util.List;
29   
30    import org.apache.commons.lang3.RandomStringUtils;
31    import org.apache.commons.lang3.StringUtils;
32    import org.hibernate.Query;
33    import org.hibernate.Session;
34    import org.slf4j.Logger;
35    import org.slf4j.LoggerFactory;
36    import org.xwiki.annotation.event.AnnotationAddedEvent;
37    import org.xwiki.annotation.event.AnnotationDeletedEvent;
38    import org.xwiki.annotation.event.AnnotationUpdatedEvent;
39    import org.xwiki.bridge.event.DocumentCreatedEvent;
40    import org.xwiki.bridge.event.DocumentDeletedEvent;
41    import org.xwiki.bridge.event.DocumentUpdatedEvent;
42    import org.xwiki.configuration.ConfigurationSource;
43    import org.xwiki.model.reference.DocumentReference;
44    import org.xwiki.model.reference.EntityReferenceSerializer;
45    import org.xwiki.observation.EventListener;
46    import org.xwiki.observation.ObservationContext;
47    import org.xwiki.observation.ObservationManager;
48    import org.xwiki.observation.event.BeginFoldEvent;
49    import org.xwiki.observation.event.Event;
50    import org.xwiki.observation.remote.RemoteObservationManagerContext;
51   
52    import com.sun.syndication.feed.synd.SyndContentImpl;
53    import com.sun.syndication.feed.synd.SyndEntry;
54    import com.sun.syndication.feed.synd.SyndEntryImpl;
55    import com.sun.syndication.feed.synd.SyndFeed;
56    import com.sun.syndication.feed.synd.SyndFeedImpl;
57    import com.sun.syndication.io.SyndFeedOutput;
58    import com.xpn.xwiki.XWikiContext;
59    import com.xpn.xwiki.XWikiException;
60    import com.xpn.xwiki.doc.XWikiDocument;
61    import com.xpn.xwiki.internal.event.AttachmentAddedEvent;
62    import com.xpn.xwiki.internal.event.AttachmentDeletedEvent;
63    import com.xpn.xwiki.internal.event.AttachmentUpdatedEvent;
64    import com.xpn.xwiki.internal.event.CommentAddedEvent;
65    import com.xpn.xwiki.internal.event.CommentDeletedEvent;
66    import com.xpn.xwiki.internal.event.CommentUpdatedEvent;
67    import com.xpn.xwiki.plugin.activitystream.api.ActivityEvent;
68    import com.xpn.xwiki.plugin.activitystream.api.ActivityEventPriority;
69    import com.xpn.xwiki.plugin.activitystream.api.ActivityEventType;
70    import com.xpn.xwiki.plugin.activitystream.api.ActivityStream;
71    import com.xpn.xwiki.plugin.activitystream.api.ActivityStreamException;
72    import com.xpn.xwiki.plugin.activitystream.plugin.ActivityStreamPlugin;
73    import com.xpn.xwiki.store.XWikiHibernateStore;
74    import com.xpn.xwiki.web.Utils;
75   
76    /**
77    * Default implementation for {@link ActivityStream}.
78    *
79    * @version $Id: 675dedc03afb3494e85c9ddf91936d9aa65478bd $
80    */
81    @SuppressWarnings("serial")
 
82    public class ActivityStreamImpl implements ActivityStream, EventListener
83    {
84    /** Logging helper object. */
85    private static final Logger LOGGER = LoggerFactory.getLogger(ActivityStreamImpl.class);
86   
87    /**
88    * Key used to store the request ID in the context.
89    */
90    private static final String REQUEST_ID_CONTEXT_KEY = "activitystream_requestid";
91   
92    /**
93    * Character used as a separator in event IDs.
94    */
95    private static final String EVENT_ID_ELEMENTS_SEPARATOR = "-";
96   
97    /**
98    * The name of the listener.
99    */
100    private static final String LISTENER_NAME = "activitystream";
101   
102    /**
103    * Used to query for nested spaces of the specified space.
104    */
105    private static final String NESTED_SPACE_FORMAT = "%s.%%";
106   
107    /**
108    * The events to match.
109    */
110    private static final List<Event> LISTENER_EVENTS = new ArrayList<Event>()
111    {
 
112  1 toggle {
113  1 add(new DocumentCreatedEvent());
114  1 add(new DocumentUpdatedEvent());
115  1 add(new DocumentDeletedEvent());
116  1 add(new CommentAddedEvent());
117  1 add(new CommentDeletedEvent());
118  1 add(new CommentUpdatedEvent());
119  1 add(new AttachmentAddedEvent());
120  1 add(new AttachmentDeletedEvent());
121  1 add(new AttachmentUpdatedEvent());
122  1 add(new AnnotationAddedEvent());
123  1 add(new AnnotationDeletedEvent());
124  1 add(new AnnotationUpdatedEvent());
125    }
126    };
127   
128    /**
129    * Set fields related to the document which fired the event in the given event object.
130    *
131    * @param event the event to prepare
132    * @param doc document which fired the event
133    * @param context the XWiki context
134    */
 
135  64 toggle private void setEventDocumentRelatedInformation(ActivityEvent event, XWikiDocument doc, XWikiContext context)
136    {
137  64 if (doc != null) {
138  64 if (event.getStream() == null) {
139  0 event.setStream(getStreamName(doc.getSpace(), context));
140    }
141   
142  64 if (event.getSpace() == null) {
143  64 event.setSpace(doc.getSpace());
144    }
145   
146  64 if (event.getPage() == null) {
147  0 event.setPage(doc.getFullName());
148    }
149   
150  64 if (event.getUrl() == null) {
151    // Protection against NPEs, events can happen before the URL factory gets created.
152  64 if (context.getURLFactory() != null) {
153  61 event.setUrl(doc.getURL("view", context));
154    }
155    }
156    }
157    }
158   
159    /**
160    * Set fields in the given event object.
161    *
162    * @param event the event to prepare
163    * @param doc document which fired the event
164    * @param context the XWiki context
165    */
 
166  64 toggle protected void prepareEvent(ActivityEvent event, XWikiDocument doc, XWikiContext context)
167    {
168  64 if (event.getUser() == null) {
169  0 event.setUser(getSerializedReference(context.getUserReference()));
170    }
171   
172  64 if (event.getWiki() == null) {
173  0 event.setWiki(context.getWikiId());
174    }
175   
176  64 if (event.getApplication() == null) {
177  64 event.setApplication("xwiki");
178    }
179   
180  64 if (event.getDate() == null) {
181  0 event.setDate(new Date());
182    }
183   
184  64 if (event.getEventId() == null) {
185  64 event.setEventId(generateEventId(event, context));
186    }
187   
188  64 if (event.getRequestId() == null) {
189  64 event.setRequestId((String) context.get(REQUEST_ID_CONTEXT_KEY));
190    }
191   
192  64 setEventDocumentRelatedInformation(event, doc, context);
193    }
194   
195    /**
196    * Generate event ID for the given ID. Note that this method does not perform the set of the ID in the event object.
197    *
198    * @param event event to generate the ID for
199    * @param context the XWiki context
200    * @return the generated ID
201    */
 
202  64 toggle protected String generateEventId(ActivityEvent event, XWikiContext context)
203    {
204  64 String keySeparator = EVENT_ID_ELEMENTS_SEPARATOR;
205  64 String wikiSpaceSeparator = ":";
206   
207  64 String key =
208    event.getStream() + keySeparator + event.getApplication() + keySeparator + event.getWiki()
209    + wikiSpaceSeparator + event.getPage() + keySeparator + event.getType();
210  64 long hash = key.hashCode();
211  64 if (hash < 0) {
212  35 hash = -hash;
213    }
214   
215  64 String id =
216    "" + hash + keySeparator + event.getDate().getTime() + keySeparator
217    + RandomStringUtils.randomAlphanumeric(8);
218  64 if (context.get(REQUEST_ID_CONTEXT_KEY) == null) {
219  39 context.put(REQUEST_ID_CONTEXT_KEY, id);
220    }
221   
222  64 return id;
223    }
224   
225    /**
226    * @return a new instance of {@link ActivityEventImpl}.
227    */
 
228  0 toggle protected ActivityEvent newActivityEvent()
229    {
230  0 return new ActivityEventImpl();
231    }
232   
 
233  1 toggle @Override
234    public void init(XWikiContext context) throws XWikiException
235    {
236    // Listent to Events.
237  1 ObservationManager observationManager = Utils.getComponent(ObservationManager.class);
238  1 if (observationManager.getListener(getName()) == null) {
239  1 observationManager.addListener(this);
240    }
241    // Init activitystream cleaner.
242  1 ActivityStreamCleaner.getInstance().init(context);
243    }
244   
 
245  64 toggle @Override
246    public String getStreamName(String space, XWikiContext context)
247    {
248  64 return space;
249    }
250   
 
251  0 toggle @Override
252    public void addActivityEvent(ActivityEvent event, XWikiContext context) throws ActivityStreamException
253    {
254  0 addActivityEvent(event, null, context);
255    }
256   
257    /**
258    * This method determine if events must be store in the local wiki. If the activitystream is set not to store events
259    * in the main wiki, the method will return true. If events are stored in the main wiki, the method retrieves the
260    * 'platform.plugin.activitystream.uselocalstore' configuration option. If the option is not found the method
261    * returns true (default behavior).
262    *
263    * @param context the XWiki context
264    * @return true if the activity stream is configured to store events in the main wiki, false otherwise
265    */
 
266  64 toggle private boolean useLocalStore(XWikiContext context)
267    {
268  64 if (!useMainStore(context)) {
269    // If the main store is disabled, force local store.
270  64 return true;
271    }
272   
273  0 ActivityStreamPlugin plugin =
274    (ActivityStreamPlugin) context.getWiki().getPlugin(ActivityStreamPlugin.PLUGIN_NAME, context);
275  0 return Integer.parseInt(plugin.getActivityStreamPreference("uselocalstore", "1", context)) == 1;
276    }
277   
278    /**
279    * This method determine if events must be store in the main wiki. If the current wiki is the main wiki, this method
280    * returns false, otherwise if retrieves the 'platform.plugin.activitystream.usemainstore' configuration option. If
281    * the option is not found the method returns true (default behavior).
282    *
283    * @param context the XWiki context
284    * @return true if the activity stream is configured to store events in the main wiki, false otherwise
285    */
 
286  128 toggle private boolean useMainStore(XWikiContext context)
287    {
288  128 if (context.isMainWiki()) {
289    // We're in the main database, we don't have to store the data twice.
290  128 return false;
291    }
292   
293  0 ActivityStreamPlugin plugin =
294    (ActivityStreamPlugin) context.getWiki().getPlugin(ActivityStreamPlugin.PLUGIN_NAME, context);
295  0 return Integer.parseInt(plugin.getActivityStreamPreference("usemainstore", "1", context)) == 1;
296    }
297   
298    /**
299    * @param event event to add to the stream
300    * @param doc which fired the event
301    * @param context the XWiki context
302    * @throws ActivityStreamException if the addition to the stream fails
303    */
 
304  64 toggle public void addActivityEvent(ActivityEvent event, XWikiDocument doc, XWikiContext context)
305    throws ActivityStreamException
306    {
307  64 prepareEvent(event, doc, context);
308   
309  64 if (useLocalStore(context)) {
310    // store event in the local database
311  64 XWikiHibernateStore localHibernateStore = context.getWiki().getHibernateStore();
312  64 try {
313  64 localHibernateStore.beginTransaction(context);
314  64 Session session = localHibernateStore.getSession(context);
315  64 session.save(event);
316  64 localHibernateStore.endTransaction(context, true);
317    } catch (XWikiException e) {
318  0 localHibernateStore.endTransaction(context, false);
319    }
320    }
321   
322  64 if (useMainStore(context)) {
323    // store event in the main database
324  0 String oriDatabase = context.getWikiId();
325  0 context.setWikiId(context.getMainXWiki());
326  0 XWikiHibernateStore mainHibernateStore = context.getWiki().getHibernateStore();
327  0 try {
328  0 mainHibernateStore.beginTransaction(context);
329  0 Session session = mainHibernateStore.getSession(context);
330  0 session.save(event);
331  0 mainHibernateStore.endTransaction(context, true);
332    } catch (XWikiException e) {
333  0 mainHibernateStore.endTransaction(context, false);
334    } finally {
335  0 context.setWikiId(oriDatabase);
336    }
337    }
338    }
339   
 
340  0 toggle @Override
341    public void addActivityEvent(String streamName, String type, String title, XWikiContext context)
342    throws ActivityStreamException
343    {
344  0 addActivityEvent(streamName, type, title, null, context);
345    }
346   
 
347  0 toggle @Override
348    public void addActivityEvent(String streamName, String type, String title, List<String> params, XWikiContext context)
349    throws ActivityStreamException
350    {
351  0 ActivityEvent event = newActivityEvent();
352  0 event.setStream(streamName);
353  0 event.setType(type);
354  0 event.setTitle(title);
355  0 event.setBody(title);
356  0 event.setParams(params);
357  0 addActivityEvent(event, context);
358    }
359   
 
360  0 toggle @Override
361    public void addDocumentActivityEvent(String streamName, XWikiDocument doc, String type, String title,
362    XWikiContext context) throws ActivityStreamException
363    {
364  0 addDocumentActivityEvent(streamName, doc, type, ActivityEventPriority.NOTIFICATION, title, null, context);
365    }
366   
 
367  0 toggle @Override
368    public void addDocumentActivityEvent(String streamName, XWikiDocument doc, String type, int priority, String title,
369    XWikiContext context) throws ActivityStreamException
370    {
371  0 addDocumentActivityEvent(streamName, doc, type, priority, title, null, context);
372    }
373   
 
374  64 toggle @Override
375    public void addDocumentActivityEvent(String streamName, XWikiDocument doc, String type, String title,
376    List<String> params, XWikiContext context) throws ActivityStreamException
377    {
378  64 addDocumentActivityEvent(streamName, doc, type, ActivityEventPriority.NOTIFICATION, title, params, context);
379    }
380   
 
381  64 toggle @Override
382    public void addDocumentActivityEvent(String streamName, XWikiDocument doc, String type, int priority, String title,
383    List<String> params, XWikiContext context) throws ActivityStreamException
384    {
385  64 ActivityEventImpl event = new ActivityEventImpl();
386  64 event.setStream(streamName);
387  64 event.setPage(doc.getFullName());
388  64 if (doc.getDatabase() != null) {
389  64 event.setWiki(doc.getDatabase());
390    }
391  64 event.setDate(doc.getDate());
392  64 event.setPriority(priority);
393  64 event.setType(type);
394  64 event.setTitle(title);
395  64 event.setBody(title);
396  64 event.setVersion(doc.getVersion());
397  64 event.setParams(params);
398    // This might be wrong once non-altering events will be logged.
399  64 event.setUser(getSerializedReference(doc.getAuthorReference()));
400  64 event.setHidden(doc.isHidden());
401  64 addActivityEvent(event, doc, context);
402    }
403   
404    /**
405    * @param event the event
406    * @param bTransaction true if inside a transaction
407    * @param context the XWiki Context
408    * @return the event
409    * @throws ActivityStreamException
410    */
 
411  0 toggle private ActivityEventImpl loadActivityEvent(ActivityEvent event, boolean bTransaction, XWikiContext context)
412    throws ActivityStreamException
413    {
414  0 boolean bTransactionMutable = bTransaction;
415  0 ActivityEventImpl act = null;
416  0 String eventId = event.getEventId();
417   
418  0 if (useLocalStore(context)) {
419    // load event from the local database
420  0 XWikiHibernateStore hibstore = context.getWiki().getHibernateStore();
421  0 try {
422  0 if (bTransactionMutable) {
423  0 hibstore.checkHibernate(context);
424  0 bTransactionMutable = hibstore.beginTransaction(false, context);
425    }
426  0 Session session = hibstore.getSession(context);
427  0 Query query =
428    session
429    .createQuery("select act.eventId from ActivityEventImpl as act where act.eventId = :eventId");
430  0 query.setString("eventId", eventId);
431  0 if (query.uniqueResult() != null) {
432  0 act = new ActivityEventImpl();
433  0 session.load(act, eventId);
434    }
435   
436  0 if (bTransactionMutable) {
437  0 hibstore.endTransaction(context, false, false);
438    }
439    } catch (Exception e) {
440  0 throw new ActivityStreamException();
441    } finally {
442  0 try {
443  0 if (bTransactionMutable) {
444  0 hibstore.endTransaction(context, false, false);
445    }
446    } catch (Exception e) {
447    // Do nothing.
448    }
449    }
450  0 } else if (useMainStore(context)) {
451    // load event from the main database
452  0 String oriDatabase = context.getWikiId();
453  0 context.setWikiId(context.getMainXWiki());
454  0 XWikiHibernateStore hibstore = context.getWiki().getHibernateStore();
455  0 try {
456  0 if (bTransactionMutable) {
457  0 hibstore.checkHibernate(context);
458  0 bTransactionMutable = hibstore.beginTransaction(false, context);
459    }
460  0 Session session = hibstore.getSession(context);
461  0 Query query =
462    session
463    .createQuery("select act.eventId from ActivityEventImpl as act where act.eventId = :eventId");
464  0 query.setString("eventId", eventId);
465  0 if (query.uniqueResult() != null) {
466  0 act = new ActivityEventImpl();
467  0 session.load(act, eventId);
468    }
469   
470  0 if (bTransactionMutable) {
471  0 hibstore.endTransaction(context, false, false);
472    }
473    } catch (Exception e) {
474  0 throw new ActivityStreamException();
475    } finally {
476  0 context.setWikiId(oriDatabase);
477  0 try {
478  0 if (bTransactionMutable) {
479  0 hibstore.endTransaction(context, false, false);
480    }
481    } catch (Exception e) {
482    // Do nothing.
483    }
484    }
485    }
486   
487  0 return act;
488    }
489   
 
490  0 toggle @Override
491    public void deleteActivityEvent(ActivityEvent event, XWikiContext context) throws ActivityStreamException
492    {
493  0 boolean bTransaction = true;
494  0 ActivityEventImpl evImpl = loadActivityEvent(event, true, context);
495  0 String oriDatabase = context.getWikiId();
496   
497  0 if (useLocalStore(context)) {
498  0 XWikiHibernateStore hibstore;
499   
500    // delete event from the local database
501  0 if (context.getWikiId().equals(event.getWiki())) {
502  0 hibstore = context.getWiki().getHibernateStore();
503    } else {
504  0 context.setWikiId(event.getWiki());
505  0 hibstore = context.getWiki().getHibernateStore();
506    }
507   
508  0 try {
509  0 if (bTransaction) {
510  0 hibstore.checkHibernate(context);
511  0 bTransaction = hibstore.beginTransaction(context);
512    }
513   
514  0 Session session = hibstore.getSession(context);
515   
516  0 session.delete(evImpl);
517   
518  0 if (bTransaction) {
519  0 hibstore.endTransaction(context, true);
520    }
521   
522    } catch (XWikiException e) {
523  0 throw new ActivityStreamException();
524    } finally {
525  0 try {
526  0 if (bTransaction) {
527  0 hibstore.endTransaction(context, false);
528    }
529  0 if (context.getWikiId().equals(oriDatabase)) {
530  0 context.setWikiId(oriDatabase);
531    }
532    } catch (Exception e) {
533    // Do nothing.
534    }
535    }
536    }
537   
538  0 if (useMainStore(context)) {
539    // delete event from the main database
540  0 context.setWikiId(context.getMainXWiki());
541  0 XWikiHibernateStore hibstore = context.getWiki().getHibernateStore();
542  0 try {
543  0 if (bTransaction) {
544  0 hibstore.checkHibernate(context);
545  0 bTransaction = hibstore.beginTransaction(context);
546    }
547   
548  0 Session session = hibstore.getSession(context);
549   
550  0 session.delete(evImpl);
551   
552  0 if (bTransaction) {
553  0 hibstore.endTransaction(context, true);
554    }
555   
556    } catch (XWikiException e) {
557  0 throw new ActivityStreamException();
558    } finally {
559  0 try {
560  0 if (bTransaction) {
561  0 hibstore.endTransaction(context, false);
562    }
563  0 context.setWikiId(oriDatabase);
564    } catch (Exception e) {
565    // Do nothing
566    }
567    }
568    }
569    }
570   
 
571  0 toggle @Override
572    public List<ActivityEvent> searchEvents(String hql, boolean filter, int nb, int start, XWikiContext context)
573    throws ActivityStreamException
574    {
575  0 return searchEvents("", hql, filter, nb, start, context);
576    }
577   
 
578  0 toggle @Override
579    public List<ActivityEvent> searchEvents(String hql, boolean filter, boolean globalSearch, int nb, int start,
580    XWikiContext context) throws ActivityStreamException
581    {
582  0 return searchEvents("", hql, filter, globalSearch, nb, start, context);
583    }
584   
 
585  1 toggle @Override
586    public List<ActivityEvent> searchEvents(String hql, boolean filter, boolean globalSearch, int nb, int start,
587    List<Object> parameterValues, XWikiContext context) throws ActivityStreamException
588    {
589  1 return searchEvents("", hql, filter, globalSearch, nb, start, parameterValues, context);
590    }
591   
 
592  0 toggle @Override
593    public List<ActivityEvent> searchEvents(String fromHql, String hql, boolean filter, int nb, int start,
594    XWikiContext context) throws ActivityStreamException
595    {
596  0 return searchEvents(fromHql, hql, filter, nb, start, null, context);
597    }
598   
 
599  0 toggle @Override
600    public List<ActivityEvent> searchEvents(String fromHql, String hql, boolean filter, boolean globalSearch, int nb,
601    int start, XWikiContext context) throws ActivityStreamException
602    {
603  0 return searchEvents(fromHql, hql, filter, globalSearch, nb, start, null, context);
604    }
605   
 
606  24 toggle @Override
607    public List<ActivityEvent> searchEvents(String fromHql, String hql, boolean filter, int nb, int start,
608    List<Object> parameterValues, XWikiContext context) throws ActivityStreamException
609    {
610  24 return searchEvents(fromHql, hql, filter, false, nb, start, parameterValues, context);
611    }
612   
613    /**
614    * This method will add a where clause to filter events fired from hidden documents. The clause will not be added to
615    * the query if the user has specified that he wish to see hidden documents in his profile. If the clause is added
616    * this method will also add a 'where' to the query if it is missing.
617    *
618    * @param query The query to add the filter to
619    */
 
620  111 toggle private void addHiddenEventsFilter(StringBuffer query)
621    {
622  111 ConfigurationSource source = Utils.getComponent(ConfigurationSource.class, "user");
623  111 Integer preference = source.getProperty("displayHiddenDocuments", Integer.class);
624  111 if (preference == null || preference != 1) {
625  111 if (!query.toString().contains(" where ")) {
626  14 query.append(" where ");
627    }
628  111 query.append(" (act.hidden <> true or act.hidden is null) and ");
629    }
630    }
631   
632    /**
633    * This method will add the passed optional where clause to the given query if the optional clause is not an empty
634    * string nor null. If the clause is added this method will also add a 'where' to the query if it is missing.
635    *
636    * @param query The query to add the where clause to
637    * @param optionalWhereClause The optional where clause to add
638    */
 
639  14 toggle private void addOptionalEventsFilter(StringBuffer query, String optionalWhereClause)
640    {
641  14 if (StringUtils.isNotBlank(optionalWhereClause)) {
642  14 if (!query.toString().contains(" where ")) {
643  0 query.append(" where ");
644    }
645  14 query.append(optionalWhereClause);
646    }
647    }
648   
 
649  97 toggle @Override
650    public List<ActivityEvent> searchEvents(String fromHql, String hql, boolean filter, boolean globalSearch, int nb,
651    int start, List<Object> parameterValues, XWikiContext context) throws ActivityStreamException
652    {
653  97 StringBuffer searchHql = new StringBuffer();
654  97 List<ActivityEvent> results;
655   
656  97 if (filter) {
657  0 searchHql.append("select act from ActivityEventImpl as act, ActivityEventImpl as act2 ");
658  0 searchHql.append(fromHql);
659  0 searchHql.append(" where act.eventId=act2.eventId and ");
660  0 addHiddenEventsFilter(searchHql);
661  0 searchHql.append(hql);
662  0 searchHql.append(" group by act.requestId having (act.priority)=max(act2.priority) order by act.date desc");
663    } else {
664  97 searchHql.append("select act from ActivityEventImpl as act ");
665  97 searchHql.append(fromHql);
666  97 searchHql.append(" where ");
667  97 addHiddenEventsFilter(searchHql);
668  97 searchHql.append(hql);
669  97 searchHql.append(" order by act.date desc");
670    }
671   
672  97 if (globalSearch) {
673    // Search in the main database
674  1 String oriDatabase = context.getWikiId();
675  1 try {
676  1 context.setWikiId(context.getMainXWiki());
677  1 results =
678    context.getWiki().getStore().search(searchHql.toString(), nb, start, parameterValues, context);
679    } catch (XWikiException e) {
680  0 throw new ActivityStreamException(e);
681    } finally {
682  1 context.setWikiId(oriDatabase);
683    }
684    } else {
685  96 try {
686    // Search in the local database
687  96 results =
688    context.getWiki().getStore().search(searchHql.toString(), nb, start, parameterValues, context);
689    } catch (XWikiException e) {
690  0 throw new ActivityStreamException(e);
691    }
692    }
693   
694  97 return results;
695    }
696   
 
697  0 toggle @Override
698    public List<ActivityEvent> getEvents(boolean filter, int nb, int start, XWikiContext context)
699    throws ActivityStreamException
700    {
701  0 return searchEvents("1=1", filter, nb, start, context);
702    }
703   
 
704  0 toggle @Override
705    public List<ActivityEvent> getEventsForSpace(String space, boolean filter, int nb, int start, XWikiContext context)
706    throws ActivityStreamException
707    {
708  0 List<Object> parameterValues = Arrays.<Object> asList(space, String.format(NESTED_SPACE_FORMAT, space));
709  0 return searchEvents("act.space=? OR act.space LIKE ?", filter, false, nb, start, parameterValues, context);
710    }
711   
 
712  0 toggle @Override
713    public List<ActivityEvent> getEventsForUser(String user, boolean filter, int nb, int start, XWikiContext context)
714    throws ActivityStreamException
715    {
716  0 List<Object> parameterValues = Arrays.<Object> asList(user);
717  0 return searchEvents("act.user=?", filter, false, nb, start, parameterValues, context);
718    }
719   
 
720  0 toggle @Override
721    public List<ActivityEvent> getEvents(String stream, boolean filter, int nb, int start, XWikiContext context)
722    throws ActivityStreamException
723    {
724  0 List<Object> parameterValues = Arrays.<Object> asList(stream);
725  0 return searchEvents("act.stream=?", filter, false, nb, start, parameterValues, context);
726    }
727   
 
728  0 toggle @Override
729    public List<ActivityEvent> getEventsForSpace(String stream, String space, boolean filter, int nb, int start,
730    XWikiContext context) throws ActivityStreamException
731    {
732  0 List<Object> parameterValues = Arrays.<Object> asList(stream, space, String.format(NESTED_SPACE_FORMAT, space));
733  0 return searchEvents("act.stream=? AND (act.space=? OR act.space LIKE ?)", filter, false, nb, start,
734    parameterValues, context);
735    }
736   
 
737  0 toggle @Override
738    public List<ActivityEvent> getEventsForUser(String stream, String user, boolean filter, int nb, int start,
739    XWikiContext context) throws ActivityStreamException
740    {
741  0 List<Object> parameterValues = Arrays.<Object> asList(stream, user);
742  0 return searchEvents("act.stream=? AND act.user=?", filter, false, nb, start, parameterValues, context);
743    }
744   
 
745  0 toggle @Override
746    public SyndEntry getFeedEntry(ActivityEvent event, XWikiContext context)
747    {
748  0 return getFeedEntry(event, "", context);
749    }
750   
 
751  0 toggle @Override
752    public SyndEntry getFeedEntry(ActivityEvent event, String suffix, XWikiContext context)
753    {
754  0 SyndEntry entry = new SyndEntryImpl();
755  0 String user = event.getUser();
756  0 String displayUser = context.getWiki().getUserName(user, null, false, context);
757  0 entry.setAuthor(displayUser);
758  0 event.setTitle(event.getTitle() + ".rss.title" + suffix);
759  0 entry.setTitle(event.getDisplayTitle(context));
760  0 event.setBody(event.getBody() + ".rss.body" + suffix);
761  0 SyndContentImpl sc = new SyndContentImpl();
762  0 sc.setValue(event.getDisplayBody(context));
763  0 sc.setType("text/html");
764  0 entry.setDescription(sc);
765  0 String url;
766  0 try {
767  0 url = (new URL(context.getURL(), event.getUrl())).toString();
768    } catch (MalformedURLException e) {
769  0 url = event.getUrl();
770    }
771  0 entry.setLink(url);
772  0 entry.setPublishedDate(event.getDate());
773  0 entry.setUpdatedDate(event.getDate());
774  0 return entry;
775    }
776   
 
777  0 toggle @Override
778    public SyndFeed getFeed(List<ActivityEvent> events, XWikiContext context)
779    {
780  0 return getFeed(events, "", context);
781    }
782   
 
783  0 toggle @Override
784    public SyndFeed getFeed(List<ActivityEvent> events, String suffix, XWikiContext context)
785    {
786  0 SyndFeed feed = new SyndFeedImpl();
787  0 List<SyndEntry> entries = new ArrayList<SyndEntry>();
788  0 for (ActivityEvent event : events) {
789  0 SyndEntry entry = getFeedEntry(event, suffix, context);
790  0 entries.add(entry);
791    }
792  0 feed.setEntries(entries);
793  0 return feed;
794    }
795   
 
796  0 toggle @Override
797    public SyndFeed getFeed(List<ActivityEvent> events, String author, String title, String description,
798    String copyright, String encoding, String url, XWikiContext context)
799    {
800  0 return getFeed(events, author, title, description, copyright, encoding, url, "", context);
801    }
802   
 
803  0 toggle @Override
804    public SyndFeed getFeed(List<ActivityEvent> events, String author, String title, String description,
805    String copyright, String encoding, String url, String suffix, XWikiContext context)
806    {
807  0 SyndFeed feed = getFeed(events, suffix, context);
808  0 feed.setAuthor(author);
809  0 feed.setDescription(description);
810  0 feed.setCopyright(copyright);
811  0 feed.setEncoding(encoding);
812  0 feed.setLink(url);
813  0 feed.setTitle(title);
814  0 return feed;
815    }
816   
 
817  0 toggle @Override
818    public String getFeedOutput(List<ActivityEvent> events, String author, String title, String description,
819    String copyright, String encoding, String url, String type, XWikiContext context)
820    {
821  0 return getFeedOutput(events, author, title, description, copyright, encoding, url, type, "", context);
822    }
823   
 
824  0 toggle @Override
825    public String getFeedOutput(List<ActivityEvent> events, String author, String title, String description,
826    String copyright, String encoding, String url, String type, String suffix, XWikiContext context)
827    {
828  0 SyndFeed feed = getFeed(events, author, title, description, copyright, encoding, url, suffix, context);
829  0 return getFeedOutput(feed, type);
830    }
831   
 
832  0 toggle @Override
833    public String getFeedOutput(SyndFeed feed, String type)
834    {
835  0 feed.setFeedType(type);
836  0 StringWriter writer = new StringWriter();
837  0 SyndFeedOutput output = new SyndFeedOutput();
838  0 try {
839  0 output.output(feed, writer);
840  0 writer.close();
841  0 return writer.toString();
842    } catch (Exception e) {
843  0 return "";
844    }
845    }
846   
 
847  1 toggle @Override
848    public List<Event> getEvents()
849    {
850  1 return LISTENER_EVENTS;
851    }
852   
 
853  20 toggle @Override
854    public String getName()
855    {
856  20 return LISTENER_NAME;
857    }
858   
859    private static BeginFoldEvent IGNORED_EVENTS = new BeginFoldEvent()
860    {
 
861  62 toggle @Override
862    public boolean matches(Object otherEvent)
863    {
864  62 return otherEvent instanceof BeginFoldEvent;
865    }
866    };
867   
 
868  64 toggle @Override
869    public void onEvent(Event event, Object source, Object data)
870    {
871    // Do not record some ignored events
872  64 ObservationContext observationContext = Utils.getComponent(ObservationContext.class);
873  64 if (observationContext.isIn(IGNORED_EVENTS)) {
874  0 return;
875    }
876   
877  64 XWikiDocument currentDoc = (XWikiDocument) source;
878  64 XWikiDocument originalDoc = currentDoc.getOriginalDocument();
879  64 XWikiContext context = (XWikiContext) data;
880  64 String wiki = context.getWikiId();
881  64 String msgPrefix = "activitystream.event.";
882  64 String streamName = getStreamName(currentDoc.getSpace(), context);
883   
884    // If we haven't found a stream to store the event or if both currentDoc and originalDoc are null: exit
885  64 if (streamName == null) {
886  0 return;
887    }
888   
889    // Take events into account only once in a cluster
890  64 if (!Utils.getComponent(RemoteObservationManagerContext.class).isRemoteState()) {
891  64 String eventType;
892  64 String displayTitle;
893  64 String additionalIdentifier = null;
894   
895  64 if (event instanceof DocumentCreatedEvent) {
896  12 eventType = ActivityEventType.CREATE;
897  12 displayTitle = currentDoc.getRenderedTitle(context);
898  52 } else if (event instanceof DocumentUpdatedEvent) {
899  49 eventType = ActivityEventType.UPDATE;
900  49 displayTitle = originalDoc.getRenderedTitle(context);
901  3 } else if (event instanceof DocumentDeletedEvent) {
902  2 eventType = ActivityEventType.DELETE;
903  2 displayTitle = originalDoc.getRenderedTitle(context);
904    // When we receive a DELETE event, the given document is blank and does not have version & hidden tag
905    // properly set.
906  2 currentDoc.setVersion(originalDoc.getVersion());
907  2 currentDoc.setHidden(originalDoc.isHidden());
908  1 } else if (event instanceof CommentAddedEvent) {
909  0 eventType = ActivityEventType.ADD_COMMENT;
910  0 displayTitle = currentDoc.getRenderedTitle(context);
911  0 additionalIdentifier = ((CommentAddedEvent) event).getIdentifier();
912  1 } else if (event instanceof CommentDeletedEvent) {
913  0 eventType = ActivityEventType.DELETE_COMMENT;
914  0 displayTitle = currentDoc.getRenderedTitle(context);
915  0 additionalIdentifier = ((CommentDeletedEvent) event).getIdentifier();
916  1 } else if (event instanceof CommentUpdatedEvent) {
917  0 eventType = ActivityEventType.UPDATE_COMMENT;
918  0 displayTitle = currentDoc.getRenderedTitle(context);
919  0 additionalIdentifier = ((CommentUpdatedEvent) event).getIdentifier();
920  1 } else if (event instanceof AttachmentAddedEvent) {
921  0 eventType = ActivityEventType.ADD_ATTACHMENT;
922  0 displayTitle = currentDoc.getRenderedTitle(context);
923  0 additionalIdentifier = ((AttachmentAddedEvent) event).getName();
924  1 } else if (event instanceof AttachmentDeletedEvent) {
925  1 eventType = ActivityEventType.DELETE_ATTACHMENT;
926  1 displayTitle = currentDoc.getRenderedTitle(context);
927  1 additionalIdentifier = ((AttachmentDeletedEvent) event).getName();
928  0 } else if (event instanceof AttachmentUpdatedEvent) {
929  0 eventType = ActivityEventType.UPDATE_ATTACHMENT;
930  0 displayTitle = currentDoc.getRenderedTitle(context);
931  0 additionalIdentifier = ((AttachmentUpdatedEvent) event).getName();
932  0 } else if (event instanceof AnnotationAddedEvent) {
933  0 eventType = ActivityEventType.ADD_ANNOTATION;
934  0 displayTitle = currentDoc.getRenderedTitle(context);
935  0 additionalIdentifier = ((AnnotationAddedEvent) event).getIdentifier();
936  0 } else if (event instanceof AnnotationDeletedEvent) {
937  0 eventType = ActivityEventType.DELETE_ANNOTATION;
938  0 displayTitle = currentDoc.getRenderedTitle(context);
939  0 additionalIdentifier = ((AnnotationDeletedEvent) event).getIdentifier();
940    } else { // update annotation
941  0 eventType = ActivityEventType.UPDATE_ANNOTATION;
942  0 displayTitle = currentDoc.getRenderedTitle(context);
943  0 additionalIdentifier = ((AnnotationUpdatedEvent) event).getIdentifier();
944    }
945   
946  64 List<String> params = new ArrayList<String>();
947  64 params.add(displayTitle);
948  64 if (additionalIdentifier != null) {
949  1 params.add(additionalIdentifier);
950    }
951   
952  64 try {
953  64 addDocumentActivityEvent(streamName, currentDoc, eventType, msgPrefix + eventType, params, context);
954    } catch (ActivityStreamException e) {
955  0 LOGGER.error("Exception while trying to add a document activity event, updated document: [" + wiki
956    + ":" + currentDoc + "]");
957    }
958    }
959    }
960   
 
961  72 toggle @Override
962    public List<ActivityEvent> getRelatedEvents(ActivityEvent event, XWikiContext context)
963    throws ActivityStreamException
964    {
965  72 List<Object> params = new ArrayList<Object>();
966  72 params.add(event.getRequestId());
967   
968  72 return this.searchEvents("", "act.requestId= ? ", false, false, 0, 0, params, context);
969    }
970   
 
971  0 toggle @Override
972    public List<Object[]> searchUniquePages(String optionalWhereClause, int maxItems, int startAt, XWikiContext context)
973    throws ActivityStreamException
974    {
975  0 return searchUniquePages(optionalWhereClause, null, maxItems, startAt, context);
976    }
977   
 
978  0 toggle @Override
979    public List<Object[]> searchUniquePages(String optionalWhereClause, List<Object> parametersValues, int maxItems,
980    int startAt, XWikiContext context) throws ActivityStreamException
981    {
982  0 StringBuffer searchHql = new StringBuffer();
983  0 List<Object[]> results;
984   
985  0 searchHql.append("select act.page, max(act.date) from ActivityEventImpl as act");
986  0 addHiddenEventsFilter(searchHql);
987  0 addOptionalEventsFilter(searchHql, optionalWhereClause);
988  0 searchHql.append(" group by act.page order by 2 desc");
989   
990  0 String originalDatabase = context.getWikiId();
991  0 try {
992  0 context.setWikiId(context.getMainXWiki());
993  0 results =
994    context.getWiki().getStore().search(searchHql.toString(), maxItems, startAt, parametersValues, context);
995    } catch (XWikiException e) {
996  0 throw new ActivityStreamException(e);
997    } finally {
998  0 context.setWikiId(originalDatabase);
999    }
1000   
1001  0 return results;
1002    }
1003   
 
1004  0 toggle @Override
1005    public List<Object[]> searchDailyPages(String optionalWhereClause, int maxItems, int startAt, XWikiContext context)
1006    throws ActivityStreamException
1007    {
1008  0 return searchDailyPages(optionalWhereClause, null, maxItems, startAt, context);
1009    }
1010   
 
1011  14 toggle @Override
1012    public List<Object[]> searchDailyPages(String optionalWhereClause, List<Object> parametersValues, int maxItems,
1013    int startAt, XWikiContext context) throws ActivityStreamException
1014    {
1015  14 StringBuffer searchHql = new StringBuffer();
1016  14 List<Object[]> results = new ArrayList<Object[]>();
1017   
1018  14 searchHql.append("select year(act.date), month(act.date), day(act.date), act.page, max(act.date), act.wiki "
1019    + "from ActivityEventImpl as act");
1020  14 addHiddenEventsFilter(searchHql);
1021  14 addOptionalEventsFilter(searchHql, optionalWhereClause);
1022  14 searchHql.append(" group by year(act.date), month(act.date), day(act.date), act.page, act.wiki "
1023    + "order by 5 desc");
1024   
1025  14 String originalDatabase = context.getWikiId();
1026  14 try {
1027  14 context.setWikiId(context.getMainXWiki());
1028  14 List<Object[]> rawResults =
1029    context.getWiki().getStore().search(searchHql.toString(), maxItems, startAt, parametersValues, context);
1030  14 for (Object[] rawResult : rawResults) {
1031  24 results.add(new Object[] { rawResult[3], rawResult[4], rawResult[5] });
1032    }
1033    } catch (XWikiException e) {
1034  0 throw new ActivityStreamException(e);
1035    } finally {
1036  14 context.setWikiId(originalDatabase);
1037    }
1038   
1039  14 return results;
1040    }
1041   
1042    /**
1043    * @param documentReference to be serialized
1044    * @return the default (absolute) string serialized document reference
1045    */
 
1046  64 toggle private static String getSerializedReference(DocumentReference documentReference)
1047    {
1048  64 EntityReferenceSerializer<String> serializer = Utils.getComponent(EntityReferenceSerializer.TYPE_STRING);
1049  64 String stringSerialization = serializer.serialize(documentReference);
1050   
1051  64 return stringSerialization;
1052    }
1053    }