001/* 002 * Copyright 2023 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.workflow.readers; 017 018import java.util.List; 019 020import org.apache.cocoon.environment.Request; 021import org.apache.commons.lang3.StringUtils; 022 023import org.ametys.plugins.workflow.dao.WorkflowStepDAO; 024import org.ametys.runtime.i18n.I18nizableText; 025 026import com.opensymphony.workflow.loader.ActionDescriptor; 027import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 028import com.opensymphony.workflow.loader.ResultDescriptor; 029import com.opensymphony.workflow.loader.StepDescriptor; 030import com.opensymphony.workflow.loader.WorkflowDescriptor; 031 032/** 033 * Serialize PlantUML state diagram 034 */ 035public class PlantUMLSVGReader extends AbstractPlantUMLSVGReader 036{ 037 @Override 038 protected String _getPlantUMLType() 039 { 040 return "uml"; 041 } 042 043 @Override 044 protected String _getPlantUMLStyle() 045 { 046 StringBuilder style = new StringBuilder(); 047 048 style.append("skinparam HyperLinkUnderline false \n"); 049 style.append("skinparam HyperLinkColor #0a7fb2 \n"); 050 style.append("skinparam State { \n"); 051 style.append("BorderThickness 1.5 \n"); 052 style.append("} \n"); 053 style.append("hide empty description\n"); 054 055 return style.toString(); 056 } 057 058 @Override 059 protected String _getPlantUMLGraphContent(Request request, WorkflowDescriptor workflowDescriptor) 060 { 061 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 062 StringBuilder content = new StringBuilder(); 063 if (steps.isEmpty()) 064 { 065 content.append("title \n"); 066 content.append(_i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_PREVIEW_EMPTY_WORKFLOW")) + " \n"); 067 content.append("end title \n"); 068 } 069 else 070 { 071 for (StepDescriptor step : steps) 072 { 073 //create state node 074 content.append(_createState(workflowDescriptor, step)); 075 076 List<ActionDescriptor> actions = step.getActions(); 077 for (ActionDescriptor action : actions) 078 { 079 //create state's outgoing transitions 080 content.append(_createTransition(workflowDescriptor, step, action)); 081 } 082 } 083 084 //create workflow's initials actions 085 List<ActionDescriptor> initialActions = workflowDescriptor.getInitialActions(); 086 for (ActionDescriptor initialAction: initialActions) 087 { 088 ResultDescriptor unconditionalResult = initialAction.getUnconditionalResult(); 089 String workflowName = workflowDescriptor.getName(); 090 String stepLabel = _i18nHelper.translateKey(workflowName, _workflowStepDAO.getStepLabel(workflowDescriptor, 0), WorkflowStepDAO.DEFAULT_STEP_NAME); 091 stepLabel = _getStringWithEscapedSpace(stepLabel); 092 String actionLabel = _workflowTransitionDAO.getActionLabel(workflowName, initialAction); 093 actionLabel = _getStringWithEscapedSpace(actionLabel); 094 095 String actionContent = _getTransitionArrow( 096 "[*]", 097 String.valueOf(unconditionalResult.getStep()), 098 _getActionLabel(workflowName, initialAction), 099 _workflowTransitionDAO.getActionIconPathAsBase64(workflowName, initialAction), 100 _getJsFunction(workflowName, "0", stepLabel, String.valueOf(initialAction.getId()), actionLabel), 101 _getActionTooltip(initialAction), 102 false 103 ); 104 content.append(actionContent); 105 } 106 } 107 return content.toString(); 108 } 109 110 /** 111 * Get the transition arrow between 2 states 112 * @param incomingState state's id before transition 113 * @param outgoingState state's id after transition 114 * @param label the transition's label 115 * @param iconPath the transition's icon path 116 * @param jsFunction the js function for sending current transition as selection 117 * @param linkTooltip the transition link's tooltip 118 * @param isDashed true if the arrow must be dashed 119 * @return the transition arrow content 120 */ 121 protected String _getTransitionArrow(String incomingState, String outgoingState, String label, String iconPath, String jsFunction, String linkTooltip, boolean isDashed) 122 { 123 StringBuilder nodeContent = new StringBuilder(incomingState); 124 nodeContent.append(isDashed ? " -[dashed]-> " : " --> "); 125 nodeContent.append(outgoingState); 126 if (StringUtils.isNotBlank(label)) 127 { 128 nodeContent.append(": "); 129 nodeContent.append(" [[javascript:parent." + jsFunction + " "); 130 nodeContent.append("{" + linkTooltip + "}"); 131 132 if (StringUtils.isNotBlank(iconPath)) 133 { 134 nodeContent.append(" <img:" + iconPath + ">"); 135 } 136 137 nodeContent.append(" " + label); 138 nodeContent.append("]]"); 139 } 140 nodeContent.append(" \n"); 141 142 return nodeContent.toString(); 143 } 144 145 /** 146 * Create new state in diagram 147 * @param workflow unique id of current workflow 148 * @param step a step of the workflow 149 * @return a string defining a plantUML state 150 */ 151 protected String _createState(WorkflowDescriptor workflow, StepDescriptor step) 152 { 153 String stepToUML = ""; 154 155 String steplabel = _getStepNodeLabel(workflow, step.getId()); 156 String stepLabelForJs = _i18nHelper.translateKey(workflow.getName(), _workflowStepDAO.getStepLabel(workflow, step.getId()), WorkflowStepDAO.DEFAULT_STEP_NAME); 157 stepLabelForJs = _getStringWithEscapedSpace(stepLabelForJs); 158 String elementAbsoluteIconPath = _workflowStepDAO.getStepIconPathAsBase64(workflow, step.getId()); 159 String stepId = String.valueOf(step.getId()); 160 String toolip = " {" + _getStepTooltip(step) + "}"; 161 String icon = StringUtils.isBlank(elementAbsoluteIconPath) 162 ? StringUtils.EMPTY 163 : "<img:" + elementAbsoluteIconPath + "> "; 164 String jsFunction = " [[javascript:parent." + _getJsFunction(workflow.getName(), stepId, stepLabelForJs, null, null) + toolip + "]]"; 165 stepToUML = "state \"" + icon + steplabel + "\" as " + stepId + jsFunction + __MAIN_STEP_NODE_COLOR + " \n"; 166 167 return stepToUML; 168 } 169 170 /** 171 * Create a new transition between two states in diagram 172 * @param workflow unique id of current workflow 173 * @param step the initial step of this transition 174 * @param transition the current action 175 * @return a string defining a plantUML transition 176 */ 177 protected String _createTransition(WorkflowDescriptor workflow, StepDescriptor step, ActionDescriptor transition) 178 { 179 StringBuilder transitionToUML = new StringBuilder(); 180 181 String workflowName = workflow.getName(); 182 String transitionlabel = _getActionLabel(workflowName, transition); 183 184 String stepLabel = _i18nHelper.translateKey(workflow.getName(), _workflowStepDAO.getStepLabel(workflow, step.getId()), WorkflowStepDAO.DEFAULT_STEP_NAME); 185 stepLabel = _getStringWithEscapedSpace(stepLabel); 186 String actionLabel = _workflowTransitionDAO.getActionLabel(workflowName, transition); 187 actionLabel = _getStringWithEscapedSpace(actionLabel); 188 189 String tooltip = _getActionTooltip(transition); 190 String elementAbsoluteIconPath = _workflowTransitionDAO.getActionIconPathAsBase64(workflowName, transition); 191 String jsFunction = _getJsFunction(workflow.getName(), String.valueOf(step.getId()), stepLabel, String.valueOf(transition.getId()), actionLabel); 192 String initialStep = String.valueOf(step.getId()); 193 ResultDescriptor unconditionalResult = transition.getUnconditionalResult(); 194 String finalStep = unconditionalResult.getStep() != -1 195 ? String.valueOf(unconditionalResult.getStep()) 196 : initialStep; 197 198 List<ConditionalResultDescriptor> conditionalResults = transition.getConditionalResults(); 199 if (!conditionalResults.isEmpty()) 200 { 201 //create intermediary conditional state 202 String choiceStateName = "step" + initialStep + "action" + String.valueOf(transition.getId()); 203 transitionToUML.append("state " + choiceStateName + " <<choice>> \n"); 204 //create intermediary transition 205 String actionContent = _getTransitionArrow(initialStep, choiceStateName, transitionlabel, elementAbsoluteIconPath, jsFunction, tooltip, true); 206 transitionToUML.append(actionContent); 207 208 //create unconditional transition 209 String unconditionalActionContent = _getTransitionArrow(choiceStateName, finalStep, null, null, null, null, false); 210 transitionToUML.append(unconditionalActionContent); 211 212 //create conditionnal transitions 213 for (ConditionalResultDescriptor result : conditionalResults) 214 { 215 String conditionalStep = result.getStep() != -1 216 ? String.valueOf(result.getStep()) 217 : initialStep; 218 if (!conditionalStep.equals(finalStep)) //prevent duplicate transition arrow in graph 219 { 220 String conditionalActionContent = _getTransitionArrow(choiceStateName, conditionalStep, null, null, null, null, false); 221 transitionToUML.append(conditionalActionContent); 222 } 223 } 224 } 225 else 226 { 227 //create unconditional transition 228 String actionContent = _getTransitionArrow(initialStep, finalStep, transitionlabel, elementAbsoluteIconPath, jsFunction, tooltip, false); 229 transitionToUML.append(actionContent); 230 } 231 return transitionToUML.toString(); 232 } 233}