001/*
002 *  Copyright 2020 Anyware Services
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 */
016package org.ametys.plugins.mobileapp;
017
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.core.util.I18nUtils;
031import org.ametys.plugins.workspaces.ObservationConstants;
032import org.ametys.plugins.workspaces.project.ProjectsCatalogueManager;
033import org.ametys.plugins.workspaces.project.objects.Project;
034import org.ametys.runtime.i18n.I18nizableText;
035import org.ametys.runtime.i18n.I18nizableTextParameter;
036import org.ametys.runtime.plugin.PluginsManager;
037import org.ametys.runtime.plugin.component.AbstractLogEnabled;
038
039/**
040 * Helper to handle project feeds
041 */
042public class FeedHelper extends AbstractLogEnabled implements Serviceable, Component
043{
044    /** Avalon Role */
045    public static final String ROLE = FeedHelper.class.getName();
046    
047    /** I18N Utils */
048    protected I18nUtils _i18nUtils;
049    
050    /** The project catalogue manager component */
051    protected ProjectsCatalogueManager _projectsCatalogueManager;
052    
053    public void service(ServiceManager manager) throws ServiceException
054    {
055        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
056        if (PluginsManager.getInstance().isPluginActive("workspaces"))
057        {
058            _projectsCatalogueManager = (ProjectsCatalogueManager) manager.lookup(ProjectsCatalogueManager.ROLE);
059        }
060    }
061    
062
063    /**
064     * Generate a map of project names with the basic projects informations to return
065     * @return a map of basic information about projects
066     */
067    public Map<String, Map<String, Object>> getProjects()
068    {
069        Map<String, Map<String, Object>> result = new HashMap<>();
070        
071        if (_projectsCatalogueManager != null)
072        {
073            List<Map<String, Object>> userProjects = _projectsCatalogueManager.getUserProjects();
074            for (Map<String, Object> fullProjectMap : userProjects)
075            {
076                String name = (String) fullProjectMap.get("name");
077                Map<String, Object> neededInfos = new HashMap<>();
078    
079                neededInfos.put("name", name);
080                neededInfos.put("description", fullProjectMap.get("description"));
081                neededInfos.put("title", fullProjectMap.get("title"));
082                neededInfos.put("url", fullProjectMap.get("url"));
083                neededInfos.put("illustration", fullProjectMap.get("illustration"));
084                neededInfos.put("language", fullProjectMap.get("language"));
085                neededInfos.put("id", fullProjectMap.get("id"));
086                neededInfos.put("category", fullProjectMap.get("category"));
087               
088                result.put(name, neededInfos);
089            }
090        }
091        
092        return result;
093    }
094    
095    /**
096     * Transform a {@link Project} into a json map
097     * @param project the project to parse
098     * @return a json map
099     */
100    public Map<String, Object> projectToMap(Project project)
101    {
102        return _projectsCatalogueManager.detailedProject2json(project);
103    }
104    /**
105     * Add infos to the activity, so it can be displayed by the app
106     * @param activity the json representing the activity to parse
107     * @param project the project, as a json map (see {@link FeedHelper#projectToMap(Project)})
108     * @param lang language to use to translate the short description
109     * @return a map to return in json
110     */
111    public Map<String, Object> getActivityInfos(Map<String, Object> activity, Map<String, Object> project, String lang)
112    {
113        Map<String, Object> result = new HashMap<>();
114        
115        result.putAll(activity);
116        
117        result.put("project", project);
118        
119        @SuppressWarnings("unchecked")
120        Map<String, String> activityAuthor = (Map<String, String>) activity.get("author");
121        
122        Map<String, String> author = new HashMap<>();
123        author.put("fullname", activityAuthor.get("fullname"));
124        result.put("author", author);
125        
126        result.put("short-description", getActivityDescription(activity, lang));
127
128        result.put("content_id", getActivityObjectId(activity));
129        result.put("content_url", getActivityUrl(activity));
130        
131        return result;
132    }
133    
134    /**
135     * Generate a description for this activity
136     * @param activity the activity to describe
137     * @return a String of the description
138     */
139    @SuppressWarnings("unchecked")
140    protected String getActivityObjectId(Map<String, Object> activity)
141    {
142        String id = null;
143        String eventType = (String) activity.get("type");
144        
145        switch (eventType)
146        {
147            /*
148             * RESOURCES
149             */
150            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_CREATED :
151            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_UPDATED :
152            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_RENAMED :
153                if (activity.containsKey("file"))
154                {
155                    id = (String) ((Map<String, Object>) activity.get("file")).get("id");
156                }
157                break;
158
159            /*
160             * CALENDAR
161             */
162            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_CREATED :
163            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED :
164                id = (String) activity.get("eventId");
165                break;
166
167            /*
168             * THREAD
169             */
170            case ObservationConstants.EVENT_THREAD_CREATED :
171            case ObservationConstants.EVENT_THREAD_COMMENTED :
172                id = (String) activity.get("threadId");
173                break;
174
175            /*
176             * MEMBER
177             */
178            case ObservationConstants.EVENT_MEMBER_ADDED :
179                // no id available
180                break;
181            case ObservationConstants.EVENT_WALLCONTENT_ADDED :
182                id = (String) activity.get("contentId");
183                break;
184
185            /**
186             * WIKI
187             */
188            case ObservationConstants.EVENT_MINISITE_PAGE_CREATED :
189            case ObservationConstants.EVENT_MINISITE_PAGE_UPDATED :
190            case ObservationConstants.EVENT_MINISITE_PAGE_RENAMED :
191            case ObservationConstants.EVENT_MINISITE_PAGE_DELETED :
192                id = (String) activity.get("pageId");
193                break;
194
195            /**
196             * TASK
197             */
198            case ObservationConstants.EVENT_TASK_CREATED :
199            case ObservationConstants.EVENT_TASK_ASSIGNED :
200            case ObservationConstants.EVENT_TASK_CLOSED_STATUS_CHANGED :
201                id = (String) activity.get("taskId");
202                break;
203            default:
204                break;
205        }
206        
207        return id;
208    }
209    
210
211    /**
212     * Generate a description for this activity
213     * @param activity the activity to describe
214     * @return a String of the description
215     */
216    protected String getActivityUrl(Map<String, Object> activity)
217    {
218        String url = null;
219        String eventType = (String) activity.get("type");
220        
221        switch (eventType)
222        {
223            /*
224             * RESOURCES
225             */
226            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_CREATED :
227            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_UPDATED :
228            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_RENAMED :
229            case org.ametys.plugins.workspaces.ObservationConstants.EVENT_RESOURCE_COMMENTED :
230                url = (String) activity.get("fileUrl");
231                break;
232
233            /*
234             * CALENDAR
235             */
236            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_CREATED :
237            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED :
238                url = (String) activity.get("eventUrl");
239                break;
240
241            /*
242             * THREAD
243             */
244            case ObservationConstants.EVENT_THREAD_CREATED :
245            case ObservationConstants.EVENT_THREAD_MODIFIED :
246            case ObservationConstants.EVENT_THREAD_COMMENTED :
247                url = (String) activity.get("threadUrl");
248                break;
249
250            /*
251             * MEMBER
252             */
253            case ObservationConstants.EVENT_MEMBER_ADDED :
254            case ObservationConstants.EVENT_MEMBER_JOINED :
255                url = (String) activity.get("memberUrl");
256                break;
257                
258            /*
259             * WALL CONTENT
260             */
261            case ObservationConstants.EVENT_WALLCONTENT_ADDED :
262                // no url available
263                break;
264
265            /*
266             * WIKI
267             */
268            case ObservationConstants.EVENT_MINISITE_PAGE_CREATED :
269            case ObservationConstants.EVENT_MINISITE_PAGE_UPDATED :
270            case ObservationConstants.EVENT_MINISITE_PAGE_RENAMED :
271            case ObservationConstants.EVENT_MINISITE_PAGE_DELETED :
272                url = (String) activity.get("pageUrl");
273                break;
274
275            /*
276             * TASK
277             */
278            case ObservationConstants.EVENT_TASK_CREATED :
279            case ObservationConstants.EVENT_TASK_ASSIGNED :
280            case ObservationConstants.EVENT_TASK_CLOSED_STATUS_CHANGED :
281            case ObservationConstants.EVENT_TASK_COMMENTED :
282                url = (String) activity.get("taskUrl");
283                break;
284            default:
285                break;
286        }
287        if (StringUtils.isBlank(url))
288        {
289            url = (String) activity.get("projectUrl");
290        }
291        return url;
292    }
293
294    /**
295     * Generate a description for this activity
296     * @param activity the activity to describe
297     * @param lang the language to use
298     * @return a String of the description
299     */
300    @SuppressWarnings("unchecked")
301    protected String getActivityDescription(Map<String, Object> activity, String lang)
302    {
303        String description = null;
304        
305        String eventType = (String) activity.get("type");
306        
307        Integer amount = (Integer) activity.get("amount");
308        if (amount == null)
309        {
310            amount = 1;
311        }
312        
313        String i18nKey = null;
314        Map<String, I18nizableTextParameter> parameters = new HashMap<>();
315        parameters.put("author", new I18nizableText(((Map<String, String>) activity.get("author")).get("fullname")));
316        parameters.put("project", new I18nizableText((String) activity.get("projectTitle")));
317        parameters.put("nb", new I18nizableText(amount.toString()));
318        
319        switch (eventType)
320        {
321
322            /*
323             * RESOURCES
324             */
325            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_CREATED :
326                parameters.put("folder", new I18nizableText((String) activity.get("parentFolder")));
327                if (amount == 1)
328                {
329                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_ADDED_DESC";
330                }
331                else
332                {
333                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_ADDED_MULTI_DESC";
334                }
335                break;
336            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_UPDATED :
337                parameters.put("folder", new I18nizableText((String) activity.get("parentFolder")));
338                if (amount == 1)
339                {
340                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_UPDATED_DESC";
341                }
342                else
343                {
344                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_UPDATED_MULTI_DESC";
345                }
346                break;
347            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_RENAMED :
348                parameters.put("folder", new I18nizableText((String) activity.get("parentFolder")));
349                if (amount == 1)
350                {
351                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_RENAMED_DESC";
352                }
353                else
354                {
355                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_RENAMED_MULTI_DESC";
356                }
357                break;
358            case ObservationConstants.EVENT_RESOURCE_COMMENTED :
359                parameters.put("folder", new I18nizableText((String) activity.get("parentFolder")));
360                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_COMMENTED_DESC";
361                break;
362            case org.ametys.plugins.explorer.ObservationConstants.EVENT_RESOURCE_DELETED :
363                parameters.put("folder", new I18nizableText((String) activity.get("parentFolder")));
364                if (amount == 1)
365                {
366                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_DELETED_DESC";
367                }
368                else
369                {
370                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_DOCUMENT_DELETED_MULTI_DESC";
371                }
372                break;
373
374            /*
375             * CALENDAR
376             */
377            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_CREATED :
378                parameters.put("calendar", new I18nizableText((String) activity.get("calendarTitle")));
379                if (amount == 1)
380                {
381                    parameters.put("event", new I18nizableText(getTitles(activity, "events", "eventTitle")));
382                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_CALENDAR_ADDED_DESC";
383                }
384                else
385                {
386                    parameters.put("events", new I18nizableText(getTitles(activity, "events", "eventTitle")));
387                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_CALENDAR_ADDED_MULTI_DESC";
388                }
389                break;
390            case org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED :
391                parameters.put("calendar", new I18nizableText((String) activity.get("calendarTitle")));
392                if (amount == 1)
393                {
394                    parameters.put("event", new I18nizableText(getTitles(activity, "events", "eventTitle")));
395                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_CALENDAR_UPDATED_DESC";
396                }
397                else
398                {
399                    parameters.put("events", new I18nizableText(getTitles(activity, "events", "eventTitle")));
400                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_CALENDAR_UPDATED_MULTI_DESC";
401                }
402                break;
403
404            /*
405             * THREAD
406             */
407            case ObservationConstants.EVENT_THREAD_CREATED :
408                if (amount == 1)
409                {
410                    parameters.put("thread", new I18nizableText(getTitles(activity, "threads", "threadTitle")));
411                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_THREAD_CREATED_DESC";
412                }
413                else
414                {
415                    parameters.put("threads", new I18nizableText(getTitles(activity, "threads", "threadTitle")));
416                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_THREAD_CREATED_MULTI_DESC";
417                }
418                break;
419            case ObservationConstants.EVENT_THREAD_MODIFIED :
420                if (amount == 1)
421                {
422                    parameters.put("thread", new I18nizableText(getTitles(activity, "threads", "threadTitle")));
423                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_THREAD_MODIFIED_DESC";
424                }
425                else
426                {
427                    parameters.put("threads", new I18nizableText(getTitles(activity, "threads", "threadTitle")));
428                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_THREAD_MODIFIED_MULTI_DESC";
429                }
430                break;
431            case ObservationConstants.EVENT_THREAD_COMMENTED :
432                parameters.put("thread", new I18nizableText(getTitles(activity, "threads", "threadTitle")));
433                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_THREAD_POST_CREATED_DESC";
434                break;
435
436            /*
437             * MEMBER
438             */
439            case ObservationConstants.EVENT_MEMBER_ADDED :
440                if (amount == 1)
441                {
442                    parameters.put("member", new I18nizableText((String) activity.get("member")));
443                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_MEMBER_ADDED_DESC";
444                }
445                else
446                {
447                    Collection<Map<String, Object>> members = (Collection<Map<String, Object>>) activity.get("members");
448                    List<String> names = members.stream().map(member -> (String) member.get("name")).collect(Collectors.toList());
449                    parameters.put("members", new I18nizableText(String.join(", ", names)));
450                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_MEMBERS_ADDED_DESC";
451                }
452                break;
453            case ObservationConstants.EVENT_MEMBER_JOINED :
454                parameters.put("member", new I18nizableText((String) activity.get("member")));
455                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_MEMBER_JOINED_DESC";
456                break;
457                
458            /**
459             * WIKI
460             */
461            case ObservationConstants.EVENT_MINISITE_PAGE_CREATED :
462                if (amount == 1)
463                {
464                    parameters.put("page", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
465                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGE_CREATED_DESC";
466                }
467                else
468                {
469                    parameters.put("pages", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
470                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGES_CREATED_DESC";
471                }
472                break;
473            case ObservationConstants.EVENT_MINISITE_PAGE_UPDATED :
474                if (amount == 1)
475                {
476                    parameters.put("page", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
477                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGE_UPDATED_DESC";
478                }
479                else
480                {
481                    parameters.put("pages", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
482                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGES_UPDATED_DESC";
483                }
484                break;
485            case ObservationConstants.EVENT_MINISITE_PAGE_RENAMED :
486                parameters.put("oldTitle", new I18nizableText((String) activity.get("pageOldTitle")));
487                parameters.put("title", new I18nizableText((String) activity.get("pageTitle")));
488                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGE_RENAMED_DESC";
489                break;
490            case ObservationConstants.EVENT_MINISITE_PAGE_DELETED :
491                if (amount == 1)
492                {
493                    parameters.put("page", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
494                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGE_DELETED_DESC";
495                }
496                else
497                {
498                    parameters.put("pages", new I18nizableText(getTitles(activity, "pages", "pageTitle")));
499                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PAGES_DELETED_DESC";
500                }
501                break;
502                
503
504            /**
505             * NEWS
506             */
507            case ObservationConstants.EVENT_PROJECT_NEWS_PUBLISHED:
508                parameters.put("contentTitle", new I18nizableText((String) activity.get("contentTitle")));
509                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_PROJECT_NEWS_ADDED_DESC";
510                break;
511
512            /**
513             * WALLCONTENT
514             */
515            case ObservationConstants.EVENT_WALLCONTENT_ADDED :
516                parameters.put("contentSummary", new I18nizableText((String) activity.get("contentSummary")));
517                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_WALLCONTENT_ADDED_WITH_TITLE_DESC";
518                break;
519                
520            /**
521             * NEWS AND WALLCONTENTS COMMENTS
522             */
523            case org.ametys.cms.ObservationConstants.EVENT_CONTENT_COMMENT_VALIDATED :
524                parameters.put("contentTitle", new I18nizableText((String) activity.get("contentTitle")));
525                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_CONTENT_COMMENT_VALIDATED_DESC";
526                break;
527
528            /**
529             * TASK
530             */
531            case ObservationConstants.EVENT_TASK_CREATED :
532                if (amount == 1)
533                {
534                    parameters.put("task", new I18nizableText(getTitles(activity, "tasks", "taskTitle")));
535                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_ADDED_DESC";
536                }
537                else
538                {
539                    parameters.put("tasks", new I18nizableText(getTitles(activity, "tasks", "taskTitle")));
540                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASKS_ADDED_DESC";
541                }
542                break;
543            case ObservationConstants.EVENT_TASK_DELETING :
544                if (amount == 1)
545                {
546                    parameters.put("task", new I18nizableText(getTitles(activity, "tasks", "taskTitle")));
547                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_DELETED_DESC";
548                }
549                else
550                {
551                    parameters.put("tasks", new I18nizableText(getTitles(activity, "tasks", "taskTitle")));
552                    i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASKS_DELETED_DESC";
553                }
554                break;
555            case ObservationConstants.EVENT_TASK_ASSIGNED :
556                parameters.put("task", new I18nizableText((String) activity.get("taskTitle")));
557                parameters.put("assignee", new I18nizableText((String) activity.get("assignees")));
558                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_ASSIGNED_DESC";
559                break;
560            case ObservationConstants.EVENT_TASK_COMMENTED :
561                parameters.put("task", new I18nizableText((String) activity.get("taskTitle")));
562                i18nKey = "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_COMMENTED_DESC";
563                break;
564            case ObservationConstants.EVENT_TASK_CLOSED_STATUS_CHANGED :
565                parameters.put("task", new I18nizableText((String) activity.get("taskTitle")));
566                
567                boolean isClosed = "true".equals(activity.get("isClosed"));
568                i18nKey = isClosed ? "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_CLOSED_STATUS_CLOSE_DESC" : "PLUGINS_WORKSPACES_PROJECT_SERVICE_ACTIVITY_STREAM_EVENT_TASK_CLOSED_STATUS_OPEN_DESC";
569                break;
570            default:
571                break;
572        }
573        
574        if (i18nKey != null)
575        {
576            I18nizableText descriptionI18n = new I18nizableText("plugin.workspaces", i18nKey, parameters);
577            description = _i18nUtils.translate(descriptionI18n, lang);
578        }
579        
580        return description;
581    }
582    
583    /**
584     * Aggregates a list of elements from the activity.
585     * activity = {
586     *      mapKey : [
587     *          {
588     *              titleKey : "Title 1",
589     *              …
590     *          },
591     *          {
592     *              titleKey : "Title 2",
593     *              …
594     *          },
595     *          {
596     *              titleKey : "Title 3",
597     *              …
598     *          }
599     *      ],
600     *      …
601     *  }
602     * @param activity the activity to use
603     * @param mapKey the key of the list to use
604     * @param titleKey title to use from the map
605     * @return a list, separated with commas, between each titles
606     */
607    protected String getTitles(Map<String, Object> activity, String mapKey, String titleKey)
608    {
609        @SuppressWarnings("unchecked")
610        List<Map<String, String>> items = (List<Map<String, String>>) activity.get(mapKey);
611        
612        
613        if (items == null)
614        {
615            return (String) activity.get(titleKey);
616        }
617        else
618        {
619            String result = null;
620            
621            for (Map<String, String> item : items)
622            {
623                if (result == null)
624                {
625                    result = item.get(titleKey);
626                }
627                else
628                {
629                    result += ", " + item.get(titleKey);
630                }
631            }
632            
633            return result;
634        }
635    }
636
637}