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