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.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.components.ContextHelper; 028import org.apache.cocoon.environment.Request; 029import org.apache.commons.lang.IllegalClassException; 030 031import org.ametys.cms.data.Binary; 032import org.ametys.core.right.RightManager.RightResult; 033import org.ametys.core.ui.Callable; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.plugins.explorer.ExplorerNode; 036import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 037import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollectionFactory; 038import org.ametys.plugins.repository.AmetysRepositoryException; 039import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 040import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 041import org.ametys.plugins.workspaces.AbstractWorkspaceModule; 042import org.ametys.plugins.workspaces.project.objects.Project; 043import org.ametys.plugins.workspaces.util.StatisticColumn; 044import org.ametys.plugins.workspaces.util.StatisticsColumnType; 045import org.ametys.runtime.i18n.I18nizableText; 046import org.ametys.web.repository.page.ModifiablePage; 047import org.ametys.web.repository.page.ModifiableZone; 048import org.ametys.web.repository.page.ModifiableZoneItem; 049import org.ametys.web.repository.page.ZoneItem.ZoneType; 050 051import com.google.common.collect.ImmutableSet; 052 053/** 054 * Tasks manager for workspaces 055 */ 056public class TasksWorkspaceModule extends AbstractWorkspaceModule 057{ 058 /** The id of task module */ 059 public static final String TASK_MODULE_ID = TasksWorkspaceModule.class.getName(); 060 061 /** Workspaces tasks list node name */ 062 private static final String __WORKSPACES_TASKS_NODE_NAME = "tasks"; 063 064 /** Workspaces root tasks node name */ 065 private static final String __WORKSPACES_TASKS_ROOT_NODE_NAME = "tasks-root"; 066 067 /** Workspaces root tasks lists node name */ 068 private static final String __WORKSPACES_TASKS_LIST_ROOT_NODE_NAME = "tasks-list-root"; 069 070 private static final String __TASK_NUMBER_HEADER_ID = __WORKSPACES_TASKS_NODE_NAME + "$task_number"; 071 072 /** The tasks module DAO */ 073 private WorkspaceTaskDAO _workspaceTaskDAO; 074 075 @Override 076 public void service(ServiceManager serviceManager) throws ServiceException 077 { 078 super.service(serviceManager); 079 _workspaceTaskDAO = (WorkspaceTaskDAO) serviceManager.lookup(WorkspaceTaskDAO.ROLE); 080 } 081 082 @Override 083 public String getId() 084 { 085 return TASK_MODULE_ID; 086 } 087 088 @Override 089 public String getModuleName() 090 { 091 return __WORKSPACES_TASKS_NODE_NAME; 092 } 093 094 public int getOrder() 095 { 096 return ORDER_TASKS; 097 } 098 099 @Override 100 protected String getModulePageName() 101 { 102 return "tasks"; 103 } 104 105 public I18nizableText getModuleTitle() 106 { 107 return new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_TASK_LABEL"); 108 } 109 public I18nizableText getModuleDescription() 110 { 111 return new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_SERVICE_MODULE_TASK_DESCRIPTION"); 112 } 113 @Override 114 protected I18nizableText getModulePageTitle() 115 { 116 return new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_TASKS_TITLE"); 117 } 118 119 @Override 120 protected void initializeModulePage(ModifiablePage taskPage) 121 { 122 ModifiableZone defaultZone = taskPage.createZone("default"); 123 124 String serviceId = "org.ametys.plugins.workspaces.module.Tasks"; 125 ModifiableZoneItem defaultZoneItem = defaultZone.addZoneItem(); 126 defaultZoneItem.setType(ZoneType.SERVICE); 127 defaultZoneItem.setServiceId(serviceId); 128 129 ModifiableModelAwareDataHolder serviceDataHolder = defaultZoneItem.getServiceParameters(); 130 serviceDataHolder.setValue("xslt", _getDefaultXslt(serviceId)); 131 } 132 133 /** 134 * Retrieves the rights for the current user in the project 135 * @return The project 136 */ 137 @Callable 138 public Map<String, Object> getTasksModuleRights() 139 { 140 Map<String, Object> rights = new HashMap<>(); 141 142 143 Request request = ContextHelper.getRequest(_context); 144 String projectName = (String) request.getAttribute("projectName"); 145 Project project = _projectManager.getProject(projectName); 146 ModifiableResourceCollection tasksRoot = getModuleRoot(project, false); 147 148 UserIdentity currentUser = _currentUserProvider.getUser(); 149 rights.put("view", tasksRoot != null && _rightManager.hasRight(currentUser, AbstractWorkspaceTaskDAO.RIGHTS_HANDLE_TASK, tasksRoot) == RightResult.RIGHT_ALLOW); 150 rights.put("add", tasksRoot != null && _rightManager.hasRight(currentUser, AbstractWorkspaceTaskDAO.RIGHTS_HANDLE_TASK, tasksRoot) == RightResult.RIGHT_ALLOW); 151 152 return rights; 153 } 154 155 /** 156 * Retrieves the rights for the current user in the projects list 157 * @param projectNames The name of projects to checks rights from 158 * @return The rights data 159 */ 160 @Callable 161 public Map<String, Object> getTasksServiceRights(List<String> projectNames) 162 { 163 Map<String, Object> rights = new HashMap<>(); 164 165 List<String> rightAdd = new ArrayList<>(); 166 167 UserIdentity currentUser = _currentUserProvider.getUser(); 168 for (String projectName : projectNames) 169 { 170 Project project = _projectManager.getProject(projectName); 171 ModifiableResourceCollection tasksRoot = getModuleRoot(project, false); 172 173 if (tasksRoot != null && _rightManager.hasRight(currentUser, AbstractWorkspaceTaskDAO.RIGHTS_HANDLE_TASK, tasksRoot) == RightResult.RIGHT_ALLOW) 174 { 175 rightAdd.add(projectName); 176 } 177 } 178 179 rights.put("add", rightAdd); 180 181 return rights; 182 } 183 184 /** 185 * Get the URI of a task in project'site 186 * @param project The project 187 * @param taskId The id of the task 188 * @return The thread uri 189 */ 190 public String getTaskUri(Project project, String taskId) 191 { 192 String moduleUrl = getModuleUrl(project); 193 if (moduleUrl != null) 194 { 195 StringBuilder sb = new StringBuilder(); 196 sb.append(moduleUrl); 197 sb.append("#task-").append(taskId); 198 199 return sb.toString(); 200 } 201 202 return null; 203 } 204 205 @Override 206 public ModifiableResourceCollection getModuleRoot(Project project, boolean create) 207 { 208 try 209 { 210 ExplorerNode projectRootNode = project.getExplorerRootNode(); 211 212 if (projectRootNode instanceof ModifiableResourceCollection) 213 { 214 ModifiableResourceCollection projectRootNodeRc = (ModifiableResourceCollection) projectRootNode; 215 return _getAmetysObject(projectRootNodeRc, __WORKSPACES_TASKS_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 216 } 217 else 218 { 219 throw new IllegalClassException(ModifiableResourceCollection.class, projectRootNode.getClass()); 220 } 221 } 222 catch (AmetysRepositoryException e) 223 { 224 throw new AmetysRepositoryException("Error getting the documents root node.", e); 225 } 226 } 227 228 /** 229 * Get the root for tasks 230 * @param project The project 231 * @param create true to create root if not exists 232 * @return The root for tasks 233 */ 234 public DefaultTraversableAmetysObject getTasksRoot(Project project, boolean create) 235 { 236 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 237 return _getAmetysObject(moduleRoot, __WORKSPACES_TASKS_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 238 } 239 240 /** 241 * Get the root for tasks lists 242 * @param project The project 243 * @param create true to create root if not exists 244 * @return The root for tasks lists 245 */ 246 public DefaultTraversableAmetysObject getTasksListsRoot(Project project, boolean create) 247 { 248 ModifiableResourceCollection moduleRoot = getModuleRoot(project, create); 249 return _getAmetysObject(moduleRoot, __WORKSPACES_TASKS_LIST_ROOT_NODE_NAME, JCRResourcesCollectionFactory.RESOURCESCOLLECTION_NODETYPE, create); 250 } 251 252 253 @Override 254 public Set<String> getAllowedEventTypes() 255 { 256 return ImmutableSet.of("task.created", "task.assigned", "task.closed.status.changed"); 257 } 258 259 @Override 260 public Map<String, Object> _getInternalStatistics(Project project, boolean isActive) 261 { 262 if (isActive) 263 { 264 return Map.of(__TASK_NUMBER_HEADER_ID, _workspaceTaskDAO.getTasksCount(project)); 265 } 266 else 267 { 268 return Map.of(__TASK_NUMBER_HEADER_ID, __SIZE_INACTIVE); 269 } 270 } 271 272 @Override 273 protected long _getModuleSize(Project project) 274 { 275 return _workspaceTaskDAO.getProjectTasks(project) 276 .stream() 277 .map(Task::getAttachments) 278 .flatMap(Collection::stream) 279 .mapToLong(Binary::getLength) 280 .sum(); 281 } 282 283 @Override 284 public List<StatisticColumn> _getInternalStatisticModel() 285 { 286 return List.of(new StatisticColumn(__TASK_NUMBER_HEADER_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_TASK_NUMBER")) 287 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderElements") 288 .withType(StatisticsColumnType.LONG) 289 .withGroup(GROUP_HEADER_ELEMENTS_ID)); 290 } 291 292 @Override 293 protected boolean _showModuleSize() 294 { 295 return true; 296 } 297}