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