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.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.nio.charset.StandardCharsets;
021import java.util.Map;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026import org.apache.cocoon.ProcessingException;
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.environment.Request;
029import org.apache.cocoon.reading.AbstractReader;
030import org.apache.commons.io.IOUtils;
031import org.xml.sax.SAXException;
032
033import org.ametys.core.util.I18nUtils;
034import org.ametys.core.util.JSONUtils;
035import org.ametys.plugins.workflow.dao.WorkflowStepDAO;
036import org.ametys.plugins.workflow.dao.WorkflowTransitionDAO;
037import org.ametys.plugins.workflow.support.I18nHelper;
038import org.ametys.plugins.workflow.support.WorflowRightHelper;
039import org.ametys.plugins.workflow.support.WorkflowSessionHelper;
040import org.ametys.runtime.i18n.I18nizableText;
041
042import com.opensymphony.workflow.loader.ActionDescriptor;
043import com.opensymphony.workflow.loader.StepDescriptor;
044import com.opensymphony.workflow.loader.WorkflowDescriptor;
045
046import net.sourceforge.plantuml.FileFormat;
047import net.sourceforge.plantuml.FileFormatOption;
048import net.sourceforge.plantuml.SourceStringReader;
049
050/**
051 * Abstract class for reading PlantUML SVG
052 */
053public abstract class AbstractPlantUMLSVGReader extends AbstractReader implements Serviceable
054{
055    /** The color for step nodes */
056    protected static final String __MAIN_STEP_NODE_COLOR = "#e5f8ff";
057    
058    /** I18n Utils */
059    protected I18nUtils _i18nUtils;
060    
061    /** The i18n workflow helper */
062    protected I18nHelper _i18nHelper;
063    
064    /** The workflow session helper */
065    protected WorkflowSessionHelper _workflowSessionHelper;
066    
067    /** The workflow step DAO */
068    protected WorkflowStepDAO _workflowStepDAO;
069    
070    /** The workflow transition DAO */
071    protected WorkflowTransitionDAO _workflowTransitionDAO;
072    
073    /** The workflow right helper */
074    protected WorflowRightHelper _workflowRightHelper;
075    
076    /** The JSON Utils */
077    protected JSONUtils _jsonUtils;
078    
079    @Override
080    public void service(ServiceManager serviceManager) throws ServiceException
081    {
082        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
083        _i18nHelper = (I18nHelper) serviceManager.lookup(I18nHelper.ROLE);
084        _workflowStepDAO = (WorkflowStepDAO) serviceManager.lookup(WorkflowStepDAO.ROLE);
085        _workflowTransitionDAO = (WorkflowTransitionDAO) serviceManager.lookup(WorkflowTransitionDAO.ROLE);
086        _workflowSessionHelper = (WorkflowSessionHelper) serviceManager.lookup(WorkflowSessionHelper.ROLE);
087        _workflowRightHelper = (WorflowRightHelper) serviceManager.lookup(WorflowRightHelper.ROLE);
088        _jsonUtils = (JSONUtils) serviceManager.lookup(JSONUtils.ROLE);
089    }
090    
091    @Override
092    public void generate() throws IOException, SAXException, ProcessingException
093    {
094        try
095        {
096            Request request = ObjectModelHelper.getRequest(objectModel);
097            String workflowName = (String) request.get("workflowName");
098            _setContextInRequestAttributes(request);
099            WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
100            if (_workflowRightHelper.canRead(workflowDescriptor))
101            {
102                _setPlantUMLProperties();
103                String content = _getPlantUMLContent(request, workflowDescriptor);
104                SourceStringReader reader = new SourceStringReader(content);
105                
106                try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream())
107                {
108                    reader.outputImage(byteArrayOutputStream, new FileFormatOption(FileFormat.SVG).withSvgLinkTarget("_self"));
109                    
110                    String svgContent = byteArrayOutputStream.toString(StandardCharsets.UTF_8);
111                    svgContent = svgContent.replaceAll("<svg ", "<svg onclick=\"parent.Ametys.tool.ToolsManager.getTool('uitool-workflow-preview').focus();\" ");
112                    
113                    IOUtils.write(svgContent, out, StandardCharsets.UTF_8);
114                }
115            }
116        }
117        finally
118        {
119            out.close();
120        }
121    }
122    
123    /**
124     * Set all context parameters separatly in request
125     * @param request the request 
126     */
127    protected void _setContextInRequestAttributes(Request request)
128    {
129        Map<String, Object> contextAsMap = _jsonUtils.convertJsonToMap(request.getParameter("context.parameters"));
130        if (contextAsMap != null)
131        {
132            for (String name : contextAsMap.keySet())
133            {
134                request.setAttribute(name, contextAsMap.get(name));
135            }
136        }
137    }
138
139    /**
140     * Set common plantUML svg properties
141     */
142    protected void _setPlantUMLProperties()
143    {
144        System.setProperty("PLANTUML_ALLOW_JAVASCRIPT_IN_LINK", "true");
145        System.setProperty("PLANTUML_LIMIT_SIZE", String.valueOf(Integer.MAX_VALUE));
146    }
147
148    /**
149     * Get the PlantUML SVG content to read
150     * @param request the request 
151     * @param workflowDescriptor descriptor of current workflow
152     * @return The content to read
153     */
154    protected String _getPlantUMLContent(Request request, WorkflowDescriptor workflowDescriptor)
155    {
156        StringBuilder content = new StringBuilder("@start" + _getPlantUMLType() + "\n");
157        content.append(_getPlantUMLStyle());
158        content.append(_getPlantUMLGraphContent(request, workflowDescriptor));
159        content.append("@end" + _getPlantUMLType());
160        return content.toString();
161    }
162    
163    /**
164     * Get the diagram type
165     * @return the diagram type
166     */
167    protected abstract String _getPlantUMLType();
168
169    /**
170     * Get plantUML style for current diagram
171     * @return the style as string
172     */
173    protected abstract String _getPlantUMLStyle();
174
175    /**
176     * Get the plantUML diagram body
177     * @param request the request 
178     * @param workflowDescriptor descriptor of current workflow
179     * @return the diagram body as string
180     */
181    protected abstract String _getPlantUMLGraphContent(Request request, WorkflowDescriptor workflowDescriptor);
182
183    /**
184     * Get js function to send selection
185     * @param workflowName unique name of current workflow
186     * @param stepId current or incoming step's id 
187     * @param stepLabel current or incoming step's label
188     * @param actionId current action's id, can be null
189     * @param actionLabel current action's label, can be null
190     * @return the js function 
191     */
192    protected String _getJsFunction(String workflowName, String stepId, String stepLabel, String actionId, String actionLabel)
193    {
194        String jsParameters = "'" + workflowName + "','" + stepId + "','" + stepLabel + "','";
195        jsParameters += actionId != null ? actionId + "','" + actionLabel + "'" : "'";
196        return "Ametys.plugins.workflow.UI.UIWorkflowGraph.sendSelection(" + jsParameters + ")";
197    }
198    
199    /**
200     * Get the node label for step 
201     * @param workflowDescriptor current workflow
202     * @param stepId current step id
203     * @return the node label which is the translated step name and its id
204     */
205    protected String _getStepNodeLabel(WorkflowDescriptor workflowDescriptor, int stepId) 
206    {
207        return _workflowStepDAO.getStepLabelAsString(workflowDescriptor, stepId, true);
208    }
209
210    /**
211     * Get the tooltip for a link on step
212     * @param step current step
213     * @return the tooltip
214     */
215    protected String _getStepTooltip(StepDescriptor step)
216    {
217        return _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_STEP"));
218    }
219
220    /**
221     * Get the action label for node
222     * @param workflowName the workflow's unique name
223     * @param action current action
224     * @return the label as action's name and id 
225     */
226    protected String _getActionLabel(String workflowName, ActionDescriptor action)
227    {
228        return _workflowTransitionDAO.getActionLabel(workflowName, action) + " (" + action.getId() + ")";
229    }
230    
231    /**
232     * Get the tooltip for a link on action
233     * @param action current action
234     * @return the tooltip
235     */
236    protected String _getActionTooltip(ActionDescriptor action)
237    {
238        return _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_TRANSITION"));
239    }
240    
241    /**
242     * Get the string with escaped spaces 
243     * @param stringToEscape a string to process for js function
244     * @return the formated string
245     */
246    protected String _getStringWithEscapedSpace(String stringToEscape)
247    {
248        return stringToEscape.replaceAll(" ", "&#160;");
249    }
250}