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