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.HashSet;
019import java.util.List;
020import java.util.Set;
021
022import org.apache.cocoon.environment.Request;
023
024import org.ametys.plugins.workflow.dao.WorkflowStepDAO;
025import org.ametys.runtime.i18n.I18nizableText;
026
027import com.opensymphony.workflow.loader.ActionDescriptor;
028import com.opensymphony.workflow.loader.ResultDescriptor;
029import com.opensymphony.workflow.loader.StepDescriptor;
030import com.opensymphony.workflow.loader.WorkflowDescriptor;
031
032/**
033 * Read step mindMap from plantUML
034 */
035public class PlantUmlMindMapStepSVGReader extends AbstractPlantUMLMindmapSVGReader 
036{
037    @Override
038    protected String _getPlantUMLStyle()
039    {
040        StringBuilder style = new StringBuilder();
041        style.append("<style> \n");
042        style.append("mindmapDiagram { \n");
043        style.append("boxless { \n");
044        style.append("HyperLinkColor #0a7fb2 \n");
045        style.append("hyperlinkUnderlineThickness 0 \n"); //workaround until HyperlinkUnderline false is fixed
046        style.append("} \n");
047        style.append("} \n");
048        style.append("</style> \n");
049        return style.toString();
050    }
051
052    @Override
053    protected String _getPlantUMLGraphContent(Request request, WorkflowDescriptor workflowDescriptor)
054    {
055        StringBuilder content = new StringBuilder();
056        String workflowName = workflowDescriptor.getName();
057        int stepId = Integer.valueOf((String) request.get("stepId"));
058        String stepIdAsString = stepId != 0 ? String.valueOf(stepId) : WorkflowStepDAO.INITIAL_STEP_ID;
059        
060        // Graph for central step node
061        String nodeContent = _getMindMapNodeContent(
062            "+",
063            _getStepNodeLabel(workflowDescriptor, stepId),
064            _workflowStepDAO.getStepIconPathAsBase64(workflowDescriptor, stepId),
065            __MAIN_STEP_NODE_COLOR,
066            null,
067            null,
068            false
069        );
070        
071        content.append(nodeContent);
072        
073        // Graph for incoming actions
074        Set<ActionDescriptor> incomingActions = _getIncomingActions(stepId, workflowDescriptor);
075        for (ActionDescriptor action : incomingActions)
076        {
077            String firstParentStepId = _getFirstParentStepId(stepId, workflowDescriptor.getSteps(), action.getId());
078            String actionNode = _getMindMapNodeContent(
079                "--",
080                _getActionLabel(action),
081                _workflowStepDAO.getActionIconPathAsBase64(action),
082                null,
083                _getJsFunction(workflowName, firstParentStepId, String.valueOf(action.getId())),
084                _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_TRANSITION")),
085                true
086            );
087            content.append(actionNode);
088        }
089        
090        // Graph for outgoing action
091        List<ActionDescriptor> outgoingActions = _getOutgoingActions(stepId, workflowDescriptor);
092        for (ActionDescriptor action : outgoingActions)
093        {
094            String actionNode = _getMindMapNodeContent(
095                "++",
096                _getActionLabel(action),
097                _workflowStepDAO.getActionIconPathAsBase64(action),
098                null,
099                _getJsFunction(workflowName, stepIdAsString, String.valueOf(action.getId())),
100                _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_TRANSITION")),
101                true
102            );
103            content.append(actionNode);
104        }
105        return content.toString();
106    }
107    
108    /**
109     * Get a set of actions incoming to current step
110     * @param stepId id of current step
111     * @param workflow current workflow
112     * @return the set of outgoing actions
113     */
114    @SuppressWarnings("unchecked")
115    protected Set<ActionDescriptor> _getIncomingActions(int stepId , WorkflowDescriptor workflow)
116    {
117        Set<ActionDescriptor> incomingActions = new HashSet<>();
118        if (stepId != 0)
119        {
120            incomingActions.addAll(_getIncomingActionsFromList(stepId, workflow.getInitialActions()));
121            List<StepDescriptor> steps = workflow.getSteps();
122            for (StepDescriptor otherSteps : steps)
123            {
124                if (otherSteps.getId() != stepId)
125                {
126                    List<ActionDescriptor> actions = otherSteps.getActions();
127                    incomingActions.addAll(_getIncomingActionsFromList(stepId, actions));
128                }
129            }
130        }
131        return incomingActions;
132    }
133    
134    /**
135     * Get a list of actions outgoing from current step
136     * @param stepId id of current step
137     * @param workflow current workflow
138     * @return the list of outgoing actions
139     */
140    protected List<ActionDescriptor> _getOutgoingActions(int stepId, WorkflowDescriptor workflow)
141    {
142        return stepId != 0 
143                ? workflow.getStep(stepId).getActions()
144                : workflow.getInitialActions();
145    }    
146    
147    /**
148     * Get id of the first step having current action, INITIAL_STEP_ID if current action is an initial action
149     * @param stepId id of current step
150     * @param steps list of all the steps in current workflow
151     * @param actionId id of current action
152     * @return the id of the first found step having current action
153     */
154    protected String _getFirstParentStepId(int stepId, List<StepDescriptor> steps, Integer actionId)
155    {
156        String firstParentStepId = "";
157        int i = 0;
158        do 
159        {
160            StepDescriptor otherStep = steps.get(i);
161            if (otherStep.getId() != stepId && otherStep.getAction(actionId) != null)
162            {
163                firstParentStepId = String.valueOf(otherStep.getId());
164            }
165            i++;
166        } while (firstParentStepId.isEmpty() && i < steps.size());
167        return firstParentStepId.isBlank() ? WorkflowStepDAO.INITIAL_STEP_ID : firstParentStepId;
168    }
169
170    /**
171     * Get a set of incoming actions if present in actions list
172     * @param stepId id of current step
173     * @param actions list of other step's actions
174     * @return a list containing other step's outgoing actions that are incoming to current step
175     */
176    protected Set<ActionDescriptor> _getIncomingActionsFromList(int stepId, List<ActionDescriptor> actions)
177    {
178        Set<ActionDescriptor> incoming = new HashSet<>();
179        for (ActionDescriptor action : actions)
180        {
181            ResultDescriptor unconditionalResult = action.getUnconditionalResult();
182            if (unconditionalResult.getStep() == stepId)
183            {
184                incoming.add(action);
185            }
186            else
187            {
188                boolean leadToStep = false;
189                List<ResultDescriptor> conditionalResults = action.getConditionalResults();
190                int indexResult = 0;
191                while (!leadToStep && indexResult < conditionalResults.size())
192                {
193                    if (conditionalResults.get(indexResult).getStep() == stepId)
194                    {
195                        incoming.add(action);
196                        leadToStep = true;
197                    }
198                    indexResult++;
199                }
200            }
201        } 
202        return incoming;
203    }
204}