001/* 002 * Copyright 2017 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.bpm; 017 018import java.io.ByteArrayInputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Date; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Map.Entry; 030import java.util.Set; 031import java.util.function.Function; 032import java.util.stream.Collectors; 033 034import org.apache.avalon.framework.activity.Initializable; 035import org.apache.avalon.framework.component.Component; 036import org.apache.avalon.framework.configuration.Configuration; 037import org.apache.avalon.framework.configuration.ConfigurationException; 038import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 039import org.apache.avalon.framework.context.Context; 040import org.apache.avalon.framework.context.ContextException; 041import org.apache.avalon.framework.context.Contextualizable; 042import org.apache.avalon.framework.service.ServiceException; 043import org.apache.avalon.framework.service.ServiceManager; 044import org.apache.avalon.framework.service.Serviceable; 045import org.apache.cocoon.components.ContextHelper; 046import org.apache.cocoon.environment.Request; 047import org.apache.cocoon.servlet.multipart.Part; 048import org.apache.cocoon.servlet.multipart.PartOnDisk; 049import org.apache.commons.lang.StringUtils; 050import org.apache.commons.lang3.ArrayUtils; 051import org.xml.sax.SAXException; 052 053import org.ametys.core.group.GroupIdentity; 054import org.ametys.core.group.GroupManager; 055import org.ametys.core.right.RightManager; 056import org.ametys.core.right.RightManager.RightResult; 057import org.ametys.core.ui.Callable; 058import org.ametys.core.user.CurrentUserProvider; 059import org.ametys.core.user.UserIdentity; 060import org.ametys.core.util.DateUtils; 061import org.ametys.core.util.I18nUtils; 062import org.ametys.core.util.JSONUtils; 063import org.ametys.core.util.URIUtils; 064import org.ametys.plugins.bpm.jcr.JCRWorkflow; 065import org.ametys.plugins.bpm.jcr.JCRWorkflowFactory; 066import org.ametys.plugins.bpm.jcr.JCRWorkflowProcess; 067import org.ametys.plugins.bpm.jcr.JCRWorkflowProcessFactory; 068import org.ametys.plugins.bpm.workflowsdef.RegisterVariable; 069import org.ametys.plugins.core.user.UserHelper; 070import org.ametys.plugins.explorer.resources.ModifiableResource; 071import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 072import org.ametys.plugins.explorer.resources.ResourceCollection; 073import org.ametys.plugins.explorer.resources.actions.AddOrUpdateResourceHelper; 074import org.ametys.plugins.explorer.resources.actions.AddOrUpdateResourceHelper.ResourceOperationMode; 075import org.ametys.plugins.repository.AmetysObject; 076import org.ametys.plugins.repository.AmetysObjectIterable; 077import org.ametys.plugins.repository.AmetysObjectIterator; 078import org.ametys.plugins.repository.AmetysObjectResolver; 079import org.ametys.plugins.repository.AmetysRepositoryException; 080import org.ametys.plugins.repository.ModifiableAmetysObject; 081import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 082import org.ametys.plugins.repository.UnknownAmetysObjectException; 083import org.ametys.plugins.repository.jcr.NameHelper; 084import org.ametys.plugins.repository.query.expression.Expression.Operator; 085import org.ametys.plugins.workflow.support.WorkflowHelper; 086import org.ametys.plugins.workflow.support.WorkflowProvider; 087import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 088import org.ametys.runtime.i18n.I18nizableText; 089import org.ametys.runtime.plugin.component.AbstractLogEnabled; 090import org.ametys.runtime.plugin.component.PluginAware; 091import org.ametys.web.URIPrefixHandler; 092import org.ametys.web.repository.page.Page; 093import org.ametys.web.repository.page.PageQueryHelper; 094import org.ametys.web.repository.site.Site; 095import org.ametys.web.repository.site.SiteManager; 096import org.ametys.web.skin.Skin; 097import org.ametys.web.skin.SkinsManager; 098import org.ametys.web.tags.TagExpression; 099 100import com.opensymphony.workflow.Workflow; 101import com.opensymphony.workflow.WorkflowException; 102import com.opensymphony.workflow.loader.StepDescriptor; 103import com.opensymphony.workflow.loader.WorkflowDescriptor; 104import com.opensymphony.workflow.spi.Step; 105import com.opensymphony.workflow.spi.WorkflowEntry; 106 107/** 108 * Manager for retrieving, creation, edition and suppression of workflows, and retrieving information on workflows definitions 109 */ 110public class BPMWorkflowManager extends AbstractLogEnabled implements Component, Serviceable, Initializable, Contextualizable, PluginAware 111{ 112 /** Avalon Role */ 113 public static final String ROLE = BPMWorkflowManager.class.getName(); 114 115 /** Right to create a new workflow */ 116 public static final String RIGHT_WORKFLOW_CREATE = "BPM_Rights_Workflow_Create"; 117 /** Right to edit any workflow */ 118 public static final String RIGHT_WORKFLOW_EDIT = "BPM_Rights_Workflow_Edit"; 119 /** Right to delete any workflow */ 120 public static final String RIGHT_WORKFLOW_DELETE = "BPM_Rights_Workflow_Delete"; 121 122 /** Right to manage processes */ 123 public static final String RIGHTS_PROCESS_MANAGE_PROCESSES = "BPM_Rights_Workflow_Manage_Processes"; 124 125 /** Tag for the create process page */ 126 public static final String PAGE_TAG_CREATEPROCESS = "CREATE_PROCESS"; 127 /** Tag for the process dashboard page */ 128 public static final String PAGE_TAG_PROCESS_DASHBOARD = "PROCESS_DASHBOARD"; 129 130 /** The fixed id for the edit workflow action on processus */ 131 public static final int WORKFLOW_ACTION_EDIT = 2; 132 133 134 /** The plugin root node name */ 135 public static final String BPM_ROOT_NODE = "bpm"; 136 /** The BPM Workflows node name*/ 137 public static final String BPMWORKFLOW_ROOT_NODE = "ametys:workflows"; 138 /** The processes node name under each workflow */ 139 public static final String BPMPROCESSES_ROOT_NODE = "ametys:processes"; 140 141 private static final String _BPM_PROCESS_TEMPLATE = "bpm-process"; 142 143 private AmetysObjectResolver _resolver; 144 private WorkflowProvider _workflowProvider; 145 private CurrentUserProvider _currentUserProvider; 146 private RightManager _rightManager; 147 private JSONUtils _jsonUtils; 148 private UserHelper _userHelper; 149 private AddOrUpdateResourceHelper _addOrUpdateResourceHelper; 150 private WorkflowHelper _workflowHelper; 151 152 private Map<String, Map<String, Object>> _workflowDefinitionsCache; 153 154 private Context _context; 155 156 private SkinsManager _skinsManager; 157 158 private AmetysObjectResolver _ametysObjectResolver; 159 160 private SiteManager _siteManager; 161 162 private GroupManager _groupManager; 163 164 private URIPrefixHandler _uriPrefixHandler; 165 166 private String _pluginName; 167 168 private I18nUtils _i18nUtils; 169 170 @Override 171 public void contextualize(Context context) throws ContextException 172 { 173 _context = context; 174 } 175 176 @Override 177 public void service(ServiceManager manager) throws ServiceException 178 { 179 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 180 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 181 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 182 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 183 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 184 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 185 _addOrUpdateResourceHelper = (AddOrUpdateResourceHelper) manager.lookup(AddOrUpdateResourceHelper.ROLE); 186 _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE); 187 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 188 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 189 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 190 _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE); 191 _uriPrefixHandler = (URIPrefixHandler) manager.lookup(URIPrefixHandler.ROLE); 192 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 193 } 194 195 @Override 196 public void initialize() throws Exception 197 { 198 _workflowDefinitionsCache = new HashMap<>(); 199 } 200 201 public void setPluginInfo(String pluginName, String featureName, String id) 202 { 203 _pluginName = pluginName; 204 } 205 206 /** 207 * Get the list of workflow definitions 208 * @return The list of workflow definitions, mapped by name and label 209 */ 210 @Callable 211 public Map<String, Object> getWorkflowDefinitions() 212 { 213 Map<String, Object> result = new HashMap<>(); 214 String[] workflowNames = _workflowHelper.getWorkflowNames(); 215 216 Map<String, Object> workflowBuffer = new HashMap<> (); 217 List<Map<String, Object>> workflowsList = new ArrayList<> (); 218 219 for (String workflowName : workflowNames) 220 { 221 if (workflowName.startsWith("bpm-")) 222 { 223 Map<String, Object> workflowDefinitionData = _getWorkflowDefinitionData(workflowName); 224 if (workflowDefinitionData != null) 225 { 226 workflowBuffer = new HashMap<>(); 227 workflowBuffer.put("value", workflowName); 228 workflowBuffer.put("label", _workflowHelper.getWorkflowLabel(workflowName)); 229 workflowsList.add(workflowBuffer); 230 } 231 } 232 } 233 234 result.put("workflowDefinition", workflowsList); 235 result.put("success", true); 236 return result; 237 } 238 239 /** 240 * Retrieve the value of a variable from a workflow 241 * @param workflowId The workflow id 242 * @param variableName The variable name 243 * @return The value 244 */ 245 public Object getWorkflowVariable(String workflowId, String variableName) 246 { 247 JCRWorkflow workflow = _resolver.resolveById(workflowId); 248 return workflow.getVariable(variableName); 249 } 250 251 /** 252 * Retrieve the list of variables from a workflow definition 253 * @param workflowDefId The workflow definition id 254 * @return The list of variables, with their attributes such as the label, type, multiple, if defined 255 */ 256 @Callable 257 public Map<String, Object> getWorkflowDefinitionVariables(String workflowDefId) 258 { 259 Map<String, Object> result = new HashMap<>(); 260 Map<String, Object> workflowDefinitionData = _getWorkflowDefinitionData(workflowDefId); 261 boolean success = false; 262 if (workflowDefinitionData != null && workflowDefinitionData.containsKey("variables")) 263 { 264 @SuppressWarnings("unchecked") 265 Map<String, Map<String, String>> variables = (Map<String, Map<String, String>>) workflowDefinitionData.get("variables"); 266 if (variables != null) 267 { 268 success = true; 269 result.put("variables", variables); 270 } 271 } 272 result.put("success", success); 273 return result; 274 } 275 276 /** 277 * Retrieve the list of workflows 278 * @return The list of workflows 279 */ 280 @Callable 281 public Map<String, Object> getWorkflows() 282 { 283 Map<String, Object> result = new HashMap<>(); 284 List<Map<String, Object>> workflows = new ArrayList<>(); 285 286 try 287 { 288 ModifiableTraversableAmetysObject workflowsRootNode = _getWorkflowsRootNode(false); 289 AmetysObjectIterable<AmetysObject> children = workflowsRootNode.getChildren(); 290 for (AmetysObject child : children) 291 { 292 if (child instanceof JCRWorkflow) 293 { 294 JCRWorkflow workflow = (JCRWorkflow) child; 295 String workflowDefinition = workflow.getWorkflowDefinition(); 296 Map<String, Object> workflowJson = _workflowToJson(workflow, _getWorkflowDefinitionData(workflowDefinition)); 297 if (workflowJson != null) 298 { 299 workflows.add(workflowJson); 300 } 301 } 302 } 303 } 304 catch (UnknownAmetysObjectException e) 305 { 306 // no workflows root node yet, ignore 307 } 308 309 result.put("workflows", workflows); 310 return result; 311 } 312 313 /** 314 * Retrieve the list of workflows availables to the current user, in JSON format 315 * @return The list of workflows 316 */ 317 public List<Map<String, Object>> getWorkflowsAvailables() 318 { 319 List<Map<String, Object>> workflows = new ArrayList<>(); 320 321 try 322 { 323 ModifiableTraversableAmetysObject workflowsRootNode = _getWorkflowsRootNode(false); 324 AmetysObjectIterable<AmetysObject> children = workflowsRootNode.getChildren(); 325 for (AmetysObject child : children) 326 { 327 if (child instanceof JCRWorkflow) 328 { 329 JCRWorkflow workflow = (JCRWorkflow) child; 330 if (isUserAllowedOnWorkflow(workflow)) 331 { 332 String workflowDefinition = workflow.getWorkflowDefinition(); 333 Map<String, Object> workflowJson = _workflowToJson(workflow, _getWorkflowDefinitionData(workflowDefinition)); 334 if (workflowJson != null) 335 { 336 workflows.add(workflowJson); 337 } 338 } 339 } 340 } 341 } 342 catch (UnknownAmetysObjectException e) 343 { 344 // no workflows root node yet, ignore 345 } 346 347 return workflows; 348 } 349 350 /** 351 * Retrieve the data of a workflow, in JSON format. Contains both the values of the workflow, and the workflow variables definitions 352 * @param workflowId The workflow id 353 * @return The data of a workflow 354 */ 355 @Callable 356 public Map<String, Object> getWorkflowData(String workflowId) 357 { 358 Map<String, Object> result = new HashMap<>(); 359 360 JCRWorkflow workflow = _resolver.resolveById(workflowId); 361 String workflowDefinition = workflow.getWorkflowDefinition(); 362 Map<String, Object> workflowDefinitionData = _getWorkflowDefinitionData(workflowDefinition); 363 result.put("workflow", _workflowToJson(workflow, workflowDefinitionData)); 364 if (workflowDefinitionData != null) 365 { 366 result.put("variables", workflowDefinitionData.get("variables")); 367 } 368 result.put("success", true); 369 return result; 370 } 371 372 /** 373 * Add a new workflow 374 * @param values The values for the new workflow. Must contains a "name", "workflowDef" and any variables mandatory for the workflowDef 375 * @return The result, with the new workflow id if successful 376 * @throws IllegalAccessException If a user with insufficient rights try to execute this method 377 */ 378 @Callable (right = RIGHT_WORKFLOW_CREATE) 379 public Map<String, Object> addWorkflow(Map<String, Object> values) throws IllegalAccessException 380 { 381 ModifiableTraversableAmetysObject workflowsRoot = _getWorkflowsRootNode(true); 382 383 String workflowDef = (String) values.getOrDefault("workflowDef", null); 384 String name = (String) values.getOrDefault("title", null); 385 386 if (StringUtils.isEmpty(workflowDef) || StringUtils.isEmpty(name)) 387 { 388 throw new IllegalArgumentException("Missing mandatory arguments for creating a new workflow"); 389 } 390 391 String originalName = NameHelper.filterName(name); 392 // Find unique name 393 String uniqueName = originalName; 394 int index = 2; 395 while (workflowsRoot.hasChild(uniqueName)) 396 { 397 uniqueName = originalName + "-" + (index++); 398 } 399 400 JCRWorkflow workflow = (JCRWorkflow) workflowsRoot.createChild(uniqueName, JCRWorkflowFactory.WORKFLOW_NODE_TYPE); 401 workflow.setWorkflowDefinition(workflowDef); 402 _setWorkflowValues(workflow, values); 403 404 workflow.saveChanges(); 405 406 Map<String, Object> result = new HashMap<>(); 407 result.put("id", workflow.getId()); 408 409 return result; 410 } 411 412 /** 413 * Edit a workflow values 414 * @param workflowId The workflow 415 * @param values The values 416 * @return The result, with the workflow id if successful 417 * @throws IllegalAccessException If a user with insufficient rights try to execute this method 418 */ 419 @Callable 420 public Map<String, Object> editWorkflow(String workflowId, Map<String, Object> values) throws IllegalAccessException 421 { 422 JCRWorkflow workflow = _resolver.resolveById(workflowId); 423 424 UserIdentity currentUser = _currentUserProvider.getUser(); 425 if ((currentUser == null || !currentUser.equals(workflow.getOwner())) && _rightManager.currentUserHasRight(RIGHT_WORKFLOW_EDIT, null) != RightResult.RIGHT_ALLOW) 426 { 427 throw new IllegalAccessException("User '" + _currentUserProvider.getUser().toString() + "' tried to create a process with insufficient rights"); 428 } 429 430 _setWorkflowValues(workflow, values); 431 if (workflow.needsSave()) 432 { 433 workflow.saveChanges(); 434 } 435 436 Map<String, Object> result = new HashMap<>(); 437 result.put("id", workflow.getId()); 438 439 return result; 440 } 441 442 /** 443 * Delete a list of workflow 444 * @param workflowIds The workflows 445 * @return The result 446 */ 447 @Callable 448 public Map<String, Object> deleteWorkflow(List<String> workflowIds) 449 { 450 Map<String, Object> result = new HashMap<>(); 451 List<JCRWorkflow> workflows = new ArrayList<>(); 452 for (String workflowId : workflowIds) 453 { 454 JCRWorkflow workflow = _resolver.resolveById(workflowId); 455 UserIdentity owner = workflow.getOwner(); 456 if (owner != null && owner.equals(_currentUserProvider.getUser()) 457 || _rightManager.currentUserHasRight(RIGHT_WORKFLOW_DELETE, null) == RightResult.RIGHT_ALLOW) 458 { 459 List<Object> workflowProcessus = getWorkflowProcessus(workflow); 460 if (workflowProcessus.size() == 0) 461 { 462 workflows.add(workflow); 463 } 464 } 465 } 466 467 List<String> workflowDeletedIds = new ArrayList<>(); 468 for (JCRWorkflow workflow : workflows) 469 { 470 ModifiableAmetysObject parent = (ModifiableAmetysObject) workflow.getParent(); 471 workflowDeletedIds.add(workflow.getId()); 472 workflow.remove(); 473 parent.saveChanges(); 474 } 475 result.put("ids", workflowDeletedIds); 476 477 result.put("success", true); 478 return result; 479 } 480 481 /** 482 * Retrieve the list of process created from a workflow 483 * @param workflowId The workflow 484 * @return The list of process 485 */ 486 @Callable (right = RIGHTS_PROCESS_MANAGE_PROCESSES) 487 public Map<String, Object> getWorkflowProcesses(String workflowId) 488 { 489 Map<String, Object> result = new HashMap<>(); 490 JCRWorkflow workflow = _resolver.resolveById(workflowId); 491 result.put("processes", getWorkflowProcessus(workflow).stream().map(process -> _processToJson((JCRWorkflowProcess) process)).collect(Collectors.toList())); 492 return result; 493 } 494 495 /** 496 * Retrieve the list of process created from a workflow 497 * @param workflow The workflow 498 * @return The list of process 499 */ 500 public List<Object> getWorkflowProcessus(JCRWorkflow workflow) 501 { 502 try 503 { 504 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, false); 505 return processesRoot.getChildren().stream().collect(Collectors.toList()); 506 } 507 catch (UnknownAmetysObjectException e) 508 { 509 // no processes for the workflow 510 } 511 return new ArrayList<>(); 512 } 513 514 515 /** 516 * Create a new process 517 * @param workflowId The workflow used by the process 518 * @param title The process title 519 * @param site The site on which the process was created 520 * @param description The process description 521 * @param uploadedAttachments The process attachments 522 * @return The new process 523 * @throws WorkflowException If an error occurred creating the workflow 524 * @throws IllegalAccessException If the user is not allowed 525 */ 526 public JCRWorkflowProcess createProcess(String workflowId, String title, String site, String description, List<PartOnDisk> uploadedAttachments) throws WorkflowException, IllegalAccessException 527 { 528 JCRWorkflow workflow = _resolver.resolveById(workflowId); 529 530 if (!isUserAllowedOnWorkflow(workflow)) 531 { 532 throw new IllegalAccessException("User '" + _currentUserProvider.getUser().toString() + "' tried to create a process with insufficient rights"); 533 } 534 535 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, true); 536 537 String originalName = NameHelper.filterName(title); 538 // Find unique name 539 String uniqueName = originalName; 540 int index = 2; 541 while (processesRoot.hasChild(uniqueName)) 542 { 543 uniqueName = originalName + "-" + (index++); 544 } 545 546 JCRWorkflowProcess process = (JCRWorkflowProcess) processesRoot.createChild(uniqueName, JCRWorkflowProcessFactory.WORKFLOW_PROCESS_NODE_TYPE); 547 process.setCreator(_currentUserProvider.getUser()); 548 process.setWorkflow(workflowId); 549 process.setCreationDate(new Date()); 550 _setProcessValues(process, title, site, description, uploadedAttachments); 551 552 // create workflow with entry id 553 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 554 555 String workflowName = workflow.getWorkflowDefinition(); 556 int initialActionId = _workflowHelper.getInitialAction(workflowName); 557 558 // Add the workflow used by the process in the request, to be used by any RegisterVariable of the workflow definition 559 Request request = ContextHelper.getRequest(_context); 560 request.setAttribute("workflowId", process.getWorkflow()); 561 562 Map<String, Object> inputs = new HashMap<>(); 563 inputs.put("workflowId", workflowId); 564 inputs.put("process", process); 565 long workflowInstanceId = workflowStore.initialize(workflowName, initialActionId, inputs); 566 process.setWorkflowId(workflowInstanceId); 567 Step currentStep = (Step) workflowStore.getCurrentSteps(process.getWorkflowId()).iterator().next(); 568 process.setCurrentStepId(currentStep.getStepId()); 569 process.saveChanges(); 570 571 return process; 572 } 573 574 575 /** 576 * Edit a process 577 * @param processId The process id 578 * @param title The title 579 * @param description The description 580 * @param uploadedAttachments The list of new attachments uploaded 581 * @param attachmentsUntouched The list of untouched attachments names 582 * @throws WorkflowException If an error occurred while executing the workflow action 583 */ 584 public void editProcess(String processId, String title, String description, List<PartOnDisk> uploadedAttachments, List<String> attachmentsUntouched) throws WorkflowException 585 { 586 JCRWorkflowProcess process = _resolver.resolveById(processId); 587 588 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 589 Map<String, Object> inputs = new HashMap<>(); 590 inputs.put("process", process); 591 592 inputs.put("title", title); 593 inputs.put("description", description); 594 inputs.put("uploadedAttachments", uploadedAttachments); 595 inputs.put("attachmentsUntouched", attachmentsUntouched); 596 597 // Add the workflow used by the process in the request, to be used by any RegisterVariable of the workflow definition 598 Request request = ContextHelper.getRequest(_context); 599 request.setAttribute("workflowId", process.getWorkflow()); 600 601 workflowStore.doAction(process.getWorkflowId(), WORKFLOW_ACTION_EDIT, inputs); 602 } 603 604 /** 605 * Retrieve a process 606 * @param workflowName The workflow name 607 * @param processName The process name 608 * @return The process 609 */ 610 public JCRWorkflowProcess getProcess(String workflowName, String processName) 611 { 612 try 613 { 614 ModifiableTraversableAmetysObject workflowsRoot = _getWorkflowsRootNode(false); 615 JCRWorkflow workflow = workflowsRoot.getChild(workflowName); 616 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, false); 617 if (processesRoot.hasChild(processName)) 618 { 619 return processesRoot.getChild(processName); 620 } 621 } 622 catch (UnknownAmetysObjectException e) 623 { 624 // No workflow or process created with this workflowId and processId 625 return null; 626 } 627 628 return null; 629 } 630 631 /** 632 * Determines if the process is complete 633 * @param process The process 634 * @return True if the process is complete 635 */ 636 public boolean isComplete(JCRWorkflowProcess process) 637 { 638 AmetysObjectWorkflow aoWorkflow = _workflowProvider.getAmetysObjectWorkflow(process, true); 639 if (aoWorkflow != null) 640 { 641 int entryState = aoWorkflow.getEntryState(process.getWorkflowId()); 642 return entryState == WorkflowEntry.COMPLETED; 643 } 644 645 // FIXME on final step the workflow is compelte but deleted ... 646 return false; 647 } 648 649 /** 650 * Determines if the process can be deleted by the current user 651 * @param process The process 652 * @return True if the process can be deleted 653 */ 654 public boolean canDelete(JCRWorkflowProcess process) 655 { 656 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) == RightResult.RIGHT_ALLOW) 657 { 658 // A super admin can delete a process at all time 659 return true; 660 } 661 662 // Otherwise a process can be deleted only by the creator if is complete 663 return isComplete(process) && process.getCreator().equals(_currentUserProvider.getUser()); 664 } 665 666 /** 667 * Delete a process 668 * @param process The process to delete 669 * @throws IllegalAccessException If a user tries to delete a process without being allowed 670 */ 671 public void deleteProcess(JCRWorkflowProcess process) throws IllegalAccessException 672 { 673 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) != RightResult.RIGHT_ALLOW) 674 { 675 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 676 int entryState = workflowStore.getEntryState(process.getWorkflowId()); 677 if (entryState != WorkflowEntry.COMPLETED || !process.getCreator().equals(_currentUserProvider.getUser())) 678 { 679 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to delete a process without sufficient rights."); 680 } 681 } 682 683 ModifiableAmetysObject parent = process.getParent(); 684 process.remove(); 685 parent.saveChanges(); 686 } 687 688 /** 689 * Delete a list of processes 690 * @param processes The processes to delete 691 * @return The result 692 * @throws IllegalAccessException If a user tries to delete a process without being allowed 693 */ 694 @Callable (right = RIGHTS_PROCESS_MANAGE_PROCESSES) 695 public Map<String, Object> deleteProcesses(List<String> processes) throws IllegalAccessException 696 { 697 Map<String, Object> result = new HashMap<>(); 698 List<String> processesDeletedIds = new ArrayList<>(); 699 for (String processId : processes) 700 { 701 JCRWorkflowProcess process = _resolver.resolveById(processId); 702 ModifiableAmetysObject parent = (ModifiableAmetysObject) process.getParent(); 703 processesDeletedIds.add(process.getId()); 704 process.remove(); 705 parent.saveChanges(); 706 } 707 708 result.put("ids", processesDeletedIds); 709 result.put("success", true); 710 return result; 711 } 712 713 /** 714 * Get the url of a process page 715 * @param process The process 716 * @param siteName The current site name. Can not be null. 717 * @param lang The current language. Can not be null. 718 * @param absolute true to get absolute url. 719 * @return the url of process page 720 */ 721 public String getProcessPageUrl(JCRWorkflowProcess process, String siteName, String lang, boolean absolute) 722 { 723 StringBuilder sb = new StringBuilder(); 724 725 if (absolute) 726 { 727 sb.append(_uriPrefixHandler.getAbsoluteUriPrefix(siteName)); 728 } 729 else 730 { 731 sb.append(_uriPrefixHandler.getUriPrefix(siteName)); 732 } 733 734 sb.append("/") 735 .append(lang) 736 .append("/_plugins/") 737 .append(_pluginName) 738 .append("/") 739 .append(getTemplateForProcessPage(siteName, lang)) 740 .append("/process/"); 741 742 JCRWorkflow workflow = _resolver.resolveById(process.getWorkflow()); 743 744 sb.append(URIUtils.encodePathSegment(workflow.getName())) 745 .append("/") 746 .append(URIUtils.encodePathSegment(process.getName())) 747 .append(".html"); 748 749 return sb.toString(); 750 } 751 752 /** 753 * Get the page with PROCESS_DASHBOARD tag 754 * @param siteName The current site name 755 * @param lang The language 756 * @return The page or null if not found. 757 */ 758 public Page getDashboardPage (String siteName, String lang) 759 { 760 TagExpression tagExpression = new TagExpression(Operator.EQ, PAGE_TAG_PROCESS_DASHBOARD); 761 762 String pathQuery = PageQueryHelper.getPageXPathQuery(siteName, lang, null, tagExpression, null); 763 AmetysObjectIterable<Page> page = _ametysObjectResolver.query(pathQuery); 764 765 if (page.getSize() == 0) 766 { 767 return null; 768 } 769 770 return page.iterator().next(); 771 } 772 773 /** 774 * Get the page with CREATE_PROCESS tag 775 * @param siteName The current site name 776 * @param lang The language 777 * @return The page or null if not found. 778 */ 779 public Page getCreateProcessPage (String siteName, String lang) 780 { 781 TagExpression tagExpression = new TagExpression(Operator.EQ, PAGE_TAG_CREATEPROCESS); 782 783 String pathQuery = PageQueryHelper.getPageXPathQuery(siteName, lang, null, tagExpression, null); 784 AmetysObjectIterable<Page> page = _ametysObjectResolver.query(pathQuery); 785 786 if (page.getSize() == 0) 787 { 788 return null; 789 } 790 791 return page.iterator().next(); 792 } 793 794 /** 795 * Get the template to use for process rendering 796 * @param siteName The site containing the page 797 * @param lang The sitemap of the page 798 * @return The template to use for process rendering 799 */ 800 public String getTemplateForProcessPage(String siteName, String lang) 801 { 802 Site site = _siteManager.getSite(siteName); 803 804 String skinName = site.getSkinId(); 805 Skin skin = _skinsManager.getSkin(skinName); 806 Set<String> templates = skin.getTemplates(); 807 808 // First look for the template bpm-process 809 if (templates.contains(_BPM_PROCESS_TEMPLATE)) 810 { 811 return _BPM_PROCESS_TEMPLATE; 812 } 813 814 // Otherwise, returns the template used by create process page 815 Page createProcessPage = getCreateProcessPage(siteName, lang); 816 if (createProcessPage != null) 817 { 818 return createProcessPage.getTemplate(); 819 } 820 821 // Otherwise, fallback to the template page if exists 822 if (templates.contains("page")) 823 { 824 return "page"; 825 } 826 827 // Finally, fallback to the first template 828 return templates.iterator().next(); 829 } 830 831 /** 832 * Test if the current user is present in any variable of the workflow 833 * @param workflow The workflow 834 * @return True if the user is in the workflow variables 835 */ 836 @SuppressWarnings("unchecked") 837 public boolean isUserInWorkflowVariables(JCRWorkflow workflow) 838 { 839 Map<String, Object> workflowDefinitionData = _getWorkflowDefinitionData(workflow.getWorkflowDefinition()); 840 841 if (workflowDefinitionData != null && workflowDefinitionData.containsKey("variables")) 842 { 843 Map<String, Map<String, String>> variables = (Map<String, Map<String, String>>) workflowDefinitionData.get("variables"); 844 UserIdentity currentUser = _currentUserProvider.getUser(); 845 846 boolean userInVariables = variables.entrySet().stream() 847 .filter(e -> "user".equals(e.getValue().getOrDefault("type", null))) 848 .map(e -> workflow.getVariable(e.getKey())) 849 .filter(var -> var != null && var instanceof String) 850 .map(var -> _jsonUtils.convertJsonToList((String) var)) 851 .map(usersList -> 852 { 853 return usersList.stream() 854 .map(userData -> 855 { 856 Map<String, String> user = (Map<String, String>) userData; 857 String login = user.get("login"); 858 String populationId = user.get("populationId"); 859 return new UserIdentity(login, populationId); 860 }) 861 .filter(user -> user != null && user.equals(currentUser)) 862 .findAny(); 863 }) 864 .filter(optionalUser -> optionalUser.isPresent()) 865 .findAny() 866 .isPresent(); 867 868 return userInVariables; 869 } 870 return false; 871 } 872 873 /** 874 * Get the list of processes accessibles to the current user 875 * @return The list of processes 876 */ 877 public Set<JCRWorkflowProcess> getUserProcesses() 878 { 879 Set<JCRWorkflowProcess> processes = new HashSet<>(); 880 UserIdentity user = _currentUserProvider.getUser(); 881 if (user == null) 882 { 883 return processes; 884 } 885 886 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) == RightResult.RIGHT_ALLOW) 887 { 888 // return all processes as we have the right to manage all of them 889 AmetysObjectIterable<JCRWorkflowProcess> processesOwned = _ametysObjectResolver.query("//element(*, ametys:workflowProcess)"); 890 Iterator<JCRWorkflowProcess> it = processesOwned.iterator(); 891 while (it.hasNext()) 892 { 893 processes.add(it.next()); 894 } 895 return processes; 896 } 897 898 // retrieve processes of which we are the creator 899 String xpath = String.format("//element(*, ametys:workflowProcess)[@ametys:creator/ametys:login='%s' and @ametys:creator/ametys:population='%s']", user.getLogin(), user.getPopulationId()); 900 AmetysObjectIterable<JCRWorkflowProcess> processesOwned = _ametysObjectResolver.query(xpath); 901 Iterator<JCRWorkflowProcess> it = processesOwned.iterator(); 902 while (it.hasNext()) 903 { 904 processes.add(it.next()); 905 } 906 907 // add processes for which we are in the workflow variables 908 ModifiableTraversableAmetysObject workflowsRootNode = _getWorkflowsRootNode(false); 909 AmetysObjectIterable<AmetysObject> children = workflowsRootNode.getChildren(); 910 for (AmetysObject child : children) 911 { 912 if (child instanceof JCRWorkflow) 913 { 914 JCRWorkflow workflow = (JCRWorkflow) child; 915 try 916 { 917 ModifiableTraversableAmetysObject processesRootNode = _getProcessesRootNode(workflow, false); 918 AmetysObjectIterable<JCRWorkflowProcess> workflowChildren = processesRootNode.getChildren(); 919 if (workflowChildren.getSize() > 0 && isUserInWorkflowVariables(workflow)) 920 { 921 AmetysObjectIterator<JCRWorkflowProcess> it2 = workflowChildren.iterator(); 922 while (it2.hasNext()) 923 { 924 processes.add(it2.next()); 925 } 926 } 927 } 928 catch (UnknownAmetysObjectException e) 929 { 930 // No process for workflow, do nothing 931 } 932 } 933 } 934 935 return processes; 936 } 937 938 /** 939 * Check if the current user is allowed to create a process from the workflow 940 * @param workflow The workflow 941 * @return True if the user is allowed 942 */ 943 public boolean isUserAllowedOnWorkflow(JCRWorkflow workflow) 944 { 945 UserIdentity user = _currentUserProvider.getUser(); 946 if (user != null) 947 { 948 if (user.equals(workflow.getOwner()) || ArrayUtils.contains(workflow.getAllowedUsers(), user)) 949 { 950 return true; 951 } 952 953 GroupIdentity[] allowedGroups = workflow.getAllowedGroups(); 954 for (GroupIdentity userGroup : _groupManager.getUserGroups(user)) 955 { 956 if (ArrayUtils.contains(allowedGroups, userGroup)) 957 { 958 return true; 959 } 960 } 961 962 } 963 return false; 964 } 965 966 @SuppressWarnings("unchecked") 967 private void _setWorkflowValues(JCRWorkflow workflow, Map<String, Object> values) 968 { 969 String title = (String) values.get("title"); 970 String description = (String) values.getOrDefault("description", null); 971 Map<String, Object> owner = (Map<String, Object>) values.getOrDefault("owner", null); 972 List<Map<String, Object>> allowedUsersValue = (List<Map<String, Object>>) values.getOrDefault("allowedUsers", null); 973 List<Map<String, Object>> allowedGroupsValue = (List<Map<String, Object>>) values.getOrDefault("allowedGroups", null); 974 Map<String, Object> variables = (Map<String, Object>) values.getOrDefault("variables", null); 975 976 workflow.setTitle(title); 977 workflow.setDescription(description); 978 workflow.setOwner(owner != null ? _convertJsonToUser(owner) : null); 979 980 if (allowedUsersValue != null) 981 { 982 Set<UserIdentity> allowedUsers = new HashSet<>(); 983 for (Object obj : allowedUsersValue) 984 { 985 UserIdentity user = _convertJsonToUser((Map<String, Object>) obj); 986 if (user != null) 987 { 988 allowedUsers.add(user); 989 } 990 } 991 992 workflow.setAllowedUsers(allowedUsers.toArray(new UserIdentity[allowedUsers.size()])); 993 } 994 995 if (allowedGroupsValue != null) 996 { 997 Set<GroupIdentity> allowedGroups = new HashSet<>(); 998 for (Object obj : allowedGroupsValue) 999 { 1000 Map<String, Object> grantedUser = (Map<String, Object>) obj; 1001 String groupId = (String) grantedUser.get("groupId"); 1002 String groupDirectory = (String) grantedUser.get("groupDirectory"); 1003 1004 if (groupId != null && groupDirectory != null) 1005 { 1006 allowedGroups.add(new GroupIdentity(groupId, groupDirectory)); 1007 } 1008 } 1009 1010 workflow.setAllowedGroups(allowedGroups.toArray(new GroupIdentity[allowedGroups.size()])); 1011 } 1012 1013 for (Entry<String, Object> entry : variables.entrySet()) 1014 { 1015 workflow.setVariable(entry.getKey(), entry.getValue()); 1016 } 1017 } 1018 1019 private void _setProcessValues(JCRWorkflowProcess process, String title, String site, String description, List<PartOnDisk> uploadedAttachments) 1020 { 1021 if (title != null) 1022 { 1023 process.setTitle(title); 1024 } 1025 1026 if (site != null) 1027 { 1028 process.setSite(site); 1029 } 1030 1031 process.setDescription(description); 1032 1033 ResourceCollection rootAttachments = process.getRootAttachments(true); 1034 if (rootAttachments instanceof ModifiableResourceCollection) 1035 { 1036 _addOrUpdateResourceHelper.checkAddResourceRight((ModifiableResourceCollection) rootAttachments); 1037 1038 for (Part attachment : uploadedAttachments) 1039 { 1040 _addOrUpdateResourceHelper.performResourceOperation(attachment, (ModifiableResourceCollection) rootAttachments, ResourceOperationMode.ADD); 1041 } 1042 } 1043 } 1044 1045 private UserIdentity _convertJsonToUser(Map<String, Object> user) 1046 { 1047 String login = (String) user.get("login"); 1048 String populationId = (String) user.get("populationId"); 1049 1050 if (login != null && populationId != null) 1051 { 1052 return new UserIdentity(login, populationId); 1053 } 1054 1055 return null; 1056 } 1057 1058 private Map<String, Object> _workflowToJson(JCRWorkflow workflow, Map<String, Object> workflowDefinitionData) 1059 { 1060 if (workflow == null) 1061 { 1062 return null; 1063 } 1064 1065 Map<String, Object> jsonData = new HashMap<>(); 1066 1067 jsonData.put("id", workflow.getId()); 1068 jsonData.put("title", workflow.getTitle()); 1069 Map<String, Object> workflowBuffer = new HashMap<>(); 1070 String workflowName = workflow.getWorkflowDefinition(); 1071 workflowBuffer.put("value", workflowName); 1072 workflowBuffer.put("label", _workflowHelper.getWorkflowLabel(workflowName)); 1073 if (workflowDefinitionData == null) 1074 { 1075 workflowBuffer.put("missing", true); 1076 } 1077 jsonData.put("workflowDefinition", workflowBuffer); 1078 jsonData.put("description", workflow.getDescription()); 1079 jsonData.put("owner", _userHelper.user2json(workflow.getOwner(), true)); 1080 jsonData.put("allowedUsers", Arrays.stream(workflow.getAllowedUsers()).map(ud -> _userHelper.user2json(ud, true)).collect(Collectors.toList())); 1081 jsonData.put("allowedGroups", _groups2Json(workflow.getAllowedGroups())); 1082 if (workflowDefinitionData != null && workflowDefinitionData.containsKey("variables")) 1083 { 1084 @SuppressWarnings("unchecked") 1085 Map<String, Map<String, String>> variables = (Map<String, Map<String, String>>) workflowDefinitionData.get("variables"); 1086 if (variables != null) 1087 { 1088 jsonData.put("variables", variables.keySet().stream().filter(key -> workflow.getVariable(key) != null).collect(Collectors.toMap(Function.identity(), key -> workflow.getVariable(key)))); 1089 } 1090 } 1091 1092 jsonData.put("process", getWorkflowProcessus(workflow).size()); 1093 1094 return jsonData; 1095 } 1096 1097 private List<Map<String, Object>> _groups2Json(GroupIdentity[] groups) 1098 { 1099 List<Map<String, Object>> json = new ArrayList<>(); 1100 for (GroupIdentity group : groups) 1101 { 1102 Map<String, Object> groupJson = new HashMap<>(); 1103 groupJson.put("groupId", group.getId()); 1104 groupJson.put("groupDirectory", group.getDirectoryId()); 1105 json.add(groupJson); 1106 } 1107 return json; 1108 } 1109 1110 /** 1111 * Gets the root of contents 1112 * @param create <code>true</code> to create automatically the root when missing. 1113 * @return the root of workflows 1114 * @throws UnknownAmetysObjectException If root node does not exist 1115 */ 1116 protected ModifiableTraversableAmetysObject _getWorkflowsRootNode(boolean create) 1117 { 1118 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins/"); 1119 1120 boolean needSave = false; 1121 if (!pluginsNode.hasChild(BPM_ROOT_NODE)) 1122 { 1123 if (create) 1124 { 1125 pluginsNode.createChild(BPM_ROOT_NODE, "ametys:unstructured"); 1126 needSave = true; 1127 } 1128 else 1129 { 1130 throw new UnknownAmetysObjectException("Node '/ametys:plugins/" + BPM_ROOT_NODE + "' is missing"); 1131 } 1132 } 1133 1134 ModifiableTraversableAmetysObject bpmNode = pluginsNode.getChild(BPM_ROOT_NODE); 1135 if (!bpmNode.hasChild(BPMWORKFLOW_ROOT_NODE)) 1136 { 1137 if (create) 1138 { 1139 bpmNode.createChild(BPMWORKFLOW_ROOT_NODE, "ametys:unstructured"); 1140 needSave = true; 1141 } 1142 else 1143 { 1144 throw new UnknownAmetysObjectException("Node '/ametys:plugins/" + BPM_ROOT_NODE + "/" + BPMWORKFLOW_ROOT_NODE + "' is missing"); 1145 } 1146 } 1147 1148 if (needSave) 1149 { 1150 pluginsNode.saveChanges(); 1151 } 1152 1153 return bpmNode.getChild(BPMWORKFLOW_ROOT_NODE); 1154 } 1155 1156 /** 1157 * Gets the root of processes of a workflow 1158 * @param workflow The workflow 1159 * @param create <code>true</code> to create automatically the root when missing. 1160 * @return the root of processes 1161 * @throws UnknownAmetysObjectException If root node does not exist 1162 */ 1163 protected ModifiableTraversableAmetysObject _getProcessesRootNode(JCRWorkflow workflow, boolean create) 1164 { 1165 boolean needSave = false; 1166 if (!workflow.hasChild(BPMPROCESSES_ROOT_NODE)) 1167 { 1168 if (create) 1169 { 1170 workflow.createChild(BPMPROCESSES_ROOT_NODE, "ametys:unstructured"); 1171 needSave = true; 1172 } 1173 else 1174 { 1175 throw new UnknownAmetysObjectException("Node '" + BPMPROCESSES_ROOT_NODE + "' is missing for workflow '" + workflow.getId() + "'"); 1176 } 1177 } 1178 1179 if (needSave) 1180 { 1181 workflow.saveChanges(); 1182 } 1183 1184 return workflow.getChild(BPMPROCESSES_ROOT_NODE); 1185 } 1186 1187 1188 /** 1189 * Get the list of variables and their properties for a workflow definition 1190 * @param workflowDefId The workflow definition id 1191 * @return The list of variables properties, mapped by variable id 1192 */ 1193 protected Map<String, Object> _getWorkflowDefinitionData(String workflowDefId) 1194 { 1195 if (_workflowDefinitionsCache.containsKey(workflowDefId)) 1196 { 1197 return _workflowDefinitionsCache.get(workflowDefId); 1198 } 1199 1200 Map<String, Object> workflowDefinitionData = new HashMap<>(); 1201 1202 Map<String, Map<String, Object>> variables = new HashMap<>(); 1203 if (!ArrayUtils.contains(_workflowHelper.getWorkflowNames(), workflowDefId)) 1204 { 1205 return null; 1206 } 1207 WorkflowDescriptor workflowDescriptor = _workflowHelper.getWorkflowDescriptor(workflowDefId); 1208 if (workflowDescriptor == null) 1209 { 1210 return null; 1211 } 1212 1213 String workflowXml = workflowDescriptor.asXML(); 1214 1215 try (InputStream is = new ByteArrayInputStream(workflowXml.getBytes())) 1216 { 1217 Configuration workflowConfiguration = new DefaultConfigurationBuilder().build(is); 1218 1219 Configuration[] registers = workflowConfiguration.getChild("registers", true).getChildren("register"); 1220 if (registers.length > 0) 1221 { 1222 variables.putAll(_getWorkflowDefinitionRegisters(registers)); 1223 } 1224 } 1225 catch (ConfigurationException | IOException | SAXException e) 1226 { 1227 getLogger().warn("Unable to retrieve the workflow definition xml file for '" + workflowDefId + "'", e); 1228 return null; 1229 } 1230 1231 workflowDefinitionData.put("variables", variables); 1232 _workflowDefinitionsCache.put(workflowDefId, workflowDefinitionData); 1233 return workflowDefinitionData; 1234 } 1235 1236 private Map< ? extends String, ? extends Map<String, Object>> _getWorkflowDefinitionRegisters(Configuration[] registers) throws ConfigurationException 1237 { 1238 Map<String, Map<String, Object>> variables = new HashMap<>(); 1239 1240 for (Configuration register : registers) 1241 { 1242 String variableName = null; 1243 if ("avalon".equals(register.getAttribute("type", null))) 1244 { 1245 Map<String, Object> variableValues = new HashMap<>(); 1246 boolean matchRole = false; 1247 for (Configuration arg : register.getChildren("arg")) 1248 { 1249 String argName = arg.getAttribute("name", null); 1250 String argValue = arg.getValue(); 1251 if (argName != null) 1252 { 1253 if ("role".equals(argName)) 1254 { 1255 matchRole = RegisterVariable.class.getName().equals(argValue); 1256 } 1257 else if ("variableId".equals(argName)) 1258 { 1259 variableName = argValue; 1260 } 1261 else 1262 { 1263 if ("label".equals(argName) || "description".equals(argName)) 1264 { 1265 variableValues.put(argName, new I18nizableText(null, argValue)); 1266 } 1267 else 1268 { 1269 variableValues.put(argName, argValue); 1270 } 1271 } 1272 } 1273 } 1274 1275 if (matchRole && StringUtils.isNotEmpty(variableName)) 1276 { 1277 variables.put(variableName, variableValues); 1278 } 1279 } 1280 } 1281 1282 return variables; 1283 } 1284 1285 private Map<String, Object> _processToJson(JCRWorkflowProcess process) 1286 { 1287 Map<String, Object> jsonData = new HashMap<>(); 1288 1289 jsonData.put("id", process.getId()); 1290 jsonData.put("title", process.getTitle()); 1291 jsonData.put("creationDate", DateUtils.dateToString(process.getCreationDate())); 1292 jsonData.put("description", process.getDescription()); 1293 jsonData.put("creator", _userHelper.user2json(process.getCreator(), true)); 1294 1295 List<Map<String, String>> attachmentsList = new ArrayList<>(); 1296 1297 ResourceCollection rootAttachments = process.getRootAttachments(false); 1298 if (rootAttachments != null) 1299 { 1300 AmetysObjectIterable<ModifiableResource> attachments = rootAttachments.getChildren(); 1301 JCRWorkflow workflow = _resolver.resolveById(process.getWorkflow()); 1302 1303 String attachmentPathPrefix = workflow.getName() + "/" + process.getName() + "/_attachments/"; 1304 1305 for (ModifiableResource attachment : attachments) 1306 { 1307 Map<String, String> attachmentData = new HashMap<>(); 1308 1309 String name = attachment.getName(); 1310 attachmentData.put("name", name); 1311 attachmentData.put("path", attachmentPathPrefix + name); 1312 1313 attachmentsList.add(attachmentData); 1314 } 1315 } 1316 1317 jsonData.put("attachments", attachmentsList); 1318 1319 AmetysObjectWorkflow aoWorkflow = _workflowProvider.getAmetysObjectWorkflow(process, true); 1320 try 1321 { 1322 String workflowName = aoWorkflow.getWorkflowName(process.getWorkflowId()); 1323 if (workflowName != null) 1324 { 1325 WorkflowDescriptor workflowDescriptor = aoWorkflow.getWorkflowDescriptor(workflowName); 1326 StepDescriptor step = workflowDescriptor.getStep((int) process.getCurrentStepId()); 1327 jsonData.put("currentStep", _i18nUtils.translate(new I18nizableText(null, step.getName()))); 1328 } 1329 } 1330 catch (AmetysRepositoryException e) 1331 { 1332 // no workflow node for process 1333 } 1334 1335 return jsonData; 1336 } 1337 1338 1339}