001/*
002 *  Copyright 2021 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.workspaces.tasks.json;
017
018import java.io.InputStream;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031
032import org.ametys.cms.data.Binary;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.ReactionableObject.ReactionType;
035import org.ametys.cms.repository.comment.Comment;
036import org.ametys.core.user.User;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.core.user.UserManager;
039import org.ametys.core.user.directory.NotUniqueUserException;
040import org.ametys.core.user.population.PopulationContextHelper;
041import org.ametys.plugins.core.user.UserHelper;
042import org.ametys.plugins.workspaces.members.ProjectMemberManager;
043import org.ametys.plugins.workspaces.tags.ProjectTagProviderExtensionPoint;
044import org.ametys.plugins.workspaces.tasks.Task;
045import org.ametys.plugins.workspaces.tasks.Task.CheckItem;
046import org.ametys.plugins.workspaces.tasks.jcr.JCRTask;
047import org.ametys.runtime.plugin.component.AbstractLogEnabled;
048
049/**
050 * Helper to convert task to JSON
051 */
052public class TaskJSONHelper extends AbstractLogEnabled implements Serviceable, Component
053{
054    /** The Avalon role */
055    public static final String ROLE = TaskJSONHelper.class.getName();
056    
057    /** The user helper */
058    protected UserHelper _userHelper;
059    
060    /** The project member manager */
061    protected ProjectMemberManager _projectMemberManager;
062    
063    /** The user manager */
064    protected UserManager _userManager;
065    
066    /** The population context helper */
067    protected PopulationContextHelper _populationContextHelper;
068    
069    /** The tag provider extension point */
070    protected ProjectTagProviderExtensionPoint _tagProviderExtensionPoint;
071    
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
076        _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE);
077        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
078        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
079        _tagProviderExtensionPoint = (ProjectTagProviderExtensionPoint) manager.lookup(ProjectTagProviderExtensionPoint.ROLE);
080    }
081
082    /**
083     * Task as JSON
084     * @param task the task
085     * @param lang the language
086     * @param siteName the site name
087     * @return the task as JSON
088     */
089    public Map<String, Object> taskAsJSON(Task task, String lang, String siteName)
090    {
091        Map<String, Object> json = new HashMap<>();
092        
093        json.put(JCRTask.ATTRIBUTE_TASK_ID, task.getId());
094        json.put(JCRTask.ATTRIBUTE_LABEL, task.getLabel());
095        json.put(JCRTask.ATTRIBUTE_DESCRIPTION, task.getDescription());
096        json.put(JCRTask.ATTRIBUTE_TASKSLISTID, task.getTaskListId());
097        json.put("position", task.getPosition());
098        json.put(JCRTask.ATTRIBUTE_CREATIONDATE, task.getCreationDate());
099        json.put("closeInfo", _closeInfoAsJson(task));
100        
101        json.put(JCRTask.ATTRIBUTE_ASSIGNMENTS, _assignmentsToJson(task));
102        
103        List<String> tags = task.getTags()
104                .stream()
105                .filter(tag -> _tagProviderExtensionPoint.hasTag(tag, Map.of("siteName", siteName)))
106                .collect(Collectors.toList());
107        json.put("tags", tags);
108        
109        List<Map<String, Object>> attachments = task.getAttachments()
110            .stream()
111            .map(this::_binaryToJson)
112            .collect(Collectors.toList());
113        json.put(JCRTask.ATTRIBUTE_ATTACHMENTS, attachments);
114        
115        json.put(JCRTask.ATTRIBUTE_CHECKLIST, _checkListToJson(task));
116
117        json.put("comments", _commentsToJson(task.getComments(true, true), lang, siteName));
118        json.put(JCRTask.ATTRIBUTE_DUEDATE, task.getDueDate());
119        json.put("datePassed", task.isClosed());
120        
121        return json;
122    }
123    
124    private List<Map<String, Object>> _commentsToJson(List<Comment> comments, String lang, String siteName)
125    {
126        List<Map<String, Object>> json = new ArrayList<>();
127        
128        for (Comment comment : comments)
129        {
130            Map<String, Object> commentJson = new HashMap<>();
131            commentJson.put("id", comment.getId());
132            commentJson.put("subComments", _commentsToJson(comment.getSubComment(true, true), lang, siteName));
133            commentJson.put("isDeleted", comment.isDeleted());
134            commentJson.put("creationDate", comment.getCreationDate());
135            if (!comment.isDeleted())
136            {
137                commentJson.put("text", comment.getContent());
138                commentJson.put("nbLike", comment.getReactionUsers(ReactionType.LIKE).size());
139                List<String> userLikes = comment.getReactionUsers(ReactionType.LIKE)
140                    .stream()
141                    .map(UserIdentity::userIdentityToString)
142                    .collect(Collectors.toList());
143                commentJson.put("userLikes", userLikes);
144                
145                Map<String, Object> jsonAuthor = new HashMap<>();
146                jsonAuthor.put("name", comment.getAuthorName());
147                jsonAuthor.put("avatar", comment.getAuthorURL());
148    
149                User user = _getUserByMail(comment.getAuthorEmail(), siteName);
150                if (user != null)
151                {
152                    jsonAuthor.put("id", UserIdentity.userIdentityToString(user.getIdentity()));
153                    Content member = _projectMemberManager.getUserContent(lang, user);
154                    if (member != null)
155                    {
156                        if (member.hasValue("function"))
157                        {
158                            jsonAuthor.put("function", member.getValue("function"));
159                        }
160                        if (member.hasValue("organisation-accronym"))
161                        {
162                            jsonAuthor.put("organisationAcronym", member.getValue("organisation-accronym"));
163                        }
164                    }
165                }
166                commentJson.put("author", jsonAuthor);
167                commentJson.put("isEdited", comment.isEdited());
168            }
169            
170            json.add(commentJson);
171        }
172        
173        return json;
174    }
175    
176    private Map<String, Object> _binaryToJson(Binary b)
177    {
178        Map<String, Object> json  = new HashMap<>();
179        
180        try (InputStream is = b.getInputStream()) 
181        {
182            json.put("id", b.getFilename());
183            json.put("name", b.getFilename());
184            json.put("type", b.getMimeType());
185            json.put("size", b.getLength());
186        }
187        catch (Exception e) 
188        {
189            getLogger().error("An error occurred reading binary {}", b.getFilename(), e);
190        }
191        return json;
192    }
193    
194    private Map<String, Object> _closeInfoAsJson(Task task)
195    {
196        UserIdentity closeAuthor = task.getCloseAuthor();
197        
198        return task.isClosed() 
199                ? Map.of(JCRTask.ATTRIBUTE_CLOSEAUTHOR, _userHelper.user2json(closeAuthor), JCRTask.ATTRIBUTE_CLOSEDATE, task.getCloseDate()) 
200                : null;
201    }
202
203    private List<Map<String, Object>> _assignmentsToJson(Task task)
204    {
205        return task.getAssignments()
206            .stream()
207            .map(u -> _userHelper.user2json(u))
208            .collect(Collectors.toList());
209    }
210    
211    private List<Map<String, Object>> _checkListToJson(Task task)
212    {
213        List<Map<String, Object>> json = new ArrayList<>();
214        for (CheckItem checkItem : task.getCheckList())
215        {
216            json.add(Map.of(JCRTask.ATTRIBUTE_CHECKLIST_LABEL, checkItem.getLabel(), JCRTask.ATTRIBUTE_CHECKLIST_ISCHECKED, checkItem.isChecked()));
217        }
218        return json;
219    }
220    
221    private User _getUserByMail(String email, String siteName)
222    {
223        Set<String> userPopulationsOnSite = _populationContextHelper.getUserPopulationsOnContexts(Arrays.asList("/sites/" + siteName, "/sites-fo/" + siteName), false, false);
224        
225        try
226        {
227            return _userManager.getUserByEmail(userPopulationsOnSite, email);
228        }
229        catch (NotUniqueUserException e)
230        {
231            getLogger().error("Cannot find user in site '" + siteName + "' by email '" + email + "' because 2 or more users match", e);
232            return null;
233        }
234    }
235}