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.URIUtils; 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()) 459 || _rightManager.currentUserHasRight(RIGHT_WORKFLOW_DELETE, null) == RightResult.RIGHT_ALLOW) 460 { 461 List<Object> workflowProcessus = getWorkflowProcessus(workflow); 462 if (workflowProcessus.size() == 0) 463 { 464 workflows.add(workflow); 465 } 466 } 467 } 468 469 List<String> workflowDeletedIds = new ArrayList<>(); 470 for (JCRWorkflow workflow : workflows) 471 { 472 ModifiableAmetysObject parent = (ModifiableAmetysObject) workflow.getParent(); 473 workflowDeletedIds.add(workflow.getId()); 474 workflow.remove(); 475 parent.saveChanges(); 476 } 477 result.put("ids", workflowDeletedIds); 478 479 result.put("success", true); 480 return result; 481 } 482 483 /** 484 * Retrieve the list of process created from a workflow 485 * @param workflowId The workflow 486 * @return The list of process 487 */ 488 @Callable (right = RIGHTS_PROCESS_MANAGE_PROCESSES) 489 public Map<String, Object> getWorkflowProcesses(String workflowId) 490 { 491 Map<String, Object> result = new HashMap<>(); 492 JCRWorkflow workflow = _resolver.resolveById(workflowId); 493 result.put("processes", getWorkflowProcessus(workflow).stream().map(process -> _processToJson((JCRWorkflowProcess) process)).collect(Collectors.toList())); 494 return result; 495 } 496 497 /** 498 * Retrieve the list of process created from a workflow 499 * @param workflow The workflow 500 * @return The list of process 501 */ 502 public List<Object> getWorkflowProcessus(JCRWorkflow workflow) 503 { 504 try 505 { 506 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, false); 507 return processesRoot.getChildren().stream().collect(Collectors.toList()); 508 } 509 catch (UnknownAmetysObjectException e) 510 { 511 // no processes for the workflow 512 } 513 return new ArrayList<>(); 514 } 515 516 517 /** 518 * Create a new process 519 * @param workflowId The workflow used by the process 520 * @param title The process title 521 * @param site The site on which the process was created 522 * @param description The process description 523 * @param uploadedAttachments The process attachments 524 * @return The new process 525 * @throws WorkflowException If an error occurred creating the workflow 526 * @throws IllegalAccessException If the user is not allowed 527 */ 528 public JCRWorkflowProcess createProcess(String workflowId, String title, String site, String description, List<PartOnDisk> uploadedAttachments) throws WorkflowException, IllegalAccessException 529 { 530 JCRWorkflow workflow = _resolver.resolveById(workflowId); 531 532 if (!isUserAllowedOnWorkflow(workflow)) 533 { 534 throw new IllegalAccessException("User '" + _currentUserProvider.getUser().toString() + "' tried to create a process with insufficient rights"); 535 } 536 537 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, true); 538 539 String originalName = FilterNameHelper.filterName(title); 540 // Find unique name 541 String uniqueName = originalName; 542 int index = 2; 543 while (processesRoot.hasChild(uniqueName)) 544 { 545 uniqueName = originalName + "-" + (index++); 546 } 547 548 JCRWorkflowProcess process = (JCRWorkflowProcess) processesRoot.createChild(uniqueName, JCRWorkflowProcessFactory.WORKFLOW_PROCESS_NODE_TYPE); 549 process.setCreator(_currentUserProvider.getUser()); 550 process.setWorkflow(workflowId); 551 process.setCreationDate(new Date()); 552 _setProcessValues(process, title, site, description, uploadedAttachments); 553 554 // create workflow with entry id 555 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 556 557 String workflowName = workflow.getWorkflowDefinition(); 558 int initialActionId = _workflowHelper.getInitialAction(workflowName); 559 560 // Add the workflow used by the process in the request, to be used by any RegisterVariable of the workflow definition 561 Request request = ContextHelper.getRequest(_context); 562 request.setAttribute("workflowId", process.getWorkflow()); 563 564 Map<String, Object> inputs = new HashMap<>(); 565 inputs.put("workflowId", workflowId); 566 inputs.put("process", process); 567 long workflowInstanceId = workflowStore.initialize(workflowName, initialActionId, inputs); 568 process.setWorkflowId(workflowInstanceId); 569 Step currentStep = (Step) workflowStore.getCurrentSteps(process.getWorkflowId()).iterator().next(); 570 process.setCurrentStepId(currentStep.getStepId()); 571 process.saveChanges(); 572 573 return process; 574 } 575 576 577 /** 578 * Edit a process 579 * @param processId The process id 580 * @param title The title 581 * @param description The description 582 * @param uploadedAttachments The list of new attachments uploaded 583 * @param attachmentsUntouched The list of untouched attachments names 584 * @throws WorkflowException If an error occurred while executing the workflow action 585 */ 586 public void editProcess(String processId, String title, String description, List<PartOnDisk> uploadedAttachments, List<String> attachmentsUntouched) throws WorkflowException 587 { 588 JCRWorkflowProcess process = _resolver.resolveById(processId); 589 590 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 591 Map<String, Object> inputs = new HashMap<>(); 592 inputs.put("process", process); 593 594 inputs.put("title", title); 595 inputs.put("description", description); 596 inputs.put("uploadedAttachments", uploadedAttachments); 597 inputs.put("attachmentsUntouched", attachmentsUntouched); 598 599 // Add the workflow used by the process in the request, to be used by any RegisterVariable of the workflow definition 600 Request request = ContextHelper.getRequest(_context); 601 request.setAttribute("workflowId", process.getWorkflow()); 602 603 workflowStore.doAction(process.getWorkflowId(), WORKFLOW_ACTION_EDIT, inputs); 604 } 605 606 /** 607 * Retrieve a process 608 * @param workflowName The workflow name 609 * @param processName The process name 610 * @return The process 611 */ 612 public JCRWorkflowProcess getProcess(String workflowName, String processName) 613 { 614 try 615 { 616 ModifiableTraversableAmetysObject workflowsRoot = _getWorkflowsRootNode(false); 617 JCRWorkflow workflow = workflowsRoot.getChild(workflowName); 618 ModifiableTraversableAmetysObject processesRoot = _getProcessesRootNode(workflow, false); 619 if (processesRoot.hasChild(processName)) 620 { 621 return processesRoot.getChild(processName); 622 } 623 } 624 catch (UnknownAmetysObjectException e) 625 { 626 // No workflow or process created with this workflowId and processId 627 return null; 628 } 629 630 return null; 631 } 632 633 /** 634 * Determines if the process is complete 635 * @param process The process 636 * @return True if the process is complete 637 */ 638 public boolean isComplete(JCRWorkflowProcess process) 639 { 640 AmetysObjectWorkflow aoWorkflow = _workflowProvider.getAmetysObjectWorkflow(process, true); 641 if (aoWorkflow != null) 642 { 643 int entryState = aoWorkflow.getEntryState(process.getWorkflowId()); 644 return entryState == WorkflowEntry.COMPLETED; 645 } 646 647 // FIXME on final step the workflow is compelte but deleted ... 648 return false; 649 } 650 651 /** 652 * Determines if the process can be deleted by the current user 653 * @param process The process 654 * @return True if the process can be deleted 655 */ 656 public boolean canDelete(JCRWorkflowProcess process) 657 { 658 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) == RightResult.RIGHT_ALLOW) 659 { 660 // A super admin can delete a process at all time 661 return true; 662 } 663 664 // Otherwise a process can be deleted only by the creator if is complete 665 return isComplete(process) && process.getCreator().equals(_currentUserProvider.getUser()); 666 } 667 668 /** 669 * Delete a process 670 * @param process The process to delete 671 * @throws IllegalAccessException If a user tries to delete a process without being allowed 672 */ 673 public void deleteProcess(JCRWorkflowProcess process) throws IllegalAccessException 674 { 675 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) != RightResult.RIGHT_ALLOW) 676 { 677 Workflow workflowStore = _workflowProvider.getAmetysObjectWorkflow(process, true); 678 int entryState = workflowStore.getEntryState(process.getWorkflowId()); 679 if (entryState != WorkflowEntry.COMPLETED || !process.getCreator().equals(_currentUserProvider.getUser())) 680 { 681 throw new IllegalAccessException("User '" + _currentUserProvider.getUser() + "' tried to delete a process without sufficient rights."); 682 } 683 } 684 685 ModifiableAmetysObject parent = process.getParent(); 686 process.remove(); 687 parent.saveChanges(); 688 } 689 690 /** 691 * Delete a list of processes 692 * @param processes The processes to delete 693 * @return The result 694 * @throws IllegalAccessException If a user tries to delete a process without being allowed 695 */ 696 @Callable (right = RIGHTS_PROCESS_MANAGE_PROCESSES) 697 public Map<String, Object> deleteProcesses(List<String> processes) throws IllegalAccessException 698 { 699 Map<String, Object> result = new HashMap<>(); 700 List<String> processesDeletedIds = new ArrayList<>(); 701 for (String processId : processes) 702 { 703 JCRWorkflowProcess process = _resolver.resolveById(processId); 704 ModifiableAmetysObject parent = (ModifiableAmetysObject) process.getParent(); 705 processesDeletedIds.add(process.getId()); 706 process.remove(); 707 parent.saveChanges(); 708 } 709 710 result.put("ids", processesDeletedIds); 711 result.put("success", true); 712 return result; 713 } 714 715 /** 716 * Get the url of a process page 717 * @param process The process 718 * @param siteName The current site name. Can not be null. 719 * @param lang The current language. Can not be null. 720 * @param absolute true to get absolute url. 721 * @return the url of process page 722 */ 723 public String getProcessPageUrl(JCRWorkflowProcess process, String siteName, String lang, boolean absolute) 724 { 725 StringBuilder sb = new StringBuilder(); 726 727 if (absolute) 728 { 729 sb.append(_uriPrefixHandler.getAbsoluteUriPrefix(siteName)); 730 } 731 else 732 { 733 sb.append(_uriPrefixHandler.getUriPrefix(siteName)); 734 } 735 736 sb.append("/") 737 .append(lang) 738 .append("/_plugins/") 739 .append(_pluginName) 740 .append("/") 741 .append(getTemplateForProcessPage(siteName, lang)) 742 .append("/process/"); 743 744 JCRWorkflow workflow = _resolver.resolveById(process.getWorkflow()); 745 746 sb.append(URIUtils.encodePathSegment(workflow.getName())) 747 .append("/") 748 .append(URIUtils.encodePathSegment(process.getName())) 749 .append(".html"); 750 751 return sb.toString(); 752 } 753 754 /** 755 * Get the page with PROCESS_DASHBOARD tag 756 * @param siteName The current site name 757 * @param lang The language 758 * @return The page or null if not found. 759 */ 760 public Page getDashboardPage (String siteName, String lang) 761 { 762 TagExpression tagExpression = new TagExpression(Operator.EQ, PAGE_TAG_PROCESS_DASHBOARD); 763 764 String pathQuery = PageQueryHelper.getPageXPathQuery(siteName, lang, null, tagExpression, null); 765 AmetysObjectIterable<Page> page = _ametysObjectResolver.query(pathQuery); 766 767 if (page.getSize() == 0) 768 { 769 return null; 770 } 771 772 return page.iterator().next(); 773 } 774 775 /** 776 * Get the page with CREATE_PROCESS tag 777 * @param siteName The current site name 778 * @param lang The language 779 * @return The page or null if not found. 780 */ 781 public Page getCreateProcessPage (String siteName, String lang) 782 { 783 TagExpression tagExpression = new TagExpression(Operator.EQ, PAGE_TAG_CREATEPROCESS); 784 785 String pathQuery = PageQueryHelper.getPageXPathQuery(siteName, lang, null, tagExpression, null); 786 AmetysObjectIterable<Page> page = _ametysObjectResolver.query(pathQuery); 787 788 if (page.getSize() == 0) 789 { 790 return null; 791 } 792 793 return page.iterator().next(); 794 } 795 796 /** 797 * Get the template to use for process rendering 798 * @param siteName The site containing the page 799 * @param lang The sitemap of the page 800 * @return The template to use for process rendering 801 */ 802 public String getTemplateForProcessPage(String siteName, String lang) 803 { 804 Site site = _siteManager.getSite(siteName); 805 806 String skinName = site.getSkinId(); 807 Skin skin = _skinsManager.getSkin(skinName); 808 Set<String> templates = skin.getTemplates(); 809 810 // First look for the template bpm-process 811 if (templates.contains(_BPM_PROCESS_TEMPLATE)) 812 { 813 return _BPM_PROCESS_TEMPLATE; 814 } 815 816 // Otherwise, returns the template used by create process page 817 Page createProcessPage = getCreateProcessPage(siteName, lang); 818 if (createProcessPage != null) 819 { 820 return createProcessPage.getTemplate(); 821 } 822 823 // Otherwise, fallback to the template page if exists 824 if (templates.contains("page")) 825 { 826 return "page"; 827 } 828 829 // Finally, fallback to the first template 830 return templates.iterator().next(); 831 } 832 833 /** 834 * Test if the current user is present in any variable of the workflow 835 * @param workflow The workflow 836 * @return True if the user is in the workflow variables 837 */ 838 @SuppressWarnings("unchecked") 839 public boolean isUserInWorkflowVariables(JCRWorkflow workflow) 840 { 841 Map<String, Object> workflowDefinitionData = _getWorkflowDefinitionData(workflow.getWorkflowDefinition()); 842 843 if (workflowDefinitionData != null && workflowDefinitionData.containsKey("variables")) 844 { 845 Map<String, Map<String, String>> variables = (Map<String, Map<String, String>>) workflowDefinitionData.get("variables"); 846 UserIdentity currentUser = _currentUserProvider.getUser(); 847 848 boolean userInVariables = variables.entrySet().stream() 849 .filter(e -> "user".equals(e.getValue().getOrDefault("type", null))) 850 .map(e -> workflow.getVariable(e.getKey())) 851 .filter(var -> var != null && var instanceof String) 852 .map(var -> _jsonUtils.convertJsonToList((String) var)) 853 .map(usersList -> 854 { 855 return usersList.stream() 856 .map(userData -> 857 { 858 Map<String, String> user = (Map<String, String>) userData; 859 String login = user.get("login"); 860 String populationId = user.get("populationId"); 861 return new UserIdentity(login, populationId); 862 }) 863 .filter(user -> user != null && user.equals(currentUser)) 864 .findAny(); 865 }) 866 .filter(optionalUser -> optionalUser.isPresent()) 867 .findAny() 868 .isPresent(); 869 870 return userInVariables; 871 } 872 return false; 873 } 874 875 /** 876 * Get the list of processes accessibles to the current user 877 * @return The list of processes 878 */ 879 public Set<JCRWorkflowProcess> getUserProcesses() 880 { 881 Set<JCRWorkflowProcess> processes = new HashSet<>(); 882 UserIdentity user = _currentUserProvider.getUser(); 883 if (user == null) 884 { 885 return processes; 886 } 887 888 if (_rightManager.currentUserHasRight(RIGHTS_PROCESS_MANAGE_PROCESSES, null) == RightResult.RIGHT_ALLOW) 889 { 890 // return all processes as we have the right to manage all of them 891 AmetysObjectIterable<JCRWorkflowProcess> processesOwned = _ametysObjectResolver.query("//element(*, ametys:workflowProcess)"); 892 Iterator<JCRWorkflowProcess> it = processesOwned.iterator(); 893 while (it.hasNext()) 894 { 895 processes.add(it.next()); 896 } 897 return processes; 898 } 899 900 // retrieve processes of which we are the creator 901 String xpath = String.format("//element(*, ametys:workflowProcess)[@ametys:creator/ametys:login='%s' and @ametys:creator/ametys:population='%s']", user.getLogin(), user.getPopulationId()); 902 AmetysObjectIterable<JCRWorkflowProcess> processesOwned = _ametysObjectResolver.query(xpath); 903 Iterator<JCRWorkflowProcess> it = processesOwned.iterator(); 904 while (it.hasNext()) 905 { 906 processes.add(it.next()); 907 } 908 909 // add processes for which we are in the workflow variables 910 ModifiableTraversableAmetysObject workflowsRootNode = _getWorkflowsRootNode(false); 911 AmetysObjectIterable<AmetysObject> children = workflowsRootNode.getChildren(); 912 for (AmetysObject child : children) 913 { 914 if (child instanceof JCRWorkflow) 915 { 916 JCRWorkflow workflow = (JCRWorkflow) child; 917 try 918 { 919 ModifiableTraversableAmetysObject processesRootNode = _getProcessesRootNode(workflow, false); 920 AmetysObjectIterable<JCRWorkflowProcess> workflowChildren = processesRootNode.getChildren(); 921 if (workflowChildren.getSize() > 0 && isUserInWorkflowVariables(workflow)) 922 { 923 AmetysObjectIterator<JCRWorkflowProcess> it2 = workflowChildren.iterator(); 924 while (it2.hasNext()) 925 { 926 processes.add(it2.next()); 927 } 928 } 929 } 930 catch (UnknownAmetysObjectException e) 931 { 932 // No process for workflow, do nothing 933 } 934 } 935 } 936 937 return processes; 938 } 939 940 /** 941 * Check if the current user is allowed to create a process from the workflow 942 * @param workflow The workflow 943 * @return True if the user is allowed 944 */ 945 public boolean isUserAllowedOnWorkflow(JCRWorkflow workflow) 946 { 947 UserIdentity user = _currentUserProvider.getUser(); 948 if (user != null) 949 { 950 if (user.equals(workflow.getOwner()) || ArrayUtils.contains(workflow.getAllowedUsers(), user)) 951 { 952 return true; 953 } 954 955 GroupIdentity[] allowedGroups = workflow.getAllowedGroups(); 956 for (GroupIdentity userGroup : _groupManager.getUserGroups(user)) 957 { 958 if (ArrayUtils.contains(allowedGroups, userGroup)) 959 { 960 return true; 961 } 962 } 963 964 } 965 return false; 966 } 967 968 @SuppressWarnings("unchecked") 969 private void _setWorkflowValues(JCRWorkflow workflow, Map<String, Object> values) 970 { 971 String title = (String) values.get("title"); 972 String description = (String) values.getOrDefault("description", null); 973 Map<String, Object> owner = (Map<String, Object>) values.getOrDefault("owner", null); 974 List<Map<String, Object>> allowedUsersValue = (List<Map<String, Object>>) values.getOrDefault("allowedUsers", null); 975 List<Map<String, Object>> allowedGroupsValue = (List<Map<String, Object>>) values.getOrDefault("allowedGroups", null); 976 Map<String, Object> variables = (Map<String, Object>) values.getOrDefault("variables", null); 977 978 workflow.setTitle(title); 979 workflow.setDescription(description); 980 workflow.setOwner(owner != null ? _convertJsonToUser(owner) : null); 981 982 if (allowedUsersValue != null) 983 { 984 Set<UserIdentity> allowedUsers = new HashSet<>(); 985 for (Object obj : allowedUsersValue) 986 { 987 UserIdentity user = _convertJsonToUser((Map<String, Object>) obj); 988 if (user != null) 989 { 990 allowedUsers.add(user); 991 } 992 } 993 994 workflow.setAllowedUsers(allowedUsers.toArray(new UserIdentity[allowedUsers.size()])); 995 } 996 997 if (allowedGroupsValue != null) 998 { 999 Set<GroupIdentity> allowedGroups = new HashSet<>(); 1000 for (Object obj : allowedGroupsValue) 1001 { 1002 Map<String, Object> grantedUser = (Map<String, Object>) obj; 1003 String groupId = (String) grantedUser.get("groupId"); 1004 String groupDirectory = (String) grantedUser.get("groupDirectory"); 1005 1006 if (groupId != null && groupDirectory != null) 1007 { 1008 allowedGroups.add(new GroupIdentity(groupId, groupDirectory)); 1009 } 1010 } 1011 1012 workflow.setAllowedGroups(allowedGroups.toArray(new GroupIdentity[allowedGroups.size()])); 1013 } 1014 1015 for (Entry<String, Object> entry : variables.entrySet()) 1016 { 1017 workflow.setVariable(entry.getKey(), entry.getValue()); 1018 } 1019 } 1020 1021 private void _setProcessValues(JCRWorkflowProcess process, String title, String site, String description, List<PartOnDisk> uploadedAttachments) 1022 { 1023 if (title != null) 1024 { 1025 process.setTitle(title); 1026 } 1027 1028 if (site != null) 1029 { 1030 process.setSite(site); 1031 } 1032 1033 process.setDescription(description); 1034 1035 ResourceCollection rootAttachments = process.getRootAttachments(true); 1036 if (rootAttachments instanceof ModifiableResourceCollection) 1037 { 1038 _addOrUpdateResourceHelper.checkAddResourceRight((ModifiableResourceCollection) rootAttachments); 1039 1040 for (Part attachment : uploadedAttachments) 1041 { 1042 _addOrUpdateResourceHelper.performResourceOperation(attachment, (ModifiableResourceCollection) rootAttachments, ResourceOperationMode.ADD); 1043 } 1044 } 1045 } 1046 1047 private UserIdentity _convertJsonToUser(Map<String, Object> user) 1048 { 1049 String login = (String) user.get("login"); 1050 String populationId = (String) user.get("populationId"); 1051 1052 if (login != null && populationId != null) 1053 { 1054 return new UserIdentity(login, populationId); 1055 } 1056 1057 return null; 1058 } 1059 1060 private Map<String, Object> _workflowToJson(JCRWorkflow workflow, Map<String, Object> workflowDefinitionData) 1061 { 1062 if (workflow == null) 1063 { 1064 return null; 1065 } 1066 1067 Map<String, Object> jsonData = new HashMap<>(); 1068 1069 jsonData.put("id", workflow.getId()); 1070 jsonData.put("title", workflow.getTitle()); 1071 Map<String, Object> workflowBuffer = new HashMap<>(); 1072 workflowBuffer.put("value", workflow.getWorkflowDefinition()); 1073 workflowBuffer.put("label", new I18nizableText("application", "WORKFLOW_" + workflow.getWorkflowDefinition())); 1074 if (workflowDefinitionData == null) 1075 { 1076 workflowBuffer.put("missing", true); 1077 } 1078 jsonData.put("workflowDefinition", workflowBuffer); 1079 jsonData.put("description", workflow.getDescription()); 1080 jsonData.put("owner", _userHelper.user2json(workflow.getOwner(), true)); 1081 jsonData.put("allowedUsers", Arrays.stream(workflow.getAllowedUsers()).map(ud -> _userHelper.user2json(ud, true)).collect(Collectors.toList())); 1082 jsonData.put("allowedGroups", _groups2Json(workflow.getAllowedGroups())); 1083 if (workflowDefinitionData != null && workflowDefinitionData.containsKey("variables")) 1084 { 1085 @SuppressWarnings("unchecked") 1086 Map<String, Map<String, String>> variables = (Map<String, Map<String, String>>) workflowDefinitionData.get("variables"); 1087 if (variables != null) 1088 { 1089 jsonData.put("variables", variables.keySet().stream().filter(key -> workflow.getVariable(key) != null).collect(Collectors.toMap(Function.identity(), key -> workflow.getVariable(key)))); 1090 } 1091 } 1092 1093 jsonData.put("process", getWorkflowProcessus(workflow).size()); 1094 1095 return jsonData; 1096 } 1097 1098 private List<Map<String, Object>> _groups2Json(GroupIdentity[] groups) 1099 { 1100 List<Map<String, Object>> json = new ArrayList<>(); 1101 for (GroupIdentity group : groups) 1102 { 1103 Map<String, Object> groupJson = new HashMap<>(); 1104 groupJson.put("groupId", group.getId()); 1105 groupJson.put("groupDirectory", group.getDirectoryId()); 1106 json.add(groupJson); 1107 } 1108 return json; 1109 } 1110 1111 /** 1112 * Gets the root of contents 1113 * @param create <code>true</code> to create automatically the root when missing. 1114 * @return the root of workflows 1115 * @throws UnknownAmetysObjectException If root node does not exist 1116 */ 1117 protected ModifiableTraversableAmetysObject _getWorkflowsRootNode(boolean create) 1118 { 1119 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins/"); 1120 1121 boolean needSave = false; 1122 if (!pluginsNode.hasChild(BPM_ROOT_NODE)) 1123 { 1124 if (create) 1125 { 1126 pluginsNode.createChild(BPM_ROOT_NODE, "ametys:unstructured"); 1127 needSave = true; 1128 } 1129 else 1130 { 1131 throw new UnknownAmetysObjectException("Node '/ametys:plugins/" + BPM_ROOT_NODE + "' is missing"); 1132 } 1133 } 1134 1135 ModifiableTraversableAmetysObject bpmNode = pluginsNode.getChild(BPM_ROOT_NODE); 1136 if (!bpmNode.hasChild(BPMWORKFLOW_ROOT_NODE)) 1137 { 1138 if (create) 1139 { 1140 bpmNode.createChild(BPMWORKFLOW_ROOT_NODE, "ametys:unstructured"); 1141 needSave = true; 1142 } 1143 else 1144 { 1145 throw new UnknownAmetysObjectException("Node '/ametys:plugins/" + BPM_ROOT_NODE + "/" + BPMWORKFLOW_ROOT_NODE + "' is missing"); 1146 } 1147 } 1148 1149 if (needSave) 1150 { 1151 pluginsNode.saveChanges(); 1152 } 1153 1154 return bpmNode.getChild(BPMWORKFLOW_ROOT_NODE); 1155 } 1156 1157 /** 1158 * Gets the root of processes of a workflow 1159 * @param workflow The workflow 1160 * @param create <code>true</code> to create automatically the root when missing. 1161 * @return the root of processes 1162 * @throws UnknownAmetysObjectException If root node does not exist 1163 */ 1164 protected ModifiableTraversableAmetysObject _getProcessesRootNode(JCRWorkflow workflow, boolean create) 1165 { 1166 boolean needSave = false; 1167 if (!workflow.hasChild(BPMPROCESSES_ROOT_NODE)) 1168 { 1169 if (create) 1170 { 1171 workflow.createChild(BPMPROCESSES_ROOT_NODE, "ametys:unstructured"); 1172 needSave = true; 1173 } 1174 else 1175 { 1176 throw new UnknownAmetysObjectException("Node '" + BPMPROCESSES_ROOT_NODE + "' is missing for workflow '" + workflow.getId() + "'"); 1177 } 1178 } 1179 1180 if (needSave) 1181 { 1182 workflow.saveChanges(); 1183 } 1184 1185 return workflow.getChild(BPMPROCESSES_ROOT_NODE); 1186 } 1187 1188 1189 /** 1190 * Get the list of variables and their properties for a workflow definition 1191 * @param workflowDefId The workflow definition id 1192 * @return The list of variables properties, mapped by variable id 1193 */ 1194 protected Map<String, Object> _getWorkflowDefinitionData(String workflowDefId) 1195 { 1196 if (_workflowDefinitionsCache.containsKey(workflowDefId)) 1197 { 1198 return _workflowDefinitionsCache.get(workflowDefId); 1199 } 1200 1201 Map<String, Object> workflowDefinitionData = new HashMap<>(); 1202 1203 Map<String, Map<String, Object>> variables = new HashMap<>(); 1204 Workflow externalWorkflow = _workflowProvider.getExternalWorkflow(JdbcWorkflowStore.ROLE); 1205 if (!ArrayUtils.contains(externalWorkflow.getWorkflowNames(), workflowDefId)) 1206 { 1207 return null; 1208 } 1209 WorkflowDescriptor workflowDescriptor = externalWorkflow.getWorkflowDescriptor(workflowDefId); 1210 if (workflowDescriptor == null) 1211 { 1212 return null; 1213 } 1214 1215 String workflowXml = workflowDescriptor.asXML(); 1216 1217 try (InputStream is = new ByteArrayInputStream(workflowXml.getBytes())) 1218 { 1219 Configuration workflowConfiguration = new DefaultConfigurationBuilder().build(is); 1220 1221 Configuration[] registers = workflowConfiguration.getChild("registers", true).getChildren("register"); 1222 if (registers.length > 0) 1223 { 1224 variables.putAll(_getWorkflowDefinitionRegisters(registers)); 1225 } 1226 } 1227 catch (ConfigurationException | IOException | SAXException e) 1228 { 1229 getLogger().warn("Unable to retrieve the workflow definition xml file for '" + workflowDefId + "'", e); 1230 return null; 1231 } 1232 1233 workflowDefinitionData.put("variables", variables); 1234 _workflowDefinitionsCache.put(workflowDefId, workflowDefinitionData); 1235 return workflowDefinitionData; 1236 } 1237 1238 private Map< ? extends String, ? extends Map<String, Object>> _getWorkflowDefinitionRegisters(Configuration[] registers) throws ConfigurationException 1239 { 1240 Map<String, Map<String, Object>> variables = new HashMap<>(); 1241 1242 for (Configuration register : registers) 1243 { 1244 String variableName = null; 1245 if ("avalon".equals(register.getAttribute("type", null))) 1246 { 1247 Map<String, Object> variableValues = new HashMap<>(); 1248 boolean matchRole = false; 1249 for (Configuration arg : register.getChildren("arg")) 1250 { 1251 String argName = arg.getAttribute("name", null); 1252 String argValue = arg.getValue(); 1253 if (argName != null) 1254 { 1255 if ("role".equals(argName)) 1256 { 1257 matchRole = RegisterVariable.class.getName().equals(argValue); 1258 } 1259 else if ("variableId".equals(argName)) 1260 { 1261 variableName = argValue; 1262 } 1263 else 1264 { 1265 if ("label".equals(argName) || "description".equals(argName)) 1266 { 1267 variableValues.put(argName, new I18nizableText(null, argValue)); 1268 } 1269 else 1270 { 1271 variableValues.put(argName, argValue); 1272 } 1273 } 1274 } 1275 } 1276 1277 if (matchRole && StringUtils.isNotEmpty(variableName)) 1278 { 1279 variables.put(variableName, variableValues); 1280 } 1281 } 1282 } 1283 1284 return variables; 1285 } 1286 1287 private Map<String, Object> _processToJson(JCRWorkflowProcess process) 1288 { 1289 Map<String, Object> jsonData = new HashMap<>(); 1290 1291 jsonData.put("id", process.getId()); 1292 jsonData.put("title", process.getTitle()); 1293 jsonData.put("creationDate", DateUtils.dateToString(process.getCreationDate())); 1294 jsonData.put("description", process.getDescription()); 1295 jsonData.put("creator", _userHelper.user2json(process.getCreator(), true)); 1296 1297 List<Map<String, String>> attachmentsList = new ArrayList<>(); 1298 1299 ResourceCollection rootAttachments = process.getRootAttachments(false); 1300 if (rootAttachments != null) 1301 { 1302 AmetysObjectIterable<ModifiableResource> attachments = rootAttachments.getChildren(); 1303 JCRWorkflow workflow = _resolver.resolveById(process.getWorkflow()); 1304 1305 String attachmentPathPrefix = workflow.getName() + "/" + process.getName() + "/_attachments/"; 1306 1307 for (ModifiableResource attachment : attachments) 1308 { 1309 Map<String, String> attachmentData = new HashMap<>(); 1310 1311 String name = attachment.getName(); 1312 attachmentData.put("name", name); 1313 attachmentData.put("path", attachmentPathPrefix + name); 1314 1315 attachmentsList.add(attachmentData); 1316 } 1317 } 1318 1319 jsonData.put("attachments", attachmentsList); 1320 1321 AmetysObjectWorkflow aoWorkflow = _workflowProvider.getAmetysObjectWorkflow(process, true); 1322 try 1323 { 1324 String workflowName = aoWorkflow.getWorkflowName(process.getWorkflowId()); 1325 if (workflowName != null) 1326 { 1327 WorkflowDescriptor workflowDescriptor = aoWorkflow.getWorkflowDescriptor(workflowName); 1328 StepDescriptor step = workflowDescriptor.getStep((int) process.getCurrentStepId()); 1329 jsonData.put("currentStep", _i18nUtils.translate(new I18nizableText(null, step.getName()))); 1330 } 1331 } 1332 catch (AmetysRepositoryException e) 1333 { 1334 // no workflow node for process 1335 } 1336 1337 return jsonData; 1338 } 1339 1340 1341}