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