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.explorer.tasks.jcr; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.time.LocalDate; 021import java.time.format.DateTimeFormatter; 022import java.util.ArrayList; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.stream.Collectors; 030 031import javax.jcr.NodeIterator; 032import javax.jcr.RepositoryException; 033import javax.jcr.Session; 034import javax.jcr.query.Query; 035 036import org.apache.avalon.framework.component.Component; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.cocoon.ProcessingException; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.lang.IllegalClassException; 043import org.apache.commons.lang3.BooleanUtils; 044import org.apache.commons.lang3.StringUtils; 045 046import org.ametys.core.observation.Event; 047import org.ametys.core.observation.ObservationManager; 048import org.ametys.core.right.RightManager; 049import org.ametys.core.right.RightManager.RightResult; 050import org.ametys.core.ui.Callable; 051import org.ametys.core.user.CurrentUserProvider; 052import org.ametys.core.user.UserIdentity; 053import org.ametys.core.user.UserManager; 054import org.ametys.core.util.DateUtils; 055import org.ametys.plugins.core.user.UserHelper; 056import org.ametys.plugins.explorer.ExplorerNode; 057import org.ametys.plugins.explorer.ModifiableExplorerNode; 058import org.ametys.plugins.explorer.ObservationConstants; 059import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 060import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO; 061import org.ametys.plugins.explorer.tasks.ModifiableTask; 062import org.ametys.plugins.explorer.tasks.Task; 063import org.ametys.plugins.explorer.tasks.Task.TaskPriority; 064import org.ametys.plugins.explorer.tasks.Task.TaskStatus; 065import org.ametys.plugins.repository.AmetysObject; 066import org.ametys.plugins.repository.AmetysObjectResolver; 067import org.ametys.plugins.repository.AmetysRepositoryException; 068import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 069import org.ametys.plugins.repository.jcr.JCRAmetysObject; 070import org.ametys.plugins.repository.metadata.ModifiableRichText; 071import org.ametys.plugins.repository.metadata.RichText; 072import org.ametys.plugins.repository.query.SortCriteria; 073import org.ametys.plugins.repository.query.expression.AndExpression; 074import org.ametys.plugins.repository.query.expression.Expression; 075import org.ametys.plugins.repository.query.expression.Expression.Operator; 076import org.ametys.plugins.repository.query.expression.OrExpression; 077import org.ametys.plugins.repository.query.expression.StringExpression; 078import org.ametys.runtime.parameter.ParameterHelper; 079import org.ametys.runtime.plugin.component.AbstractLogEnabled; 080 081/** 082 * DAO for interacting with JCRTasks 083 */ 084public class JCRTasksDAO extends AbstractLogEnabled implements Serviceable, Component 085{ 086 /** Avalon Role */ 087 public static final String ROLE = JCRTasksDAO.class.getName(); 088 089 /** Rights to view the tasks */ 090 public static final String RIGHTS_VIEW_TASKS = "Plugin_Explorer_Task_View"; 091 092 /** Rights to add a task */ 093 public static final String RIGHTS_ADD_TASK = "Plugin_Explorer_Task_Add"; 094 095 /** Rights to edit a task */ 096 public static final String RIGHTS_EDIT_TASK = "Plugin_Explorer_Task_Edit"; 097 098 /** Rights to delete a task */ 099 public static final String RIGHTS_DELETE_TASK = "Plugin_Explorer_Task_Delete"; 100 101 /** Rights to delete_all the tasks */ 102 public static final String RIGHTS_DELETE_ALL_TASK = "Plugin_Explorer_Task_Delete_All"; 103 104 /** Ametys object resolver */ 105 protected AmetysObjectResolver _resolver; 106 107 /** DAO for the explorer resources */ 108 protected ExplorerResourcesDAO _explorerResourcesDAO; 109 110 /** Current user provider */ 111 protected CurrentUserProvider _currentUserProvider; 112 113 /** The user manager */ 114 protected UserManager _userManager; 115 116 /** The observation manager */ 117 protected ObservationManager _observationManager; 118 119 /** The rights manager */ 120 protected RightManager _rightManager; 121 /** The user helper */ 122 protected UserHelper _userHelper; 123 124 125 public void service(ServiceManager manager) throws ServiceException 126 { 127 _explorerResourcesDAO = (ExplorerResourcesDAO) manager.lookup(ExplorerResourcesDAO.ROLE); 128 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 129 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 130 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 131 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 132 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 133 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 134 } 135 136 /** 137 * Add a new task 138 * @param parentId The parent node id 139 * @param parameters The task parameters 140 * @return The task data 141 * @throws IllegalAccessException If an error occurs when checking the rights 142 */ 143 @Callable 144 public Map<String, Object> addTask(String parentId, Map<String, Object> parameters) throws IllegalAccessException 145 { 146 Map<String, Object> result = new HashMap<>(); 147 148 AmetysObject object = _resolver.resolveById(parentId); 149 if (!(object instanceof ModifiableResourceCollection || object instanceof JCRTasksList)) 150 { 151 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 152 } 153 154 ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) object; 155 156 // Check user right 157 _explorerResourcesDAO.checkUserRight(object, RIGHTS_ADD_TASK); 158 159 if (!_explorerResourcesDAO.checkLock(parent)) 160 { 161 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify task list '" + object.getName() + "' but it is locked by another user"); 162 result.put("message", "locked"); 163 return result; 164 } 165 166 int index = 1; 167 String name = "task-1"; 168 while (parent.hasChild(name)) 169 { 170 index++; 171 name = "task-" + index; 172 } 173 174 JCRTask task = parent.createChild(name, JCRTaskFactory.TASK_NODETYPE); 175 176 _setTaskParameters(task, parameters, index, true); 177 178 Date now = new Date(); 179 task.setCreationDate(now); 180 task.setLastModified(now); 181 task.setAuthor(_currentUserProvider.getUser()); 182 parent.saveChanges(); 183 184 result.put("task", getTask(task, false)); 185 186 // Notify listeners 187 Map<String, Object> eventParams = new HashMap<>(); 188 eventParams.put(ObservationConstants.ARGS_TASK, task); 189 eventParams.put(ObservationConstants.ARGS_ID, task.getId()); 190 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_CREATED, _currentUserProvider.getUser(), eventParams)); 191 192 return result; 193 } 194 195 196 /** 197 * Edit a task 198 * @param id The id of the task to edit 199 * @param parameters The task parameters 200 * @return The task data 201 * @throws IllegalAccessException If an error occurs when checking the rights 202 */ 203 @Callable 204 public Map<String, Object> editTask(String id, Map<String, Object> parameters) throws IllegalAccessException 205 { 206 Map<String, Object> result = new HashMap<>(); 207 208 AmetysObject object = _resolver.resolveById(id); 209 if (!(object instanceof JCRTask)) 210 { 211 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 212 } 213 214 JCRTask task = (JCRTask) object; 215 216 // Check user right 217 UserIdentity currentUser = _currentUserProvider.getUser(); 218 boolean canEdit = currentUser.equals(task.getAuthor()) || _explorerResourcesDAO.getUserRight(currentUser, RIGHTS_EDIT_TASK, object); 219 boolean isAssigned = task.getAssignment().contains(currentUser); 220 if (!canEdit && !isAssigned) 221 { 222 throw new IllegalAccessException("User '" + currentUser + "' tried to access a privilege feature without convenient right [" + RIGHTS_EDIT_TASK 223 + ", /resources" + ((ExplorerNode) object.getParent()).getExplorerPath() + "]"); 224 } 225 226 if (!_explorerResourcesDAO.checkLock(task)) 227 { 228 getLogger().warn("User '" + currentUser + "' try to modify task '" + object.getName() + "' but it is locked by another user"); 229 result.put("message", "locked"); 230 return result; 231 } 232 233 Map<String, Object> updatedValues = _setTaskParameters(task, parameters, null, parameters.containsKey("description")); 234 235 if (task.needsSave()) 236 { 237 if (!canEdit && isAssigned && (!updatedValues.containsKey("progress") || updatedValues.size() > 1)) 238 { 239 // only the load can be edited when you are assigned to a task and without any rights 240 task.revertChanges(); 241 throw new IllegalAccessException("User '" + currentUser + "' tried to access a privilege feature without convenient right [" + RIGHTS_EDIT_TASK 242 + ", /resources" + ((ExplorerNode) object.getParent()).getExplorerPath() + "]"); 243 } 244 245 Date now = new Date(); 246 task.setLastModified(now); 247 task.saveChanges(); 248 249 // Notify listeners 250 Map<String, Object> eventParams = new HashMap<>(); 251 eventParams.put(ObservationConstants.ARGS_TASK, task); 252 eventParams.put(ObservationConstants.ARGS_ID, task.getId()); 253 254 if (updatedValues.size() == 1 && updatedValues.containsKey("status")) 255 { 256 eventParams.put("status", updatedValues.get("status")); 257 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_STATUS_CHANGED, _currentUserProvider.getUser(), eventParams)); 258 } 259 else 260 { 261 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_UPDATED, _currentUserProvider.getUser(), eventParams)); 262 } 263 } 264 265 result.put("task", getTask(task, false)); 266 267 return result; 268 } 269 270 /** 271 * Set task's properties 272 * @param task The task to edit 273 * @param parameters The JS parameters 274 * @param index The index of task 275 * @param setDescription <code>true</code> to edit description 276 * @return The map of updated values 277 */ 278 @SuppressWarnings("unchecked") 279 protected Map<String, Object> _setTaskParameters(JCRTask task, Map<String, Object> parameters, Integer index, boolean setDescription) 280 { 281 Map<String, Object> updatedValues = new HashMap<>(); 282 283 String label = (String) parameters.get("title"); 284 if (!_isTaskParameterEquals(label, task.getLabel(), "label", updatedValues)) 285 { 286 task.setLabel(label); 287 } 288 289 if (index != null) 290 { 291 task.setTaskId(Integer.toString(index)); 292 } 293 294 if (setDescription) 295 { 296 String description = (String) parameters.getOrDefault("description", StringUtils.EMPTY); 297 setTaskDescription(task, description); 298 } 299 300 String startDateAsStr = (String) parameters.getOrDefault("start", null); 301 Date startDate = null; 302 if (startDateAsStr != null) 303 { 304 LocalDate localDate = LocalDate.parse(startDateAsStr, DateTimeFormatter.ISO_LOCAL_DATE); 305 startDate = DateUtils.asDate(localDate); 306 } 307 if (!_isTaskParameterEquals(startDate, task.getStartDate(), "startDate", updatedValues)) 308 { 309 task.setStartDate(startDate); 310 } 311 312 String endDateAsStr = (String) parameters.getOrDefault("end", null); 313 Date endDate = null; 314 if (endDateAsStr != null) 315 { 316 LocalDate localDate = LocalDate.parse(endDateAsStr, DateTimeFormatter.ISO_LOCAL_DATE); 317 endDate = DateUtils.asDate(localDate); 318 } 319 if (!_isTaskParameterEquals(endDate, task.getEndDate(), "endDate", updatedValues)) 320 { 321 task.setEndDate(endDate); 322 } 323 324 TaskStatus status = TaskStatus.createsFromString((String) parameters.getOrDefault("status", null)); 325 if (!_isTaskParameterEquals(status, task.getStatus(), "status", updatedValues)) 326 { 327 task.setStatus(status); 328 } 329 330 TaskPriority priority = TaskPriority.createsFromString((String) parameters.getOrDefault("priority", null)); 331 if (!_isTaskParameterEquals(priority, task.getPriority(), "priority", updatedValues)) 332 { 333 task.setPriority(priority); 334 } 335 336 Object initialLoad = parameters.getOrDefault("load", null); 337 if (initialLoad != null) 338 { 339 if (initialLoad instanceof Integer) 340 { 341 task.setInitialLoad(new Double((Integer) initialLoad)); 342 } 343 else if (initialLoad instanceof Double) 344 { 345 task.setInitialLoad((Double) initialLoad); 346 } 347 } 348 349 Double progress = ((Integer) parameters.get("progress")).doubleValue(); 350 if (!_isTaskParameterEquals(progress, task.getProgress(), "progress", updatedValues)) 351 { 352 task.setProgress(progress); 353 } 354 355 List<String> assignment = (List<String>) parameters.getOrDefault("assignmentIds", new ArrayList<>()); 356 task.setAssignment(assignment.stream().map(user -> UserIdentity.stringToUserIdentity(user)).collect(Collectors.toList())); 357 358 List<String> subscribers = (List<String>) parameters.getOrDefault("subscribersIds", new ArrayList<>()); 359 task.setSubscribers(subscribers.stream().map(user -> UserIdentity.stringToUserIdentity(user)).collect(Collectors.toList())); 360 361 return updatedValues; 362 } 363 364 private boolean _isTaskParameterEquals(Object newValue, Object oldValue, String valueName, Map<String, Object> updatedValues) 365 { 366 if ((newValue != null && !newValue.equals(oldValue)) || (newValue == null && oldValue != null)) 367 { 368 updatedValues.put(valueName, newValue); 369 return false; 370 } 371 372 return true; 373 } 374 375 /** 376 * Update the description of a task 377 * @param task The task to update 378 * @param description The description as string 379 */ 380 protected void setTaskDescription(ModifiableTask task, String description) 381 { 382 try 383 { 384 ModifiableRichText richText = task.getDescription(); 385 386 richText.setMimeType("text/plain"); 387 richText.setLastModified(new Date()); 388 richText.setInputStream(new ByteArrayInputStream(description.getBytes("UTF-8"))); 389 } 390 catch (IOException e) 391 { 392 throw new AmetysRepositoryException("Failed to set task's description as rich text", e); 393 } 394 } 395 396 /** 397 * Get the description of a task as a String 398 * @param task the task 399 * @return The content as String 400 * @throws AmetysRepositoryException if failed to parse description 401 */ 402 protected String getTaskDescription(Task task) throws AmetysRepositoryException 403 { 404 try 405 { 406 RichText richText = task.getDescription(); 407 return IOUtils.toString(richText.getInputStream(), "UTF-8"); 408 } 409 catch (IOException e) 410 { 411 throw new AmetysRepositoryException("Failed to get task's description", e); 412 } 413 } 414 415 /** 416 * Get the description of a task to edit as a String 417 * @param task the task 418 * @return The content as String 419 * @throws AmetysRepositoryException if failed to parse description 420 */ 421 protected String getTaskDescriptionForEdition(Task task) throws AmetysRepositoryException 422 { 423 return getTaskDescription(task); 424 } 425 426 /** 427 * Get the data of a task 428 * @param taskId the task id 429 * @param isEdition true to get the task in edit mode 430 * @return The task data 431 */ 432 @Callable 433 public Map<String, Object> getTask(String taskId, boolean isEdition) 434 { 435 Task task = _resolver.resolveById(taskId); 436 return getTask(task, isEdition); 437 } 438 439 /** 440 * Get the list of tasks 441 * @param parentIds The tasks parents 442 * @param assignedToUser Filter only the tasks assigned to the current user 443 * @param userSubscribed Filter only the tasks for which current user subscribed 444 * @param offset Offset the list of results 445 * @param limit The maximum number of results to return 446 * @param filter Return only tasks matching the filter 447 * @param orderBy Order the list by this property. Default to the creation date 448 * @param orderAsc Sort the list by order ascendant or descendant. Default to ascendant. 449 * @return The list as a {@link TaskListResult} object 450 * @throws ProcessingException If an error occurred 451 */ 452 public TaskListResult getTaskList(List<String> parentIds, boolean assignedToUser, boolean userSubscribed, Integer offset, Integer limit, String filter, String orderBy, Boolean orderAsc) throws ProcessingException 453 { 454 List<Task> tasksList = new ArrayList<>(); 455 List<Expression> andExprs = new ArrayList<>(); 456 457 UserIdentity currentUser = _currentUserProvider.getUser(); 458 if (currentUser == null && (assignedToUser || userSubscribed)) 459 { 460 // Not authenticated, but looking for tasks assigned or subscribed to the current user. 461 return new TaskListResult(tasksList, 0); 462 } 463 464 _handleAssignedOrSubscribedExprs(andExprs, assignedToUser, userSubscribed, currentUser); 465 466 if (StringUtils.isNotEmpty(filter)) 467 { 468 StringExpression labelExpr = new StringExpression(JCRTask.METADATA_LABEL, Operator.WD, filter); 469 StringExpression taskIdExpr = new StringExpression(JCRTask.METADATA_TASKID, Operator.WD, filter); 470 andExprs.add(new OrExpression(labelExpr, taskIdExpr)); 471 } 472 473 SortCriteria sortCriteria = new SortCriteria(); 474 String realOrderBy = StringUtils.defaultIfEmpty(orderBy, JCRTask.METADATA_CREATIONDATE); 475 boolean realOrderAsc = BooleanUtils.isNotFalse(orderAsc); 476 sortCriteria.addCriterion(realOrderBy, realOrderAsc, true); 477 478 long size = 0; 479 try 480 { 481 for (String parentId : parentIds) 482 { 483 JCRAmetysObject object = _resolver.resolveById(parentId); 484 485 if (_rightManager.hasRight(_currentUserProvider.getUser(), RIGHTS_VIEW_TASKS, object) == RightResult.RIGHT_ALLOW) 486 { 487 Expression expr = andExprs.size() > 0 ? new AndExpression(andExprs.toArray(new Expression[andExprs.size()])) : null; 488 String xPathQuery = getTasksXpathQuery(object.getNode().getPath(), expr, sortCriteria); 489 490 Session session = object.getNode().getSession(); 491 492 @SuppressWarnings("deprecation") 493 Query query = session.getWorkspace().getQueryManager().createQuery(xPathQuery, Query.XPATH); 494 size += query.execute().getNodes().getSize(); 495 if (offset != null) 496 { 497 query.setOffset(offset); 498 } 499 if (limit != null) 500 { 501 query.setLimit(limit); 502 } 503 NodeIterator nodes = query.execute().getNodes(); 504 while (nodes.hasNext()) 505 { 506 tasksList.add(_resolver.resolve(nodes.nextNode(), false)); 507 } 508 } 509 } 510 } 511 catch (RepositoryException e) 512 { 513 throw new ProcessingException("Unable to retrieve the list of tasks", e); 514 } 515 516 // FIXME INTRANET-196 517 // tasks are not sorted and limit is wrong in case of multiple parent ids 518 return new TaskListResult(tasksList, Math.toIntExact(size)); 519 } 520 521 private void _handleAssignedOrSubscribedExprs(List<Expression> andExprs, boolean assignedToUser, boolean userSubscribed, UserIdentity currentUser) 522 { 523 if (assignedToUser || userSubscribed) 524 { 525 List<Expression> exprs = new ArrayList<>(); 526 527 if (assignedToUser) 528 { 529 exprs.add(new StringExpression(JCRTask.METADATA_ASSIGNMENT, Operator.EQ, UserIdentity.userIdentityToString(currentUser))); 530 } 531 532 if (userSubscribed) 533 { 534 exprs.add(new StringExpression(JCRTask.METADATA_SUBSCRIBERS, Operator.EQ, UserIdentity.userIdentityToString(currentUser))); 535 } 536 537 andExprs.add(new OrExpression(exprs.toArray(new Expression[exprs.size()]))); 538 } 539 } 540 541 /** 542 * Creates the XPath query corresponding to specified {@link Expression}. 543 * @param rootPath the path to the node containing the tasks 544 * @param tasksExpression the query predicates. 545 * @param sortCriteria the sort criteria. 546 * @return the created XPath query. 547 * @throws RepositoryException if an error occurred 548 */ 549 public static String getTasksXpathQuery(String rootPath, Expression tasksExpression, SortCriteria sortCriteria) throws RepositoryException 550 { 551 String predicats = null; 552 553 if (tasksExpression != null) 554 { 555 predicats = StringUtils.trimToNull(tasksExpression.build()); 556 } 557 558 String xpathQuery = "/jcr:root" 559 + rootPath 560 + "//element(*, ametys:task)" 561 + (predicats != null ? "[" + predicats + "]" : "") 562 + ((sortCriteria != null) ? (" " + sortCriteria.build()) : ""); 563 return xpathQuery; 564 } 565 566 567 /** 568 * Get the list of tasks 569 * @param parentIds The tasks parents 570 * @param assignedToUser Filter only the tasks assigned to the current user 571 * @param userSubscribed Filter only the tasks for which current user subscribed 572 * @param offset Offset the list of results 573 * @param limit The maximum number of results to return 574 * @param filter Return only tasks matching the filter 575 * @param orderBy Order the list by this property. Default to the creation date 576 * @param orderAsc Sort the list by order ascendant or descendant. Default to ascendant. 577 * @return The list of tasks 578 * @throws ProcessingException If an error occurred 579 */ 580 @Callable 581 public Map<String, Object> getTasks(List<String> parentIds, boolean assignedToUser, boolean userSubscribed, Integer offset, Integer limit, String filter, String orderBy, Boolean orderAsc) throws ProcessingException 582 { 583 TaskListResult taskListResult = getTaskList(parentIds, assignedToUser, userSubscribed, offset, limit, filter, orderBy, orderAsc); 584 585 Map<String, Object> result = new HashMap<>(); 586 587 result.put("total", taskListResult.getTotal()); 588 result.put("tasks", _tasksToJson(taskListResult.getTasks())); 589 590 return result; 591 } 592 593 /** 594 * Simple structure used to store a list of task and some metadata. 595 * Used when retrieving a list of task as a result of a search operation. 596 */ 597 public static class TaskListResult 598 { 599 /** task list */ 600 protected List<Task> _tasks; 601 /** number of result before applying the filter and the limit */ 602 protected int _total; 603 604 /** 605 * Task list result constructor 606 * @param task The task list to reference 607 * @param total number of result before applying the filter and the limit 608 */ 609 protected TaskListResult(List<Task> task, int total) 610 { 611 _tasks = task; 612 _total = total; 613 } 614 615 /** 616 * Retrieves the tasks 617 * @return the tasks 618 */ 619 public List<Task> getTasks() 620 { 621 return _tasks; 622 } 623 624 /** 625 * Retrieves the total 626 * @return the total 627 */ 628 public int getTotal() 629 { 630 return _total; 631 } 632 } 633 634 private List<Map<String, Object>> _tasksToJson(List<Task> tasks) 635 { 636 List<Map<String, Object>> result = new ArrayList<>(); 637 638 for (Task task : tasks) 639 { 640 result.add(getTask(task, false)); 641 } 642 643 return result; 644 } 645 646 /** 647 * Assign one or more task to one or more users 648 * @param taskIds The tasks ids 649 * @param users The users 650 * @return The tasks data, updated 651 * @throws IllegalAccessException If an error occurs 652 */ 653 @Callable 654 public Map<String, Object> assignTasks(List<String> taskIds, List<String> users) throws IllegalAccessException 655 { 656 Map<String, Object> result = new HashMap<>(); 657 List<JCRTask> tasks = _getTasksById(taskIds, result, RIGHTS_EDIT_TASK, false); 658 659 if (result.containsKey("message")) 660 { 661 return result; 662 } 663 664 List<UserIdentity> userIdentities = new ArrayList<>(); 665 for (String user : users) 666 { 667 userIdentities.add(UserIdentity.stringToUserIdentity(user)); 668 } 669 670 ArrayList<Map<String, Object>> tasksData = new ArrayList<>(); 671 result.put("tasks", tasksData); 672 for (JCRTask task : tasks) 673 { 674 boolean modified = false; 675 List<UserIdentity> assignment = task.getAssignment(); 676 for (UserIdentity identity : userIdentities) 677 { 678 if (!assignment.contains(identity)) 679 { 680 modified = true; 681 assignment.add(identity); 682 } 683 } 684 685 if (modified) 686 { 687 task.setAssignment(assignment); 688 689 Date now = new Date(); 690 task.setLastModified(now); 691 task.saveChanges(); 692 693 if (!assignment.isEmpty()) 694 { 695 // Notify listeners 696 Map<String, Object> eventParams = new HashMap<>(); 697 eventParams.put(ObservationConstants.ARGS_TASK, task); 698 eventParams.put(ObservationConstants.ARGS_ID, task.getId()); 699 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_ASSIGNED, _currentUserProvider.getUser(), eventParams)); 700 } 701 } 702 703 tasksData.add(getTask(task, false)); 704 } 705 706 return result; 707 } 708 709 /** 710 * Update the status of one or multiple tasks 711 * @param taskIds The tasks ids 712 * @param statusString The status 713 * @return The tasks data updated 714 * @throws IllegalAccessException If an error occurs 715 */ 716 @Callable 717 public Map<String, Object> setTasksStatus(List<String> taskIds, String statusString) throws IllegalAccessException 718 { 719 Map<String, Object> result = new HashMap<>(); 720 List<JCRTask> tasks = _getTasksById(taskIds, result, RIGHTS_EDIT_TASK, false); 721 722 if (result.containsKey("message")) 723 { 724 return result; 725 } 726 727 TaskStatus status = TaskStatus.createsFromString(statusString); 728 if (status == null) 729 { 730 getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to set the task status '" + statusString + "' which is not a valid value."); 731 result.put("message", "unknown_status"); 732 return result; 733 } 734 735 ArrayList<Map<String, Object>> tasksData = new ArrayList<>(); 736 result.put("tasks", tasksData); 737 for (JCRTask task : tasks) 738 { 739 if (!task.getStatus().equals(status)) 740 { 741 task.setStatus(status); 742 743 Date now = new Date(); 744 task.setLastModified(now); 745 task.saveChanges(); 746 747 // Notify listeners 748 Map<String, Object> eventParams = new HashMap<>(); 749 eventParams.put(ObservationConstants.ARGS_TASK, task); 750 eventParams.put(ObservationConstants.ARGS_ID, task.getId()); 751 eventParams.put("status", status.toString()); 752 753 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_STATUS_CHANGED, _currentUserProvider.getUser(), eventParams)); 754 } 755 756 tasksData.add(getTask(task, false)); 757 } 758 759 return result; 760 } 761 762 /** 763 * Update the progress of one or multiple tasks 764 * @param taskIds The tasks ids 765 * @param progress The progress 766 * @return The tasks data updated 767 * @throws IllegalAccessException If an error occurs 768 */ 769 @Callable 770 public Map<String, Object> setTasksProgress(List<String> taskIds, Integer progress) throws IllegalAccessException 771 { 772 Map<String, Object> result = new HashMap<>(); 773 List<JCRTask> tasks = _getTasksById(taskIds, result, RIGHTS_EDIT_TASK, true); 774 775 if (result.containsKey("message")) 776 { 777 return result; 778 } 779 780 ArrayList<Map<String, Object>> tasksData = new ArrayList<>(); 781 result.put("tasks", tasksData); 782 for (JCRTask task : tasks) 783 { 784 if (progress == null) 785 { 786 task.setProgress(null); 787 } 788 else if (!task.getProgress().equals(progress.doubleValue())) 789 { 790 task.setProgress(progress.doubleValue()); 791 792 Date now = new Date(); 793 task.setLastModified(now); 794 task.saveChanges(); 795 } 796 797 tasksData.add(getTask(task, false)); 798 } 799 800 return result; 801 } 802 803 /** 804 * Delete one or more tasks 805 * @param taskIds The tasks ids 806 * @return The list of tasks ids 807 * @throws IllegalAccessException If an error occurs 808 */ 809 @Callable 810 public Map<String, Object> deleteTasks(List<String> taskIds) throws IllegalAccessException 811 { 812 Map<String, Object> result = new HashMap<>(); 813 List<JCRTask> tasks = new ArrayList<>(); 814 815 UserIdentity currentUser = _currentUserProvider.getUser(); 816 for (String id : taskIds) 817 { 818 AmetysObject object = _resolver.resolveById(id); 819 if (!(object instanceof JCRTask)) 820 { 821 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 822 } 823 824 JCRTask task = (JCRTask) object; 825 826 // Check user right 827 _explorerResourcesDAO.checkUserRight(object.getParent(), task.getAuthor().equals(currentUser) ? RIGHTS_DELETE_TASK : RIGHTS_DELETE_ALL_TASK); 828 829 if (!_explorerResourcesDAO.checkLock(task)) 830 { 831 getLogger().warn("User '" + currentUser + "' try to modify task '" + object.getName() + "' but it is locked by another user"); 832 result.put("message", "locked"); 833 } 834 835 tasks.add(task); 836 } 837 838 if (result.containsKey("message")) 839 { 840 return result; 841 } 842 843 Set<ModifiableExplorerNode> parents = new HashSet<>(); 844 845 for (JCRTask task : tasks) 846 { 847 ModifiableExplorerNode parent = task.getParent(); 848 849 Map<String, Object> eventParams = new HashMap<>(); 850 eventParams.put(ObservationConstants.ARGS_TASK, task); 851 eventParams.put(ObservationConstants.ARGS_ID, task.getId()); 852 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_DELETING, currentUser, eventParams)); 853 854 eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId()); 855 eventParams.remove(ObservationConstants.ARGS_TASK); 856 eventParams.put(ObservationConstants.ARGS_PATH, task.getPath()); 857 858 task.remove(); 859 parents.add(parent); 860 861 _observationManager.notify(new Event(ObservationConstants.EVENT_TASK_DELETED, currentUser, eventParams)); 862 } 863 864 for (ModifiableExplorerNode parent : parents) 865 { 866 parent.saveChanges(); 867 } 868 869 result.put("tasks", taskIds); 870 871 return result; 872 } 873 874 875 private List<JCRTask> _getTasksById(List<String> taskIds, Map<String, Object> result, String right, boolean allowAssigned) throws IllegalAccessException 876 { 877 List<JCRTask> tasks = new ArrayList<>(); 878 879 for (String id : taskIds) 880 { 881 AmetysObject object = _resolver.resolveById(id); 882 if (!(object instanceof JCRTask)) 883 { 884 throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass()); 885 } 886 887 JCRTask task = (JCRTask) object; 888 889 // Check user right 890 UserIdentity currentUser = _currentUserProvider.getUser(); 891 boolean canEdit = currentUser.equals(task.getAuthor()) || _explorerResourcesDAO.getUserRight(currentUser, right, object); 892 boolean isAssigned = allowAssigned && task.getAssignment().contains(currentUser); 893 if (!canEdit && !isAssigned) 894 { 895 throw new IllegalAccessException("User '" + currentUser + "' tried to access a privilege feature without convenient right [" + right 896 + ", /resources" + ((ExplorerNode) object.getParent()).getExplorerPath() + "]"); 897 } 898 899 if (!_explorerResourcesDAO.checkLock(task)) 900 { 901 getLogger().warn("User '" + currentUser + "' try to modify task '" + object.getName() + "' but it is locked by another user"); 902 result.put("message", "locked"); 903 } 904 905 tasks.add(task); 906 } 907 return tasks; 908 } 909 910 /** 911 * Transform a task to JSON data 912 * @param task The task 913 * @param isEdition true to get the task in edit mode 914 * @return The JSON data 915 */ 916 protected Map<String, Object> getTask(Task task, boolean isEdition) 917 { 918 Map<String, Object> result = new HashMap<>(); 919 920 Date start = task.getStartDate(); 921 Date end = task.getEndDate(); 922 923 result.put("id", task.getId()); 924 result.put("taskId", task.getTaskId()); 925 result.put("title", task.getLabel()); 926 result.put("description", isEdition ? getTaskDescriptionForEdition(task) : getTaskDescription(task)); 927 result.put("startDate", ParameterHelper.valueToString(start)); 928 result.put("endDate", ParameterHelper.valueToString(end)); 929 result.put("status", task.getStatus().toString()); 930 result.put("priority", task.getPriority().toString()); 931 result.put("load", task.getInitialLoad()); 932 result.put("progress", task.getProgress()); 933 result.put("assignment", _assignmentToJSON(task)); 934 result.put("subscribers", _subscribersToJSON(task)); 935 result.put("creationDate", task.getCreationDate()); 936 result.put("lastModified", task.getLastModified()); 937 938 Map<String, Object> rights = new HashMap<>(); 939 940 UserIdentity currentUser = _currentUserProvider.getUser(); 941 rights.put("assigned", task.getAssignment().contains(currentUser)); 942 rights.put("edit", _explorerResourcesDAO.getUserRight(currentUser, RIGHTS_EDIT_TASK, task)); 943 boolean canDelete = _explorerResourcesDAO.getUserRight(currentUser, RIGHTS_DELETE_ALL_TASK, task); 944 if (!canDelete && (task instanceof JCRTask)) 945 { 946 JCRTask jcrtask = (JCRTask) task; 947 if (jcrtask.getAuthor().equals(currentUser)) 948 { 949 canDelete = _explorerResourcesDAO.getUserRight(currentUser, RIGHTS_DELETE_TASK, task); 950 } 951 } 952 rights.put("delete", canDelete); 953 result.put("rights", rights); 954 955 return result; 956 } 957 958 /** 959 * Transform assignments of a task to JSON data 960 * @param task The task 961 * @return The JSON data 962 */ 963 protected List<Map<String, Object>> _assignmentToJSON(Task task) 964 { 965 return _userHelper.userIdentities2json(task.getAssignment()); 966 } 967 968 /** 969 * Transform subscribers of a task to JSON data 970 * @param task The task 971 * @return The JSON data 972 */ 973 protected List<Map<String, Object>> _subscribersToJSON(Task task) 974 { 975 return _userHelper.userIdentities2json(task.getSubscribers()); 976 } 977}