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(" ", " "); 249 } 250}