001/* 002 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Mariana Cedica 018 * 019 * $Id$ 020 */ 021package org.nuxeo.ecm.platform.routing.web; 022 023import static org.jboss.seam.ScopeType.CONVERSATION; 024 025import java.io.Serializable; 026import java.text.ParseException; 027import java.text.SimpleDateFormat; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.Date; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.LinkedHashMap; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038import javax.faces.application.FacesMessage; 039import javax.faces.component.EditableValueHolder; 040import javax.faces.component.UIComponent; 041import javax.faces.context.FacesContext; 042import javax.faces.validator.ValidatorException; 043 044import org.apache.commons.lang3.StringUtils; 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047import org.jboss.seam.annotations.In; 048import org.jboss.seam.annotations.Name; 049import org.jboss.seam.annotations.Observer; 050import org.jboss.seam.annotations.Scope; 051import org.jboss.seam.annotations.intercept.BypassInterceptors; 052import org.jboss.seam.annotations.web.RequestParameter; 053import org.jboss.seam.core.Events; 054import org.jboss.seam.faces.FacesMessages; 055import org.jboss.seam.international.StatusMessage; 056import org.nuxeo.ecm.core.api.CoreSession; 057import org.nuxeo.ecm.core.api.DocumentModel; 058import org.nuxeo.ecm.core.api.DocumentNotFoundException; 059import org.nuxeo.ecm.core.api.IdRef; 060import org.nuxeo.ecm.core.api.NuxeoException; 061import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 062import org.nuxeo.ecm.core.api.security.SecurityConstants; 063import org.nuxeo.ecm.platform.actions.Action; 064import org.nuxeo.ecm.platform.actions.ActionContext; 065import org.nuxeo.ecm.platform.actions.ejb.ActionManager; 066import org.nuxeo.ecm.platform.contentview.seam.ContentViewActions; 067import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition; 068import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager; 069import org.nuxeo.ecm.platform.routing.api.DocumentRoutingConstants; 070import org.nuxeo.ecm.platform.routing.api.DocumentRoutingService; 071import org.nuxeo.ecm.platform.routing.api.exception.DocumentRouteException; 072import org.nuxeo.ecm.platform.routing.core.impl.GraphNode; 073import org.nuxeo.ecm.platform.routing.core.impl.GraphNode.Button; 074import org.nuxeo.ecm.platform.routing.core.impl.GraphRoute; 075import org.nuxeo.ecm.platform.task.Task; 076import org.nuxeo.ecm.platform.task.TaskEventNames; 077import org.nuxeo.ecm.platform.task.TaskImpl; 078import org.nuxeo.ecm.platform.ui.web.api.NavigationContext; 079import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 080import org.nuxeo.ecm.webapp.action.ActionContextProvider; 081import org.nuxeo.ecm.webapp.documentsLists.DocumentsListsManager; 082import org.nuxeo.ecm.webapp.helpers.EventNames; 083import org.nuxeo.runtime.api.Framework; 084 085/** 086 * Task validators 087 * 088 * @since 5.6 089 */ 090@Scope(CONVERSATION) 091@Name("routingTaskActions") 092public class RoutingTaskActionsBean implements Serializable { 093 094 private static final long serialVersionUID = 1L; 095 096 private static final Log log = LogFactory.getLog(RoutingTaskActionsBean.class); 097 098 public static final String SUBJECT_PATTERN = "([a-zA-Z_0-9]*(:)[a-zA-Z_0-9]*)"; 099 100 /** 101 * Runtime property name, that makes it possible to cache actions available on a given task, depending on its type. 102 * <p> 103 * This caching is global to all tasks in the platform, and will not work correctly if some tasks are filtering some 104 * actions depending on local variables, for instance. 105 * 106 * @since 5.7 107 */ 108 public static final String CACHE_ACTIONS_PER_TASK_TYPE_PROP_NAME = "org.nuxeo.routing.cacheActionsPerTaskType"; 109 110 @In(create = true, required = false) 111 protected transient CoreSession documentManager; 112 113 @In(required = true, create = true) 114 protected NavigationContext navigationContext; 115 116 @In(create = true, required = false) 117 protected FacesMessages facesMessages; 118 119 @In(create = true) 120 protected Map<String, String> messages; 121 122 @In(create = true) 123 protected transient DocumentsListsManager documentsListsManager; 124 125 @In(create = true, required = false) 126 protected transient ActionContextProvider actionContextProvider; 127 128 @In(create = true, required = false) 129 protected ContentViewActions contentViewActions; 130 131 @RequestParameter("button") 132 protected String button; 133 134 protected ActionManager actionService; 135 136 protected Map<String, TaskInfo> tasksInfoCache = new HashMap<String, TaskInfo>(); 137 138 protected Task currentTask; 139 140 public void validateTaskDueDate(FacesContext context, UIComponent component, Object value) { 141 final String DATE_FORMAT = "dd/MM/yyyy"; 142 SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); 143 144 String messageString = null; 145 if (value != null) { 146 try { 147 Date dueDate = dateFormat.parse(dateFormat.format((Date) value)); 148 Date today = dateFormat.parse(dateFormat.format(new Date())); 149 if (dueDate.before(today)) { 150 messageString = "label.workflow.error.outdated_duedate"; 151 } 152 } catch (ParseException e) { 153 messageString = "label.workflow.error.date_parsing"; 154 } 155 } 156 157 if (messageString != null) { 158 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context, 159 "label.workflow.error.outdated_duedate"), null); 160 ((EditableValueHolder) component).setValid(false); 161 context.addMessage(component.getClientId(context), message); 162 } 163 } 164 165 public void validateSubject(FacesContext context, UIComponent component, Object value) { 166 if (!((value instanceof String) && ((String) value).matches(SUBJECT_PATTERN))) { 167 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, ComponentUtils.translate(context, 168 "label.document.routing.invalid.subject"), null); 169 context.addMessage(null, message); 170 throw new ValidatorException(message); 171 } 172 } 173 174 public String getTaskLayout(Task task) { 175 return getTaskInfo(task, true).layout; 176 } 177 178 public List<Action> getTaskButtons(Task task) { 179 List<Button> buttons = getTaskInfo(task, true).buttons; 180 List<Action> actions = new ArrayList<Action>(); 181 182 DocumentModel workflowInstance = documentManager.getDocument(new IdRef(task.getProcessId())); 183 GraphRoute workflow = workflowInstance.getAdapter(GraphRoute.class); 184 if (workflow == null) { 185 // task was not created by a workflow process , no actions to 186 // display; 187 return actions; 188 } 189 GraphNode node = workflow.getNode(task.getType()); 190 for (Button button : buttons) { 191 Action action = new Action(button.getName(), Action.EMPTY_CATEGORIES); 192 action.setLabel(button.getLabel()); 193 boolean displayAction = true; 194 if (StringUtils.isNotEmpty(button.getFilter())) { 195 ActionContext actionContext = actionContextProvider.createActionContext(); 196 if (node != null) { 197 Map<String, Object> workflowContextualInfo = new HashMap<String, Object>(); 198 workflowContextualInfo.putAll(node.getWorkflowContextualInfo(documentManager, true)); 199 actionContext.putAllLocalVariables(workflowContextualInfo); 200 } 201 displayAction = getActionService().checkFilter(button.filter, actionContext); 202 } 203 if (displayAction) { 204 actions.add(action); 205 } 206 } 207 return actions; 208 } 209 210 public String endTask(Task task) { 211 // collect form data 212 Map<String, Object> data = new HashMap<String, Object>(); 213 Map<String, Serializable> formVariables = getFormVariables(task); 214 if (getFormVariables(task) != null) { 215 data.put("WorkflowVariables", getFormVariables(task)); 216 data.put("NodeVariables", getFormVariables(task)); 217 // if there is a comment on the submitted form, pass it to be 218 // logged 219 // by audit 220 if (formVariables.containsKey(GraphNode.NODE_VARIABLE_COMMENT)) { 221 data.put(GraphNode.NODE_VARIABLE_COMMENT, formVariables.get(GraphNode.NODE_VARIABLE_COMMENT)); 222 } 223 } 224 // add the button name that was clicked 225 try { 226 DocumentRoutingService routing = Framework.getService(DocumentRoutingService.class); 227 routing.endTask(documentManager, task, data, button); 228 facesMessages.add(StatusMessage.Severity.INFO, messages.get("workflow.feedback.info.taskEnded")); 229 } catch (DocumentRouteException e) { 230 log.error(e, e); 231 facesMessages.add(StatusMessage.Severity.ERROR, messages.get("workflow.feedback.error.taskEnded")); 232 } 233 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 234 clear(task.getId()); 235 if (navigationContext.getCurrentDocument() != null 236 && documentManager.hasPermission(navigationContext.getCurrentDocument().getRef(), 237 SecurityConstants.READ)) { 238 return null; 239 } 240 // if the user only had temporary permissions on the current doc given 241 // by the workflow 242 navigationContext.setCurrentDocument(null); 243 return navigationContext.goHome(); 244 } 245 246 private void clear(String taskId) { 247 button = null; 248 if (tasksInfoCache.containsKey(taskId)) { 249 tasksInfoCache.remove(taskId); 250 } 251 } 252 253 public Map<String, Serializable> getFormVariables(Task task) { 254 return getTaskInfo(task, true).formVariables; 255 } 256 257 public class TaskInfo { 258 protected HashMap<String, Serializable> formVariables; 259 260 protected String layout; 261 262 protected boolean canBeReassigned; 263 264 protected List<Button> buttons; 265 266 protected List<String> actors; 267 268 protected String comment; 269 270 protected String taskId; 271 272 protected String name; 273 274 protected TaskInfo(String taskId, HashMap<String, Serializable> formVariables, String layout, 275 List<Button> buttons, boolean canBeReassigned, String name) { 276 this.formVariables = formVariables; 277 this.layout = layout; 278 this.buttons = buttons; 279 this.canBeReassigned = canBeReassigned; 280 this.taskId = taskId; 281 this.name = name; 282 } 283 284 public List<String> getActors() { 285 return actors; 286 } 287 288 public void setActors(List<String> actors) { 289 this.actors = actors; 290 } 291 292 public String getComment() { 293 return comment; 294 } 295 296 public void setComment(String comment) { 297 this.comment = comment; 298 } 299 300 public boolean isCanBeReassigned() { 301 return canBeReassigned; 302 } 303 304 public String getTaskId() { 305 return taskId; 306 } 307 308 public String getName() { 309 return name; 310 } 311 } 312 313 // we have to be unrestricted to get this info 314 // because the current user may not be the one that started the 315 // workflow 316 public TaskInfo getTaskInfo(final Task task, final boolean getFormVariables) { 317 if (tasksInfoCache.containsKey(task.getId())) { 318 return tasksInfoCache.get(task.getId()); 319 } 320 final String routeDocId = task.getVariable(DocumentRoutingConstants.TASK_ROUTE_INSTANCE_DOCUMENT_ID_KEY); 321 final String nodeId = task.getVariable(DocumentRoutingConstants.TASK_NODE_ID_KEY); 322 if (routeDocId == null) { 323 throw new NuxeoException("Can not get the source graph for this task"); 324 } 325 if (nodeId == null) { 326 throw new NuxeoException("Can not get the source node for this task"); 327 } 328 final TaskInfo[] res = new TaskInfo[1]; 329 new UnrestrictedSessionRunner(documentManager) { 330 @Override 331 public void run() { 332 DocumentModel doc = session.getDocument(new IdRef(routeDocId)); 333 GraphRoute route = doc.getAdapter(GraphRoute.class); 334 GraphNode node = route.getNode(nodeId); 335 HashMap<String, Serializable> map = new HashMap<String, Serializable>(); 336 if (getFormVariables) { 337 map.putAll(node.getVariables()); 338 map.putAll(route.getVariables()); 339 } 340 res[0] = new TaskInfo(task.getId(), map, node.getTaskLayout(), node.getTaskButtons(), 341 node.allowTaskReassignment(), task.getName()); 342 } 343 }.runUnrestricted(); 344 // don't add tasks in cache when are fetched without the form variables 345 // for 346 // bulk processing 347 if (getFormVariables) { 348 tasksInfoCache.put(task.getId(), res[0]); 349 } 350 return res[0]; 351 } 352 353 /** 354 * @since 5.6 355 */ 356 public boolean isRoutingTask(Task task) { 357 return task.getDocument().hasFacet(DocumentRoutingConstants.ROUTING_TASK_FACET_NAME); 358 } 359 360 /** 361 * @since 5.6 362 */ 363 public List<Action> getTaskActions(Task task) { 364 return new ArrayList<Action>(getTaskActionsMap(task).values()); 365 } 366 367 // temp method because Studio also refers to empty layouts 368 protected boolean isLayoutEmpty(String layoutName) { 369 if (layoutName == null || layoutName.isEmpty()) { 370 return true; 371 } 372 // explore the layout and find out if it contains only empty widgets 373 WebLayoutManager lm = Framework.getService(WebLayoutManager.class); 374 LayoutDefinition layout = lm.getLayoutDefinition(layoutName); 375 if (layout == null || layout.isEmpty()) { 376 return true; 377 } 378 return false; 379 } 380 381 /** 382 * Helper to generate a unique action id for all task types 383 * 384 * @since 5.7 385 */ 386 protected String getTaskActionId(Task task, String buttonId) { 387 return String.format("%s_%s", task.getType(), buttonId); 388 } 389 390 /** 391 * @since 5.6 392 */ 393 public Map<String, Action> getTaskActionsMap(Task task) { 394 Map<String, Action> actions = new LinkedHashMap<String, Action>(); 395 TaskInfo taskInfo = getTaskInfo(task, true); 396 String layout = taskInfo.layout; 397 List<Button> buttons = taskInfo.buttons; 398 399 boolean addLayout = !isLayoutEmpty(layout); 400 Map<String, Serializable> props = null; 401 if (addLayout) { 402 props = new HashMap<String, Serializable>(); 403 props.put("layout", layout); 404 props.put("formVariables", taskInfo.formVariables); 405 } 406 407 if (buttons != null && !buttons.isEmpty()) { 408 for (Button button : buttons) { 409 String buttonId = button.getName(); 410 String id = getTaskActionId(task, buttonId); 411 Action action = new Action(id, Action.EMPTY_CATEGORIES); 412 action.setLabel(button.getLabel()); 413 Map<String, Serializable> actionProps = new HashMap<String, Serializable>(); 414 actionProps.put("buttonId", buttonId); 415 if (addLayout) { 416 actionProps.putAll(props); 417 action.setProperties(actionProps); 418 action.setType("fancybox"); 419 } else { 420 action.setProperties(actionProps); 421 action.setType("link"); 422 } 423 boolean displayAction = true; 424 if (StringUtils.isNotEmpty(button.getFilter())) { 425 displayAction = getActionService().checkFilter(button.filter, 426 actionContextProvider.createActionContext()); 427 } 428 if (displayAction) { 429 actions.put(id, action); 430 } 431 } 432 } 433 434 // If there is a form attached to these tasks, add a generic 435 // process action to open the fancy box. 436 // The form of the first task will be displayed, but all the tasks 437 // concerned by this action share the same form, as they share the 438 // same type. 439 if (addLayout && !actions.isEmpty()) { 440 String id = getTaskActionId(task, "process_task"); 441 Action processAction = new Action(id, Action.EMPTY_CATEGORIES); 442 processAction.setProperties(props); 443 processAction.setType("process_task"); 444 actions.put(id, processAction); 445 } 446 447 return actions; 448 } 449 450 /** 451 * Returns actions for task document buttons defined in the workflow graph 452 * 453 * @since 5.6 454 */ 455 @SuppressWarnings("boxing") 456 public List<Action> getTaskActions(String selectionListName) { 457 Map<String, Action> actions = new LinkedHashMap<String, Action>(); 458 Map<String, Map<String, Action>> actionsPerTaskType = new LinkedHashMap<String, Map<String, Action>>(); 459 Map<String, Integer> actionsCounter = new HashMap<String, Integer>(); 460 List<DocumentModel> docs = documentsListsManager.getWorkingList(selectionListName); 461 boolean cachePerType = Boolean.TRUE.equals(Boolean.valueOf(Framework.getProperty(CACHE_ACTIONS_PER_TASK_TYPE_PROP_NAME))); 462 int taskDocsNum = 0; 463 if (docs != null && !docs.isEmpty()) { 464 for (DocumentModel doc : docs) { 465 if (doc.hasFacet(DocumentRoutingConstants.ROUTING_TASK_FACET_NAME)) { 466 Task task = new TaskImpl(doc); 467 String taskType = task.getType(); 468 Map<String, Action> taskActions = Collections.emptyMap(); 469 // if caching per type, fill the per type map, else update 470 // actions directly 471 if (cachePerType) { 472 if (actionsPerTaskType.containsKey(taskType)) { 473 taskActions = actionsPerTaskType.get(taskType); 474 } else { 475 taskActions = getTaskActionsMap(task); 476 actionsPerTaskType.put(taskType, taskActions); 477 } 478 } else { 479 taskActions = getTaskActionsMap(task); 480 actions.putAll(taskActions); 481 } 482 for (String actionId : taskActions.keySet()) { 483 Integer count = actionsCounter.get(actionId); 484 if (count == null) { 485 actionsCounter.put(actionId, 1); 486 } else { 487 actionsCounter.put(actionId, count + 1); 488 } 489 } 490 taskDocsNum++; 491 } 492 } 493 } 494 if (cachePerType) { 495 // initialize actions for cache map 496 for (Map<String, Action> actionsPerType : actionsPerTaskType.values()) { 497 actions.putAll(actionsPerType); 498 } 499 } 500 List<Action> res = new ArrayList<Action>(actions.values()); 501 for (Action action : res) { 502 if (!actionsCounter.get(action.getId()).equals(taskDocsNum)) { 503 action.setAvailable(false); 504 } 505 } 506 return res; 507 } 508 509 /** 510 * Ends a task given a selection list name and an action 511 * 512 * @since 5.6 513 */ 514 @SuppressWarnings("unchecked") 515 public String endTasks(String selectionListName, Action taskAction) { 516 // collect form data 517 Map<String, Object> data = new HashMap<String, Object>(); 518 String buttonId = (String) taskAction.getProperties().get("buttonId"); 519 Map<String, Serializable> formVariables = (Map<String, Serializable>) taskAction.getProperties().get( 520 "formVariables"); 521 if (formVariables != null && !formVariables.isEmpty()) { 522 data.put("WorkflowVariables", formVariables); 523 data.put("NodeVariables", formVariables); 524 // if there is a comment on the submitted form, pass it to be 525 // logged by audit 526 if (formVariables.containsKey(GraphNode.NODE_VARIABLE_COMMENT)) { 527 data.put(GraphNode.NODE_VARIABLE_COMMENT, formVariables.get(GraphNode.NODE_VARIABLE_COMMENT)); 528 } 529 } 530 531 // get task documents 532 boolean hasErrors = false; 533 DocumentRoutingService routing = Framework.getService(DocumentRoutingService.class); 534 List<DocumentModel> docs = documentsListsManager.getWorkingList(selectionListName); 535 if (docs != null && !docs.isEmpty()) { 536 for (DocumentModel doc : docs) { 537 if (doc.hasFacet(DocumentRoutingConstants.ROUTING_TASK_FACET_NAME)) { 538 // add the button name that was clicked 539 try { 540 routing.endTask(documentManager, new TaskImpl(doc), data, buttonId); 541 } catch (DocumentRouteException e) { 542 log.error(e, e); 543 hasErrors = true; 544 } 545 } 546 } 547 } 548 if (hasErrors) { 549 facesMessages.add(StatusMessage.Severity.ERROR, messages.get("workflow.feedback.error.tasksEnded")); 550 } else { 551 facesMessages.add(StatusMessage.Severity.INFO, messages.get("workflow.feedback.info.tasksEnded")); 552 } 553 // reset selection list 554 documentsListsManager.resetWorkingList(selectionListName); 555 // raise document change event to trigger refresh of content views 556 // listing task documents. 557 Events.instance().raiseEvent(EventNames.DOCUMENT_CHANGED); 558 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 559 return null; 560 } 561 562 private ActionManager getActionService() { 563 if (actionService == null) { 564 actionService = Framework.getService(ActionManager.class); 565 } 566 return actionService; 567 } 568 569 /*** 570 * @since 5.7 571 */ 572 @Observer(value = { TaskEventNames.WORKFLOW_TASK_COMPLETED, TaskEventNames.WORKFLOW_TASK_REASSIGNED, 573 TaskEventNames.WORKFLOW_TASK_DELEGATED }) 574 @BypassInterceptors 575 public void OnTaskCompleted() { 576 if (contentViewActions != null) { 577 contentViewActions.refreshOnSeamEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 578 contentViewActions.resetPageProviderOnSeamEvent(TaskEventNames.WORKFLOW_TASK_COMPLETED); 579 } 580 tasksInfoCache.clear(); 581 currentTask = null; 582 } 583 584 /** 585 * @since 5.7.3 586 */ 587 public String reassignTask(TaskInfo taskInfo) { 588 try { 589 Framework.getService(DocumentRoutingService.class).reassignTask(documentManager, taskInfo.getTaskId(), 590 taskInfo.getActors(), taskInfo.getComment()); 591 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_REASSIGNED); 592 } catch (DocumentRouteException e) { 593 log.error(e); 594 facesMessages.add(StatusMessage.Severity.ERROR, messages.get("workflow.feedback.error.taskEnded")); 595 } 596 return null; 597 } 598 599 /** 600 * @since 5.7.3 601 */ 602 public String getWorkflowTitle(String instanceId) { 603 String workflowTitle = ""; 604 605 try { 606 DocumentModel routeInstance = documentManager.getDocument(new IdRef(instanceId)); 607 workflowTitle = routeInstance.getTitle(); 608 } catch (DocumentNotFoundException e) { 609 log.error("Can not fetch route instance with id " + instanceId, e); 610 } 611 return workflowTitle; 612 } 613 614 /** 615 * @since 5.8 616 */ 617 public String delegateTask(TaskInfo taskInfo) { 618 try { 619 Framework.getService(DocumentRoutingService.class).delegateTask(documentManager, taskInfo.getTaskId(), 620 taskInfo.getActors(), taskInfo.getComment()); 621 Events.instance().raiseEvent(TaskEventNames.WORKFLOW_TASK_DELEGATED); 622 } catch (DocumentRouteException e) { 623 log.error(e); 624 facesMessages.add(StatusMessage.Severity.ERROR, messages.get("workflow.feedback.error.taskEnded")); 625 } 626 return null; 627 } 628 629 /** 630 * @since 5.8 631 */ 632 public String navigateToTask(DocumentModel taskDoc) { 633 setCurrentTask(taskDoc.getAdapter(Task.class)); 634 return null; 635 } 636 637 /** 638 * @since 5.8 639 */ 640 public String navigateToTasksView() { 641 setCurrentTask(null); 642 return null; 643 } 644 645 /** 646 * @since 5.8 647 */ 648 public Task getCurrentTask() { 649 return currentTask; 650 } 651 652 /** 653 * @since 5.8 654 */ 655 public void setCurrentTask(Task currentTask) { 656 this.currentTask = currentTask; 657 } 658 659 /** 660 * Added to avoid an error when opening a task created @before 5.8 see NXP-14047 661 * 662 * @since 5.9.3, 5.8.0-HF10 663 * @return 664 */ 665 @SuppressWarnings("deprecation") 666 public List<String> getCurrentTaskTargetDocumentsIds() { 667 Set<String> uniqueTargetDocIds = new HashSet<String>(); 668 List<String> docIds = new ArrayList<String>(); 669 if (currentTask == null) { 670 return docIds; 671 } 672 uniqueTargetDocIds.addAll(currentTask.getTargetDocumentsIds()); 673 docIds.addAll(uniqueTargetDocIds); 674 return docIds.isEmpty() ? null : docIds; 675 } 676 677 /** 678 * @since 5.8 - Define if action reassign task can be displayed. 679 */ 680 public boolean canBeReassign() { 681 if (currentTask == null) { 682 return false; 683 } 684 DocumentModel workflowInstance = documentManager.getDocument(new IdRef(currentTask.getProcessId())); 685 GraphRoute workflow = workflowInstance.getAdapter(GraphRoute.class); 686 if (workflow == null) { 687 return false; 688 } 689 GraphNode node = workflow.getNode(currentTask.getType()); 690 return node.allowTaskReassignment() && !currentTask.getDelegatedActors().contains(documentManager.getPrincipal().getName()); 691 } 692}