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}