Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.edl.framework.workflow; import org.apache.log4j.Logger; import org.kuali.rice.core.api.util.xml.XmlException; import org.kuali.rice.core.api.util.xml.XmlJotter; import org.kuali.rice.kew.api.KewApiServiceLocator; import org.kuali.rice.kew.api.document.DocumentContent; import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent; import org.kuali.rice.kew.framework.postprocessor.DeleteEvent; import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange; import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport; import org.kuali.rice.kew.postprocessor.DefaultPostProcessor; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.StringReader; import java.lang.reflect.Method; import java.net.Socket; import java.net.URL; import java.rmi.RemoteException; import java.util.Timer; import java.util.TimerTask; /** * PostProcessor responsible for posting events to a url defined in the EDL doc definition. * @author Kuali Rice Team (rice.collab@kuali.org) */ public class EDocLitePostProcessor extends DefaultPostProcessor { private static final Logger LOG = Logger.getLogger(EDocLitePostProcessor.class); private static final Timer TIMER = new Timer(); public static final int SUBMIT_URL_MILLISECONDS_WAIT = 60000; public static final String EVENT_TYPE_ACTION_TAKEN = "actionTaken"; public static final String EVENT_TYPE_DELETE_ROUTE_HEADER = "deleteRouteHeader"; public static final String EVENT_TYPE_ROUTE_LEVEL_CHANGE = "routeLevelChange"; public static final String EVENT_TYPE_ROUTE_STATUS_CHANGE = "statusChange"; private static String getURL(Document edlDoc) throws XPathExpressionException { XPath xpath = XPathFactory.newInstance().newXPath(); return (String) xpath.evaluate("//edlContent/edl/eventNotificationURL", edlDoc, XPathConstants.STRING); } /** * @param urlstring * @param eventDoc */ private static void submitURL(String urlstring, Document eventDoc) throws IOException { String content; try { content = XmlJotter.jotNode(eventDoc, true); } catch (XmlException te) { LOG.error("Error writing serializing event doc: " + eventDoc); throw te; } byte[] contentBytes = content.getBytes("UTF-8"); LOG.debug("submitURL: " + urlstring); URL url = new URL(urlstring); String message = "POST " + url.getFile() + " HTTP/1.0\r\n" + "Content-Length: " + contentBytes.length + "\r\n" + "Cache-Control: no-cache\r\n" + "Pragma: no-cache\r\n" + "User-Agent: Java/1.4.2; EDocLitePostProcessor\r\n" + "Host: " + url.getHost() + "\r\n" + "Connection: close\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n\r\n" + content; byte[] buf = message.getBytes("UTF-8"); Socket s = new Socket(url.getHost(), url.getPort()); /*URLConnection con = url.openConnection(); LOG.debug("got connection: " + con); con.setDoOutput(true); con.setDoInput(true); LOG.debug("setDoOutput(true)"); con.setRequestProperty("Connection", "close"); con.setRequestProperty("Content-Length", String.valueOf(buf.length));*/ OutputStream os = s.getOutputStream(); try { try { os.write(buf, 0, buf.length); os.flush(); } catch (InterruptedIOException ioe) { LOG.error("IO was interrupted while posting event to url " + urlstring + ": " + ioe.getMessage()); } catch (IOException ioe) { LOG.error("Error posting EDocLite content to url " + urlstring + ioe.getMessage()); } finally { try { LOG.debug("Shutting down output stream"); s.shutdownOutput(); } catch (IOException ioe) { LOG.error("Error shutting down output stream for url " + urlstring + ": " + ioe.getMessage()); } } InputStream is = s.getInputStream(); try { buf = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // this is what actually forces the write on the URLConnection! int read = is.read(buf); if (read != -1) { baos.write(buf, 0, read); } LOG.debug("EDocLite post processor response:\n" + new String(baos.toByteArray())); } catch (InterruptedIOException ioe) { LOG.error("IO was interrupted while reading response from url " + urlstring + ": " + ioe.getMessage()); } catch (IOException ioe) { LOG.error("Error reading response from EDocLite handler url " + urlstring + ioe.getMessage()); } finally { try { LOG.debug("Shutting down input stream"); s.shutdownInput(); } catch (IOException ioe) { LOG.error("Error shutting down input stream for url " + urlstring + ": " + ioe.getMessage()); } } } finally { try { s.close(); } catch (IOException ioe) { LOG.error("Error closing socket", ioe); } } } protected static void postEvent(String docId, Object event, String eventName) { try { Document doc = getEDLContent(docId); if (LOG.isDebugEnabled()) { LOG.debug("Submitting doc: " + XmlJotter.jotNode(doc)); } String urlstring = getURL(doc); if (org.apache.commons.lang.StringUtils.isEmpty(urlstring)) { LOG.warn("No eventNotificationURL defined in EDLContent"); return; } Document eventDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element eventE = eventDoc.createElement("event"); eventE.setAttribute("type", eventName); eventDoc.appendChild(eventE); Element infoE = (Element) eventDoc.importNode(propertiesToXml(event, "info"), true); Element docIdE = eventDoc.createElement("docId"); docIdE.appendChild(eventDoc.createTextNode(String.valueOf(docId))); infoE.appendChild(docIdE); eventE.appendChild(infoE); eventE.appendChild(eventDoc.importNode(doc.getDocumentElement(), true)); String query = "docId=" + docId; if (urlstring.indexOf('?') != -1) { urlstring += "&" + query; } else { urlstring += "?" + query; } final String _urlstring = urlstring; final Document _eventDoc = eventDoc; // a super cheesy way to enforce asynchronicity/timeout follows: final Thread t = new Thread(new Runnable() { public void run() { try { LOG.debug("Post Event calling url: " + _urlstring); submitURL(_urlstring, _eventDoc); LOG.debug("Post Event done calling url: " + _urlstring); } catch (Exception e) { LOG.error(e); } } }); t.setDaemon(true); t.start(); // kill the submission thread if it hasn't completed after 1 minute TIMER.schedule(new TimerTask() { public void run() { t.interrupt(); } }, SUBMIT_URL_MILLISECONDS_WAIT); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } } public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange event) throws RemoteException { LOG.debug("doRouteStatusChange: " + event); postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_STATUS_CHANGE); return new ProcessDocReport(true, ""); } public ProcessDocReport doActionTaken(ActionTakenEvent event) throws RemoteException { LOG.debug("doActionTaken: " + event); postEvent(event.getDocumentId(), event, EVENT_TYPE_ACTION_TAKEN); return new ProcessDocReport(true, ""); } public ProcessDocReport doDeleteRouteHeader(DeleteEvent event) throws RemoteException { LOG.debug("doDeleteRouteHeader: " + event); postEvent(event.getDocumentId(), event, EVENT_TYPE_DELETE_ROUTE_HEADER); return new ProcessDocReport(true, ""); } public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange event) throws RemoteException { LOG.debug("doRouteLevelChange: " + event); postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_LEVEL_CHANGE); return new ProcessDocReport(true, ""); } public static Document getEDLContent(String documentId) { try { DocumentContent documentContent = KewApiServiceLocator.getWorkflowDocumentService() .getDocumentContent(documentId); String content = documentContent.getFullContent(); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() .parse(new InputSource(new StringReader(content))); return doc; } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } } public static DocumentBuilder getDocumentBuilder() throws Exception { return DocumentBuilderFactory.newInstance().newDocumentBuilder(); } private static String lowerCaseFirstChar(String s) { if (s.length() == 0 || Character.isLowerCase(s.charAt(0))) return s; StringBuffer sb = new StringBuffer(s.length()); sb.append(Character.toLowerCase(s.charAt(0))); if (s.length() > 1) { sb.append(s.substring(1)); } return sb.toString(); } public static Element propertiesToXml(Object o, String elementName) throws Exception { Class c = o.getClass(); Document doc = getDocumentBuilder().newDocument(); Element wrapper = doc.createElement(elementName); Method[] methods = c.getMethods(); for (int i = 0; i < methods.length; i++) { String name = methods[i].getName(); if ("getClass".equals(name)) continue; if (!name.startsWith("get") || methods[i].getParameterTypes().length > 0) continue; name = name.substring("get".length()); name = lowerCaseFirstChar(name); String value = null; try { Object result = methods[i].invoke(o, null); if (result == null) { LOG.debug("value of " + name + " method on object " + o.getClass() + " is null"); value = ""; } else { value = result.toString(); } Element fieldE = doc.createElement(name); fieldE.appendChild(doc.createTextNode(value)); wrapper.appendChild(fieldE); } catch (RuntimeException e) { LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c); throw e; } catch (Exception e) { LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c); } } return wrapper; } }