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