001/*
002 *  Copyright 2016 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;
017
018import java.io.IOException;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.ProcessingException;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.cocoon.environment.Request;
034import org.apache.commons.io.IOUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.apache.excalibur.source.Source;
037import org.apache.excalibur.source.SourceResolver;
038
039import org.ametys.core.ui.Callable;
040import org.ametys.plugins.explorer.tasks.ModifiableTask;
041import org.ametys.plugins.explorer.tasks.Task;
042import org.ametys.plugins.explorer.tasks.TasksList;
043import org.ametys.plugins.explorer.tasks.jcr.JCRTasksDAO;
044import org.ametys.plugins.repository.AmetysObject;
045import org.ametys.plugins.repository.AmetysRepositoryException;
046import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
047import org.ametys.plugins.workspaces.html.HTMLTransformer;
048import org.ametys.plugins.workspaces.project.ProjectManager;
049import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
050import org.ametys.plugins.workspaces.project.objects.Project;
051import org.ametys.runtime.plugin.component.PluginAware;
052
053/**
054 * DAO for interacting with tasks of a project
055 */
056public class WorkspaceTaskDAO extends JCRTasksDAO implements PluginAware, Contextualizable
057{
058    /** The Avalon role */
059    @SuppressWarnings("hiding")
060    public static final String ROLE = WorkspaceTaskDAO.class.getName();
061    
062    private HTMLTransformer _htmlTransformer;
063    
064    private SourceResolver _sourceResolver;
065
066    private ProjectManager _projectManager;
067
068    private String _pluginName;
069
070    private Context _context;
071
072    private WorkspaceModuleExtensionPoint _moduleEP;
073
074    @Override
075    public void service(ServiceManager manager) throws ServiceException
076    {
077        super.service(manager);
078        _htmlTransformer = (HTMLTransformer) manager.lookup(HTMLTransformer.ROLE);
079        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
080        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
081        _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
082    }
083
084    @Override
085    public void setPluginInfo(String pluginName, String featureName, String id)
086    {
087        _pluginName = pluginName;
088    }
089    
090    @Override
091    public void contextualize(Context context) throws ContextException
092    {
093        _context = context;
094    }
095    
096    /**
097     * Add a new task to the current project
098     * @param parameters The task parameters
099     * @return The task data
100     * @throws IllegalAccessException If an error occurs
101     */
102    @Callable
103    public Map<String, Object> addTask(Map<String, Object> parameters) throws IllegalAccessException
104    {
105        String projectName = (String) parameters.get("project");
106        
107        if (StringUtils.isEmpty(projectName))
108        {
109            projectName = _getProjectFromRequest();
110        }
111        
112        Project project = _projectManager.getProject(projectName);
113        
114        TasksWorkspaceModule taskModule = _moduleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
115        DefaultTraversableAmetysObject tasksRoot = taskModule.getTasksRoot(project, true);
116        
117        return addTask(tasksRoot.getId(), parameters);
118    }
119    
120    /**
121     * Get the list of tasks
122     * @param projectNames The project that contain the tasks. Can be null to search in all project.
123     * @param assignedToUser Filter only the tasks assigned to the current user
124     * @param userSubscribed Filter only the tasks for which current user subscribed
125     * @param offset Offset the list of results
126     * @param limit  The maximum number of results to return
127     * @param filter Return only tasks matching the filter
128     * @param orderBy Order the list by this property. Default to the creation date
129     * @param orderAsc Sort the list by order ascendant or descendant. Default to ascendant. 
130     * @return The list as a {@link org.ametys.plugins.explorer.tasks.jcr.JCRTasksDAO.TaskListResult} object
131     * @throws ProcessingException If an error occurred
132     */
133    public TaskListResult getProjectTaskList(List<String> projectNames, boolean assignedToUser, boolean userSubscribed, Integer offset, Integer limit, String filter, String orderBy, Boolean orderAsc) throws ProcessingException
134    {
135        List<String> parentIds = _projectToTaskList(projectNames);
136        return getTaskList(parentIds, assignedToUser, userSubscribed, offset, limit, filter, orderBy, orderAsc);
137    }
138    
139    /**
140     * Get the list of tasks
141     * @param projectNames The project that contain the tasks. Can be null to search in all project.
142     * @param assignedToUser Filter only the tasks assigned to the current user
143     * @param userSubscribed Filter only the tasks for which current user subscribed
144     * @param offset Offset the list of results
145     * @param limit  The maximum number of results to return
146     * @param filter Return only tasks matching the filter
147     * @param orderBy Order the list by this property. Default to the creation date
148     * @param orderAsc Sort the list by order ascendant or descendant. Default to ascendant. 
149     * @return The list of tasks
150     * @throws ProcessingException If an error occurred
151     */
152    @Callable
153    public Map<String, Object> getProjectTasks(List<String> projectNames, boolean assignedToUser, boolean userSubscribed, Integer offset, Integer limit, String filter, String orderBy, Boolean orderAsc) throws ProcessingException
154    {
155        List<String> parentIds = _projectToTaskList(projectNames);
156        return getTasks(parentIds, assignedToUser, userSubscribed, offset, limit, filter, orderBy, orderAsc);
157    }
158    
159    /**
160     * Retrieves the list of {@link TasksList}
161     * @param projectNames The project names
162     * @return list of task list identifiers
163     */
164    private List<String> _projectToTaskList(List<String> projectNames)
165    {
166        Collection<String> finalProjectNames = projectNames;
167        
168        // All project if no project names provided
169        if (finalProjectNames == null)
170        {
171            finalProjectNames = _projectManager.getProjectNames();
172        }
173        
174        TasksWorkspaceModule taskModule = _moduleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
175        
176        return finalProjectNames.stream()
177                                     .map(_projectManager::getProject)
178                                     .map(project -> taskModule.getTasksRoot(project, true).getId())
179                                     .collect(Collectors.toList());
180    }
181    
182    /**
183     * Get the tasks of the current project
184     * @param assignedToUser True to filter only the tasks assigned to the current user
185     * @param userSubscribed Filter only the tasks for which current user subscribed
186     * @param offset Start the list of tasks at the Nth task, used for pagination
187     * @param limit Only return a maximum of N tasks, used for pagination
188     * @param filter Return only tasks matching the filter
189     * @param orderBy Order the list by this property. Default to the creation date
190     * @param orderAsc Sort the list by order ascendant or descendant. Default to ascendant.
191     * @return The list of tasks
192     * @throws IllegalAccessException If the user is not authorized
193     * @throws ProcessingException If an error occurred
194     */
195    @Callable
196    public Map<String, Object> getProjectTasks(boolean assignedToUser, boolean userSubscribed, Integer offset, Integer limit, String filter, String orderBy, Boolean orderAsc) throws IllegalAccessException, ProcessingException
197    {
198        String projectName = _getProjectFromRequest();
199        Project project = _projectManager.getProject(projectName);
200        
201        TasksWorkspaceModule taskModule = _moduleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
202        DefaultTraversableAmetysObject tasksRoot = taskModule.getTasksRoot(project, true);
203        
204        return getTasks(Collections.singletonList(tasksRoot.getId()), assignedToUser, userSubscribed, offset, limit, filter, orderBy, orderAsc);
205    }
206    
207    @Override
208    protected void setTaskDescription(ModifiableTask task, String description)
209    {
210        try
211        {
212            _htmlTransformer.transform(description, task.getDescription());
213        }
214        catch (IOException e)
215        {
216            throw new AmetysRepositoryException("Failed to transform task's description into rich text", e);
217        }
218    }
219    
220    @Override
221    protected String getTaskDescription(Task task) throws AmetysRepositoryException
222    {
223        Source contentSource = null;
224        try
225        {
226            Map<String, Object> parameters = new HashMap<>();
227            parameters.put("source", task.getDescription().getInputStream());
228            contentSource = _sourceResolver.resolveURI("cocoon://_plugins/" + _pluginName + "/convert/html2html", null, parameters);
229            return IOUtils.toString(contentSource.getInputStream(), "UTF-8");
230        }
231        catch (IOException e)
232        {
233            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
234        }
235        finally
236        {
237            _sourceResolver.release(contentSource);
238        }
239    }
240    
241    @Override
242    protected String getTaskDescriptionForEdition(Task task) throws AmetysRepositoryException
243    {
244        try
245        {
246            if (!(task instanceof ModifiableTask))
247            {
248                throw new AmetysRepositoryException("Can not transform description of a non modifiable task");
249            }
250            
251            StringBuilder sb = new StringBuilder();
252            _htmlTransformer.transformForEditing(((ModifiableTask) task).getDescription(), sb);
253            return sb.toString();
254        }
255        catch (IOException e)
256        {
257            throw new AmetysRepositoryException("Failed to transform rich text into string", e);
258        }
259    }
260    
261    @Override
262    protected Map<String, Object> getTask(Task task, boolean isEdition)
263    {
264        Map<String, Object> data = super.getTask(task, isEdition);
265        
266        Project project = getParentProject(task);
267        if (project != null)
268        {
269            data.put("project", _projectToJson(project));
270        }
271        
272        return data;
273    }
274    
275    /**
276     * Get the parent project of a task
277     * @param task The task
278     * @return the project or <code>null</code> if not found
279     */
280    protected Project getParentProject(Task task)
281    {
282        AmetysObject parent = task.getParent();
283        while (parent != null)
284        {
285            if (parent instanceof Project)
286            {
287                return (Project) parent;
288            }
289            parent = parent.getParent();
290        }
291        
292        return null;
293    }
294    
295    /**
296     * Retrieve the project JSON data
297     * @param project The project
298     * @return The data
299     */
300    protected Map<String, String> _projectToJson(Project project)
301    {
302        Map<String, String> projectData = new HashMap<>();
303        projectData.put("title", project.getTitle());
304        projectData.put("name", project.getName());
305        return projectData;
306    }
307    
308    private String _getProjectFromRequest()
309    {
310        Request request = ContextHelper.getRequest(_context);
311        return (String) request.getAttribute("projectName");
312    }
313}