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