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 String getMimeType()
093    {
094        return "image/svg+xml";
095    }
096    
097    @Override
098    public void generate() throws IOException, SAXException, ProcessingException
099    {
100        try
101        {
102            Request request = ObjectModelHelper.getRequest(objectModel);
103            String workflowName = (String) request.get("workflowName");
104            _setContextInRequestAttributes(request);
105            WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
106            if (_workflowRightHelper.canRead(workflowDescriptor))
107            {
108                _setPlantUMLProperties();
109                String content = _getPlantUMLContent(request, workflowDescriptor);
110                SourceStringReader reader = new SourceStringReader(content);
111                
112                try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream())
113                {
114                    reader.outputImage(byteArrayOutputStream, new FileFormatOption(FileFormat.SVG).withSvgLinkTarget("_self"));
115                    
116                    String svgContent = byteArrayOutputStream.toString(StandardCharsets.UTF_8);
117                    svgContent = svgContent.replaceAll("<svg ", "<svg onclick=\"parent.Ametys.tool.ToolsManager.getTool('uitool-workflow-preview').focus();\" ");
118                    
119                    IOUtils.write(svgContent, out, StandardCharsets.UTF_8);
120                }
121            }
122        }
123        finally
124        {
125            out.close();
126        }
127    }
128    
129    /**
130     * Set all context parameters separatly in request
131     * @param request the request
132     */
133    protected void _setContextInRequestAttributes(Request request)
134    {
135        Map<String, Object> contextAsMap = _jsonUtils.convertJsonToMap(request.getParameter("context.parameters"));
136        if (contextAsMap != null)
137        {
138            for (String name : contextAsMap.keySet())
139            {
140                request.setAttribute(name, contextAsMap.get(name));
141            }
142        }
143    }
144
145    /**
146     * Set common plantUML svg properties
147     */
148    protected void _setPlantUMLProperties()
149    {
150        System.setProperty("PLANTUML_ALLOW_JAVASCRIPT_IN_LINK", "true");
151        System.setProperty("PLANTUML_LIMIT_SIZE", String.valueOf(Integer.MAX_VALUE));
152    }
153
154    /**
155     * Get the PlantUML SVG content to read
156     * @param request the request
157     * @param workflowDescriptor descriptor of current workflow
158     * @return The content to read
159     */
160    protected String _getPlantUMLContent(Request request, WorkflowDescriptor workflowDescriptor)
161    {
162        StringBuilder content = new StringBuilder("@start" + _getPlantUMLType() + "\n");
163        content.append(_getPlantUMLStyle());
164        content.append(_getPlantUMLGraphContent(request, workflowDescriptor));
165        content.append("@end" + _getPlantUMLType());
166        return content.toString();
167    }
168    
169    /**
170     * Get the diagram type
171     * @return the diagram type
172     */
173    protected abstract String _getPlantUMLType();
174
175    /**
176     * Get plantUML style for current diagram
177     * @return the style as string
178     */
179    protected abstract String _getPlantUMLStyle();
180
181    /**
182     * Get the plantUML diagram body
183     * @param request the request
184     * @param workflowDescriptor descriptor of current workflow
185     * @return the diagram body as string
186     */
187    protected abstract String _getPlantUMLGraphContent(Request request, WorkflowDescriptor workflowDescriptor);
188
189    /**
190     * Get js function to send selection
191     * @param workflowName unique name of current workflow
192     * @param stepId current or incoming step's id
193     * @param stepLabel current or incoming step's label
194     * @param actionId current action's id, can be null
195     * @param actionLabel current action's label, can be null
196     * @return the js function
197     */
198    protected String _getJsFunction(String workflowName, String stepId, String stepLabel, String actionId, String actionLabel)
199    {
200        String jsParameters = "'" + workflowName + "','" + stepId + "','" + stepLabel + "','";
201        jsParameters += actionId != null ? actionId + "','" + actionLabel + "'" : "'";
202        return "Ametys.plugins.workflow.UI.UIWorkflowGraph.sendSelection(" + jsParameters + ")";
203    }
204    
205    /**
206     * Get the node label for step
207     * @param workflowDescriptor current workflow
208     * @param stepId current step id
209     * @return the node label which is the translated step name and its id
210     */
211    protected String _getStepNodeLabel(WorkflowDescriptor workflowDescriptor, int stepId)
212    {
213        return _workflowStepDAO.getStepLabelAsString(workflowDescriptor, stepId, true);
214    }
215
216    /**
217     * Get the tooltip for a link on step
218     * @param step current step
219     * @return the tooltip
220     */
221    protected String _getStepTooltip(StepDescriptor step)
222    {
223        return _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_STEP"));
224    }
225
226    /**
227     * Get the action label for node
228     * @param workflowName the workflow's unique name
229     * @param action current action
230     * @return the label as action's name and id
231     */
232    protected String _getActionLabel(String workflowName, ActionDescriptor action)
233    {
234        return _workflowTransitionDAO.getActionLabel(workflowName, action) + " (" + action.getId() + ")";
235    }
236    
237    /**
238     * Get the tooltip for a link on action
239     * @param action current action
240     * @return the tooltip
241     */
242    protected String _getActionTooltip(ActionDescriptor action)
243    {
244        return _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_LINK_SEE_TRANSITION"));
245    }
246    
247    /**
248     * Get the string with escaped spaces
249     * @param stringToEscape a string to process for js function
250     * @return the formated string
251     */
252    protected String _getStringWithEscapedSpace(String stringToEscape)
253    {
254        return stringToEscape.replaceAll(" ", "&#160;");
255    }
256}