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