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