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 java.io.Serializable;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.MissingResourceException;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033import javax.persistence.EntityManager;
034import javax.persistence.Query;
035
036import org.apache.commons.lang.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.nuxeo.common.utils.i18n.I18NUtils;
040import org.nuxeo.ecm.core.api.NuxeoException;
041import org.nuxeo.ecm.core.persistence.PersistenceProvider;
042import org.nuxeo.ecm.core.persistence.PersistenceProviderFactory;
043import org.nuxeo.ecm.core.repository.RepositoryInitializationHandler;
044import org.nuxeo.runtime.api.Framework;
045import org.nuxeo.runtime.model.ComponentContext;
046import org.nuxeo.runtime.model.ComponentInstance;
047import org.nuxeo.runtime.model.DefaultComponent;
048
049/**
050 * Default implementation of {@link ActivityStreamService}.
051 *
052 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
053 * @since 5.5
054 */
055public class ActivityStreamServiceImpl extends DefaultComponent implements ActivityStreamService {
056
057    private static final Log log = LogFactory.getLog(ActivityStreamServiceImpl.class);
058
059    public static final String ACTIVITIES_PROVIDER = "nxactivities";
060
061    public static final String ACTIVITY_STREAM_FILTER_EP = "activityStreamFilters";
062
063    /**
064     * @deprecated since 5.6. Use {@link #ACTIVITY_VERBS_EP}.
065     */
066    @Deprecated
067    public static final String ACTIVITY_MESSAGE_LABELS_EP = "activityMessageLabels";
068
069    public static final String ACTIVITY_STREAMS_EP = "activityStreams";
070
071    public static final String ACTIVITY_VERBS_EP = "activityVerbs";
072
073    public static final String ACTIVITY_LINK_BUILDERS_EP = "activityLinkBuilders";
074
075    public static final String ACTIVITY_UPGRADERS_EP = "activityUpgraders";
076
077    protected final ThreadLocal<EntityManager> localEntityManager = new ThreadLocal<EntityManager>();
078
079    protected final Map<String, ActivityStreamFilter> activityStreamFilters = new HashMap<String, ActivityStreamFilter>();
080
081    protected ActivityStreamRegistry activityStreamRegistry;
082
083    protected ActivityVerbRegistry activityVerbRegistry;
084
085    protected ActivityLinkBuilderRegistry activityLinkBuilderRegistry;
086
087    protected ActivityUpgraderRegistry activityUpgraderRegistry;
088
089    protected PersistenceProvider persistenceProvider;
090
091    protected RepositoryInitializationHandler initializationHandler;
092
093    public void upgradeActivities() {
094        for (final ActivityUpgrader upgrader : activityUpgraderRegistry.getOrderedActivityUpgraders()) {
095            try {
096                getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunVoid() {
097                    @Override
098                    public void runWith(EntityManager em) {
099                        upgradeActivities(em, upgrader);
100                    }
101                });
102            } catch (NuxeoException e) {
103                log.error(String.format("Error while running '%s' activity upgrader: %s", upgrader.getName(),
104                        e.getMessage()));
105                log.debug(e, e);
106            }
107        }
108    }
109
110    protected void upgradeActivities(EntityManager em, ActivityUpgrader upgrader) {
111        try {
112            localEntityManager.set(em);
113            upgrader.doUpgrade(this);
114        } finally {
115            localEntityManager.remove();
116        }
117    }
118
119    @Override
120    public ActivitiesList query(String filterId, final Map<String, Serializable> parameters) {
121        return query(filterId, parameters, 0, 0);
122    }
123
124    @Override
125    public ActivitiesList query(String filterId, final Map<String, Serializable> parameters, final long offset,
126            final long limit) {
127        if (ALL_ACTIVITIES.equals(filterId)) {
128            return queryAll(offset, limit);
129        }
130
131        final ActivityStreamFilter filter = activityStreamFilters.get(filterId);
132        if (filter == null) {
133            throw new NuxeoException(String.format("Unable to retrieve '%s' ActivityStreamFilter", filterId));
134        }
135
136        return query(filter, parameters, offset, limit);
137    }
138
139    protected ActivitiesList query(final ActivityStreamFilter filter, final Map<String, Serializable> parameters,
140            final long offset, final long limit) {
141        return getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunCallback<ActivitiesList>() {
142            @Override
143            public ActivitiesList runWith(EntityManager em) {
144                return query(em, filter, parameters, offset, limit);
145            }
146        });
147    }
148
149    protected ActivitiesList query(EntityManager em, ActivityStreamFilter filter, Map<String, Serializable> parameters,
150            long offset, long limit) {
151        try {
152            localEntityManager.set(em);
153            return filter.query(this, parameters, offset, limit);
154        } finally {
155            localEntityManager.remove();
156        }
157
158    }
159
160    protected ActivitiesList queryAll(final long offset, final long limit) {
161        return getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunCallback<ActivitiesList>() {
162            @Override
163            public ActivitiesList runWith(EntityManager em) {
164                return queryAll(em, offset, limit);
165            }
166        });
167    }
168
169    @SuppressWarnings("unchecked")
170    protected ActivitiesList queryAll(EntityManager em, long offset, long limit) {
171        Query query = em.createQuery("select activity from Activity activity order by activity.id asc");
172        if (limit > 0) {
173            query.setMaxResults((int) limit);
174        }
175        if (offset > 0) {
176            query.setFirstResult((int) offset);
177        }
178        return new ActivitiesListImpl(query.getResultList());
179    }
180
181    @Override
182    public Activity addActivity(final Activity activity) {
183        if (activity.getPublishedDate() == null) {
184            activity.setPublishedDate(new Date());
185        }
186        getOrCreatePersistenceProvider().run(true, new PersistenceProvider.RunVoid() {
187            @Override
188            public void runWith(EntityManager em) {
189                addActivity(em, activity);
190            }
191        });
192        return activity;
193    }
194
195    protected void addActivity(EntityManager em, Activity activity) {
196        try {
197            localEntityManager.set(em);
198            em.persist(activity);
199            for (ActivityStreamFilter filter : activityStreamFilters.values()) {
200                if (filter.isInterestedIn(activity)) {
201                    filter.handleNewActivity(this, activity);
202                }
203            }
204        } finally {
205            localEntityManager.remove();
206        }
207    }
208
209    @Override
210    public void removeActivities(final Collection<Activity> activities) {
211        if (activities == null || activities.isEmpty()) {
212            return;
213        }
214        getOrCreatePersistenceProvider().run(true, new PersistenceProvider.RunVoid() {
215            @Override
216            public void runWith(EntityManager em) {
217                removeActivities(em, activities);
218            }
219        });
220    }
221
222    protected void removeActivities(EntityManager em, Collection<Activity> activities) {
223        try {
224            localEntityManager.set(em);
225
226            ActivitiesList l = new ActivitiesListImpl(activities);
227            for (ActivityStreamFilter filter : activityStreamFilters.values()) {
228                filter.handleRemovedActivities(this, l);
229            }
230
231            Query query = em.createQuery("delete from Activity activity where activity.id in (:ids)");
232            query.setParameter("ids", l.toActivityIds());
233            query.executeUpdate();
234        } finally {
235            localEntityManager.remove();
236        }
237    }
238
239    @Override
240    public ActivityMessage toActivityMessage(final Activity activity, Locale locale) {
241        return toActivityMessage(activity, locale, null);
242    }
243
244    @Override
245    public ActivityMessage toActivityMessage(Activity activity, Locale locale, String activityLinkBuilderName) {
246        ActivityLinkBuilder activityLinkBuilder = getActivityLinkBuilder(activityLinkBuilderName);
247
248        Map<String, String> fields = activity.toMap();
249
250        String actor = activity.getActor();
251        String displayActor = activity.getDisplayActor();
252        String displayActorLink;
253        if (ActivityHelper.isUser(actor)) {
254            displayActorLink = activityLinkBuilder.getUserProfileLink(actor, activity.getDisplayActor());
255        } else {
256            displayActorLink = activity.getDisplayActor();
257        }
258
259        List<ActivityReplyMessage> activityReplyMessages = toActivityReplyMessages(activity.getActivityReplies(),
260                locale, activityLinkBuilderName);
261
262        ActivityVerb verb = activityVerbRegistry.get(activity.getVerb());
263
264        if (verb == null || verb.getLabelKey() == null) {
265            return new ActivityMessage(activity.getId(), actor, displayActor, displayActorLink, activity.getVerb(),
266                    activity.toString(), activity.getPublishedDate(), null, activityReplyMessages);
267        }
268
269        String labelKey = verb.getLabelKey();
270        String messageTemplate;
271        try {
272            messageTemplate = I18NUtils.getMessageString("messages", labelKey, null, locale);
273        } catch (MissingResourceException e) {
274            log.error(e.getMessage());
275            log.debug(e, e);
276            // just return the labelKey if we have no resource bundle
277            return new ActivityMessage(activity.getId(), actor, displayActor, displayActorLink, activity.getVerb(),
278                    labelKey, activity.getPublishedDate(), verb.getIcon(), activityReplyMessages);
279        }
280
281        Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}");
282        Matcher m = pattern.matcher(messageTemplate);
283        while (m.find()) {
284            String param = m.group().replaceAll("[\\|$\\|{\\}]", "");
285            if (fields.containsKey(param)) {
286                String value = fields.get(param);
287                final String displayValue = fields.get("display" + StringUtils.capitalize(param));
288                if (ActivityHelper.isDocument(value)) {
289                    value = activityLinkBuilder.getDocumentLink(value, displayValue);
290                } else if (ActivityHelper.isUser(value)) {
291                    value = activityLinkBuilder.getUserProfileLink(value, displayValue);
292                } else {
293                    // simple text
294                    value = ActivityMessageHelper.replaceURLsByLinks(value);
295                }
296                messageTemplate = messageTemplate.replace(m.group(), value);
297            }
298        }
299
300        return new ActivityMessage(activity.getId(), actor, displayActor, displayActorLink, activity.getVerb(),
301                messageTemplate, activity.getPublishedDate(), verb.getIcon(), activityReplyMessages);
302    }
303
304    @Override
305    public ActivityLinkBuilder getActivityLinkBuilder(String name) {
306        ActivityLinkBuilder activityLinkBuilder;
307        if (StringUtils.isBlank(name)) {
308            activityLinkBuilder = activityLinkBuilderRegistry.getDefaultActivityLinkBuilder();
309        } else {
310            activityLinkBuilder = activityLinkBuilderRegistry.get(name);
311            if (activityLinkBuilder == null) {
312                log.warn("Fallback on default Activity link builder");
313                activityLinkBuilder = activityLinkBuilderRegistry.getDefaultActivityLinkBuilder();
314            }
315        }
316        return activityLinkBuilder;
317    }
318
319    @Override
320    public ActivityReplyMessage toActivityReplyMessage(ActivityReply activityReply, Locale locale) {
321        return toActivityReplyMessage(activityReply, locale, null);
322    }
323
324    @Override
325    public ActivityReplyMessage toActivityReplyMessage(ActivityReply activityReply, Locale locale,
326            String activityLinkBuilderName) {
327        ActivityLinkBuilder activityLinkBuilder = getActivityLinkBuilder(activityLinkBuilderName);
328
329        String actor = activityReply.getActor();
330        String displayActor = activityReply.getDisplayActor();
331        String displayActorLink = activityLinkBuilder.getUserProfileLink(actor, displayActor);
332        String message = ActivityMessageHelper.replaceURLsByLinks(activityReply.getMessage());
333        return new ActivityReplyMessage(activityReply.getId(), actor, displayActor, displayActorLink, message,
334                activityReply.getPublishedDate());
335
336    }
337
338    private List<ActivityReplyMessage> toActivityReplyMessages(List<ActivityReply> replies, Locale locale,
339            String activityLinkBuilderName) {
340        List<ActivityReplyMessage> activityReplyMessages = new ArrayList<ActivityReplyMessage>();
341        for (ActivityReply reply : replies) {
342            activityReplyMessages.add(toActivityReplyMessage(reply, locale, activityLinkBuilderName));
343        }
344        return activityReplyMessages;
345    }
346
347    @Override
348    public ActivityStream getActivityStream(String name) {
349        return activityStreamRegistry.get(name);
350    }
351
352    @Override
353    public ActivityReply addActivityReply(Serializable activityId, ActivityReply activityReply) {
354        Activity activity = getActivity(activityId);
355        if (activity != null) {
356            List<ActivityReply> replies = activity.getActivityReplies();
357            String newReplyId = computeNewReplyId(activity);
358            activityReply.setId(newReplyId);
359            replies.add(activityReply);
360            activity.setActivityReplies(replies);
361            updateActivity(activity);
362        }
363        return activityReply;
364    }
365
366    /**
367     * @since 5.6
368     */
369    protected String computeNewReplyId(Activity activity) {
370        String replyIdPrefix = activity.getId() + "-reply-";
371        List<ActivityReply> replies = activity.getActivityReplies();
372        long maxId = 0;
373        for (ActivityReply reply : replies) {
374            String replyId = reply.getId();
375            long currentId = Long.valueOf(replyId.replace(replyIdPrefix, ""));
376            if (currentId > maxId) {
377                maxId = currentId;
378            }
379        }
380        return replyIdPrefix + (maxId + 1);
381    }
382
383    public Activity getActivity(final Serializable activityId) {
384        return getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunCallback<Activity>() {
385            @Override
386            public Activity runWith(EntityManager em) {
387                return getActivity(em, activityId);
388            }
389        });
390    }
391
392    public ActivitiesList getActivities(final Collection<Serializable> activityIds) {
393        return getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunCallback<ActivitiesList>() {
394            @Override
395            public ActivitiesList runWith(EntityManager em) {
396                return getActivities(em, activityIds);
397            }
398        });
399    }
400
401    @Override
402    public ActivityReply removeActivityReply(final Serializable activityId, final String activityReplyId) {
403        return getOrCreatePersistenceProvider().run(true, new PersistenceProvider.RunCallback<ActivityReply>() {
404            @Override
405            public ActivityReply runWith(EntityManager em) {
406                return removeActivityReply(em, activityId, activityReplyId);
407            }
408        });
409    }
410
411    /**
412     * @since 5.6
413     */
414    protected ActivityReply removeActivityReply(EntityManager em, Serializable activityId, String activityReplyId) {
415        try {
416            localEntityManager.set(em);
417
418            Activity activity = getActivity(activityId);
419            if (activity != null) {
420                List<ActivityReply> replies = activity.getActivityReplies();
421                for (Iterator<ActivityReply> it = replies.iterator(); it.hasNext();) {
422                    ActivityReply reply = it.next();
423                    if (reply.getId().equals(activityReplyId)) {
424                        for (ActivityStreamFilter filter : activityStreamFilters.values()) {
425                            filter.handleRemovedActivityReply(this, activity, reply);
426                        }
427                        it.remove();
428                        activity.setActivityReplies(replies);
429                        updateActivity(activity);
430                        return reply;
431                    }
432                }
433            }
434            return null;
435        } finally {
436            localEntityManager.remove();
437        }
438    }
439
440    protected Activity getActivity(EntityManager em, Serializable activityId) {
441        Query query = em.createQuery("select activity from Activity activity where activity.id = :activityId");
442        query.setParameter("activityId", activityId);
443        return (Activity) query.getSingleResult();
444    }
445
446    @SuppressWarnings("unchecked")
447    protected ActivitiesList getActivities(EntityManager em, Collection<Serializable> activityIds) {
448        Query query = em.createQuery("select activity from Activity activity where activity.id in (:ids)");
449        query.setParameter("ids", activityIds);
450        return new ActivitiesListImpl(query.getResultList());
451    }
452
453    protected void updateActivity(final Activity activity) {
454        getOrCreatePersistenceProvider().run(false, new PersistenceProvider.RunCallback<Activity>() {
455            @Override
456            public Activity runWith(EntityManager em) {
457                activity.setLastUpdatedDate(new Date());
458                return em.merge(activity);
459            }
460        });
461    }
462
463    public EntityManager getEntityManager() {
464        return localEntityManager.get();
465    }
466
467    public PersistenceProvider getOrCreatePersistenceProvider() {
468        if (persistenceProvider == null) {
469            activatePersistenceProvider();
470        }
471        return persistenceProvider;
472    }
473
474    protected void activatePersistenceProvider() {
475        Thread thread = Thread.currentThread();
476        ClassLoader last = thread.getContextClassLoader();
477        try {
478            thread.setContextClassLoader(PersistenceProvider.class.getClassLoader());
479            PersistenceProviderFactory persistenceProviderFactory = Framework.getLocalService(PersistenceProviderFactory.class);
480            persistenceProvider = persistenceProviderFactory.newProvider(ACTIVITIES_PROVIDER);
481            persistenceProvider.openPersistenceUnit();
482        } finally {
483            thread.setContextClassLoader(last);
484        }
485    }
486
487    protected void deactivatePersistenceProvider() {
488        if (persistenceProvider != null) {
489            persistenceProvider.closePersistenceUnit();
490            persistenceProvider = null;
491        }
492    }
493
494    @Override
495    public void activate(ComponentContext context) {
496        super.activate(context);
497        activityStreamRegistry = new ActivityStreamRegistry();
498        activityVerbRegistry = new ActivityVerbRegistry();
499        activityLinkBuilderRegistry = new ActivityLinkBuilderRegistry();
500        activityUpgraderRegistry = new ActivityUpgraderRegistry();
501
502        initializationHandler = new ActivityRepositoryInitializationHandler();
503        initializationHandler.install();
504    }
505
506    @Override
507    public void deactivate(ComponentContext context) {
508        deactivatePersistenceProvider();
509
510        if (initializationHandler != null) {
511            initializationHandler.uninstall();
512        }
513
514        super.deactivate(context);
515    }
516
517    @Override
518    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
519        if (ACTIVITY_STREAM_FILTER_EP.equals(extensionPoint)) {
520            registerActivityStreamFilter((ActivityStreamFilterDescriptor) contribution);
521        } else if (ACTIVITY_MESSAGE_LABELS_EP.equals(extensionPoint)) {
522            registerActivityMessageLabel((ActivityMessageLabelDescriptor) contribution);
523        } else if (ACTIVITY_STREAMS_EP.equals(extensionPoint)) {
524            registerActivityStream((ActivityStream) contribution);
525        } else if (ACTIVITY_VERBS_EP.equals(extensionPoint)) {
526            registerActivityVerb((ActivityVerb) contribution);
527        } else if (ACTIVITY_LINK_BUILDERS_EP.equals(extensionPoint)) {
528            registerActivityLinkBuilder((ActivityLinkBuilderDescriptor) contribution);
529        } else if (ACTIVITY_UPGRADERS_EP.equals(extensionPoint)) {
530            registerActivityUpgrader((ActivityUpgraderDescriptor) contribution);
531        }
532    }
533
534    private void registerActivityStreamFilter(ActivityStreamFilterDescriptor descriptor) {
535        ActivityStreamFilter filter = descriptor.getActivityStreamFilter();
536
537        String filterId = filter.getId();
538
539        boolean enabled = descriptor.isEnabled();
540        if (activityStreamFilters.containsKey(filterId)) {
541            log.info("Overriding activity stream filter with id " + filterId);
542            if (!enabled) {
543                activityStreamFilters.remove(filterId);
544                log.info("Disabled activity stream filter with id " + filterId);
545            }
546        }
547        if (enabled) {
548            log.info("Registering activity stream filter with id " + filterId);
549            activityStreamFilters.put(filterId, descriptor.getActivityStreamFilter());
550        }
551    }
552
553    private void registerActivityMessageLabel(ActivityMessageLabelDescriptor descriptor) {
554        log.info("Registering activity message label for verb" + descriptor.getActivityVerb());
555        log.warn("The 'activityMessageLabels' extension point is deprecated, "
556                + "please use the 'activityVerbs' extension point.");
557        ActivityVerb activityVerb = new ActivityVerb();
558        activityVerb.setVerb(descriptor.getActivityVerb());
559        activityVerb.setLabelKey(descriptor.getLabelKey());
560        registerActivityVerb(activityVerb);
561    }
562
563    private void registerActivityStream(ActivityStream activityStream) {
564        log.info(String.format("Registering activity stream '%s'", activityStream.getName()));
565        activityStreamRegistry.addContribution(activityStream);
566    }
567
568    private void registerActivityVerb(ActivityVerb activityVerb) {
569        log.info(String.format("Registering activity verb '%s'", activityVerb.getVerb()));
570        activityVerbRegistry.addContribution(activityVerb);
571    }
572
573    private void registerActivityLinkBuilder(ActivityLinkBuilderDescriptor activityLinkBuilderDescriptor) {
574        log.info(String.format("Registering activity link builder '%s'", activityLinkBuilderDescriptor.getName()));
575        activityLinkBuilderRegistry.addContribution(activityLinkBuilderDescriptor);
576    }
577
578    private void registerActivityUpgrader(ActivityUpgraderDescriptor activityUpgraderDescriptor) {
579        log.info(String.format("Registering activity upgrader '%s'", activityUpgraderDescriptor.getName()));
580        activityUpgraderRegistry.addContribution(activityUpgraderDescriptor);
581    }
582
583    @Override
584    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
585        if (ACTIVITY_STREAM_FILTER_EP.equals(extensionPoint)) {
586            unregisterActivityStreamFilter((ActivityStreamFilterDescriptor) contribution);
587        } else if (ACTIVITY_MESSAGE_LABELS_EP.equals(extensionPoint)) {
588            unregisterActivityMessageLabel((ActivityMessageLabelDescriptor) contribution);
589        } else if (ACTIVITY_STREAMS_EP.equals(extensionPoint)) {
590            unregisterActivityStream((ActivityStream) contribution);
591        } else if (ACTIVITY_VERBS_EP.equals(extensionPoint)) {
592            unregisterActivityVerb((ActivityVerb) contribution);
593        } else if (ACTIVITY_LINK_BUILDERS_EP.equals(extensionPoint)) {
594            unregisterActivityLinkBuilder((ActivityLinkBuilderDescriptor) contribution);
595        } else if (ACTIVITY_UPGRADERS_EP.equals(extensionPoint)) {
596            unregisterActivityUpgrader((ActivityUpgraderDescriptor) contribution);
597        }
598    }
599
600    private void unregisterActivityStreamFilter(ActivityStreamFilterDescriptor descriptor) {
601        ActivityStreamFilter filter = descriptor.getActivityStreamFilter();
602        String filterId = filter.getId();
603        activityStreamFilters.remove(filterId);
604        log.info("Unregistering activity stream filter with id " + filterId);
605    }
606
607    private void unregisterActivityMessageLabel(ActivityMessageLabelDescriptor descriptor) {
608        ActivityVerb activityVerb = new ActivityVerb();
609        activityVerb.setVerb(descriptor.getActivityVerb());
610        activityVerb.setLabelKey(descriptor.getLabelKey());
611        unregisterActivityVerb(activityVerb);
612        log.info("Unregistering activity message label for verb " + activityVerb);
613        log.warn("The 'activityMessageLabels' extension point is deprecated, "
614                + "please use the 'activityVerbs' extension point.");
615    }
616
617    private void unregisterActivityStream(ActivityStream activityStream) {
618        activityStreamRegistry.removeContribution(activityStream);
619        log.info(String.format("Unregistering activity stream '%s'", activityStream.getName()));
620    }
621
622    private void unregisterActivityVerb(ActivityVerb activityVerb) {
623        activityVerbRegistry.removeContribution(activityVerb);
624        log.info(String.format("Unregistering activity verb '%s'", activityVerb.getVerb()));
625    }
626
627    private void unregisterActivityLinkBuilder(ActivityLinkBuilderDescriptor activityLinkBuilderDescriptor) {
628        activityLinkBuilderRegistry.removeContribution(activityLinkBuilderDescriptor);
629        log.info(String.format("Unregistering activity link builder '%s'", activityLinkBuilderDescriptor.getName()));
630    }
631
632    private void unregisterActivityUpgrader(ActivityUpgraderDescriptor activityUpgraderDescriptor) {
633        activityUpgraderRegistry.removeContribution(activityUpgraderDescriptor);
634        log.info(String.format("Unregistering activity upgrader '%s'", activityUpgraderDescriptor.getName()));
635    }
636
637}