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(" ", " "); 255 } 256}