package org.jboss.processFlow.rest; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.activation.DataHandler; import javax.activation.DataSource; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.jboss.processFlow.tasks.ITaskService; import org.jboss.resteasy.plugins.providers.multipart.InputPart; import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; import org.jbpm.integration.console.shared.GuvnorConnectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; @Stateless @Path("/form") public class FormProcessingFacade { private static final Logger log = LoggerFactory.getLogger(FormProcessingFacade.class); @EJB(lookup="java:global/processFlow-taskService/taskProxy!org.jboss.processFlow.tasks.ITaskService") ITaskService taskServiceProxy; @GET @Path("task/{id}/render") @Produces({ "text/html" }) public Response renderTaskUI(@PathParam("id") String taskId) { return provideForm(new FormAuthorityRef(taskId)); } @POST @Path("task/{id}/{userId}/complete") @Produces({ "text/html" }) @Consumes({ "multipart/form-data" }) public Response closeTaskWithUI(@Context HttpServletRequest request, @PathParam("id") String taskId, @PathParam("userId") String userId, MultipartFormDataInput payload) { FieldMapping mapping = createFieldMapping(payload); String outcomeDirective = (String) mapping.directives.get("outcome"); if (outcomeDirective != null) { completeTask(Long.valueOf(taskId).longValue(), outcomeDirective, mapping.processVars, userId); } else { completeTask(Long.valueOf(taskId).longValue(), mapping.processVars, userId); } return Response.ok("<div style='font-family:sans-serif; padding:10px;'><h3>Successfully processed input</h3><p/>You can now close this window.</div>") .build(); } @SuppressWarnings("unchecked") public void completeTask(long taskId, Map data, String userId) { try { taskServiceProxy.completeTask(taskId, data, userId); } catch (Exception x) { throw new RuntimeException(x); } } @SuppressWarnings("unchecked") public void completeTask(long taskId, String outcome, Map data, String userId) { if ("jbpm_skip_task".equalsIgnoreCase(outcome)) { taskServiceProxy.skipTask(taskId, userId, null); } else { data.put("outcome", outcome); completeTask(taskId, data, userId); } } private FieldMapping createFieldMapping(MultipartFormDataInput payload) { FieldMapping mapping = new FieldMapping(); Map formData = payload.getFormData(); Iterator partNames = formData.keySet().iterator(); while (partNames.hasNext()) { final String partName = (String) partNames.next(); InputPart part = (InputPart) formData.get(partName); final MediaType mediaType = part.getMediaType(); String mType = mediaType.getType(); String mSubtype = mediaType.getSubtype(); try { if (("text".equals(mType)) && ("plain".equals(mSubtype))) { if (mapping.isReserved(partName)) mapping.directives.put(partName, part.getBodyAsString()); else { mapping.processVars.put(partName, part.getBodyAsString()); } } else { final byte[] data = part.getBodyAsString().getBytes(); DataHandler dh = new DataHandler(new DataSource() { public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(data); } public OutputStream getOutputStream() throws IOException { throw new RuntimeException("This is a readonly DataHandler"); } public String getContentType() { return mediaType.getType(); } public String getName() { return partName; } }); mapping.processVars.put(partName, dh); } } catch (IOException e) { throw new RuntimeException(e); } } return mapping; } private Response provideForm(FormAuthorityRef authorityRef) { DataHandler dh = provideFormDataHandler(authorityRef); if (null == dh) { throw new RuntimeException("No UI associated with " + authorityRef.getType() + " " + authorityRef.getReferenceId()); } return Response.ok(dh.getDataSource()).type("text/html").build(); } private DataHandler provideFormDataHandler(FormAuthorityRef ref) { String englishTaskName = taskServiceProxy.getTaskName(new Long(ref.getReferenceId()), "en-UK"); InputStream template = getTemplate(englishTaskName); if (template == null) throw new RuntimeException("provideForm() unable to locate FreeMarker Template with name = "+englishTaskName); // get processInstance --> task variable Map Map<?,?> documentContentMap = taskServiceProxy.getTaskContent(new Long(ref.getReferenceId()), true); Map<String, Object> renderContext = null; //renderContext.put("content", documentContentMap); if(documentContentMap.size() != 0) { renderContext = new HashMap<String, Object>(); for (Map.Entry<?, ?> entry: documentContentMap.entrySet()) { if (entry.getKey() instanceof String) { renderContext.put((String) entry.getKey(), entry.getValue()); } else { log.warn("provideForm() processInstance --> task variables includes a non-String variable .. will skip : "+entry.getKey()+" : "+entry.getValue()); } } } else { log.warn("provideForm() processInstance --> task variables map is empty and will be passed a null rootMap !!!"); } // merge template with process variables try { return processTemplate(englishTaskName, template, renderContext); } catch(RuntimeException x) { log.error("provideForm() the following are the contents of the processInstance --> task variables Map being used to create a FreeMarker template :"); if(renderContext != null && renderContext.size() > 0) { for(Map.Entry<String, Object> entry : renderContext.entrySet()) { log.error("\tkey = "+entry.getKey()+" : value = "+entry.getValue()); } } else { log.error("provideForm() processInstance --> task variables map is empty and was passed a null rootMap !!!"); } throw x; } } private InputStream getTemplate(String name) { // try to find on classpath InputStream nameTaskformResult = FormProcessingFacade.class.getResourceAsStream("/" + name + "-taskform.ftl"); if (nameTaskformResult != null) { return nameTaskformResult; } else { InputStream nameResult = FormProcessingFacade.class.getResourceAsStream("/" + name + ".ftl"); if (nameResult != null) { return nameResult; } } // try to find in guvnor repository GuvnorConnectionUtils guvnorUtils = new GuvnorConnectionUtils(); if(guvnorUtils.guvnorExists()) { try { String templateName; if(guvnorUtils.templateExistsInRepo(name + "-taskform")) { templateName = name + "-taskform"; } else if(guvnorUtils.templateExistsInRepo(name)) { templateName = name; } else { return null; } return guvnorUtils.getFormTemplateFromGuvnor(templateName); } catch (Throwable t) { log.error("Could not load process template from Guvnor: " + t.getMessage()); return null; } } else { log.warn("Could not connect to Guvnor."); return null; } } private DataHandler processTemplate(final String name, InputStream src, Map<String, Object> renderContext) { DataHandler merged = null; try { freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE); freemarker.template.Configuration cfg = new freemarker.template.Configuration(); cfg.setObjectWrapper(new DefaultObjectWrapper()); cfg.setTemplateUpdateDelay(0); Template temp = new Template(name, new InputStreamReader(src), cfg); final ByteArrayOutputStream bout = new ByteArrayOutputStream(); Writer out = new OutputStreamWriter(bout); temp.process(renderContext, out); out.flush(); merged = new DataHandler(new DataSource() { public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(bout.toByteArray()); } public OutputStream getOutputStream() throws IOException { return bout; } public String getContentType() { return "*/*"; } public String getName() { return name + "_DataSource"; } }); } catch (Exception e) { throw new RuntimeException("Failed to process form template", e); } return merged; } private class FieldMapping { final String[] reservedNames = { "outcome", "form" }; Map<String, Object> processVars = new HashMap<String, Object>(); Map<String, String> directives = new HashMap<String, String>(); private FieldMapping() { } public boolean isReserved(String name) { boolean result = false; for (String s : this.reservedNames) { if (s.equals(name)) { result = true; break; } } return result; } } }