Coverage Report - org.kuali.rice.edl.framework.workflow.EDocLitePostProcessor
 
Classes in this File Line Coverage Branch Coverage Complexity
EDocLitePostProcessor
0%
0/144
0%
0/28
3.769
EDocLitePostProcessor$1
0%
0/8
N/A
3.769
EDocLitePostProcessor$2
0%
0/3
N/A
3.769
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.edl.framework.workflow;
 17  
 
 18  
 import org.apache.log4j.Logger;
 19  
 import org.kuali.rice.core.api.util.xml.XmlException;
 20  
 import org.kuali.rice.core.api.util.xml.XmlJotter;
 21  
 import org.kuali.rice.kew.api.KewApiServiceLocator;
 22  
 import org.kuali.rice.kew.api.document.DocumentContent;
 23  
 import org.kuali.rice.kew.framework.postprocessor.ActionTakenEvent;
 24  
 import org.kuali.rice.kew.framework.postprocessor.DeleteEvent;
 25  
 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
 26  
 import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
 27  
 import org.kuali.rice.kew.framework.postprocessor.ProcessDocReport;
 28  
 import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
 29  
 import org.w3c.dom.Document;
 30  
 import org.w3c.dom.Element;
 31  
 import org.xml.sax.InputSource;
 32  
 
 33  
 import javax.xml.parsers.DocumentBuilder;
 34  
 import javax.xml.parsers.DocumentBuilderFactory;
 35  
 import javax.xml.xpath.XPath;
 36  
 import javax.xml.xpath.XPathConstants;
 37  
 import javax.xml.xpath.XPathExpressionException;
 38  
 import javax.xml.xpath.XPathFactory;
 39  
 import java.io.ByteArrayOutputStream;
 40  
 import java.io.IOException;
 41  
 import java.io.InputStream;
 42  
 import java.io.InterruptedIOException;
 43  
 import java.io.OutputStream;
 44  
 import java.io.StringReader;
 45  
 import java.lang.reflect.Method;
 46  
 import java.net.Socket;
 47  
 import java.net.URL;
 48  
 import java.rmi.RemoteException;
 49  
 import java.util.Timer;
 50  
 import java.util.TimerTask;
 51  
 
 52  
 
 53  
 /**
 54  
  * PostProcessor responsible for posting events to a url defined in the EDL doc definition.
 55  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 56  
  */
 57  0
 public class EDocLitePostProcessor extends DefaultPostProcessor {
 58  0
     private static final Logger LOG = Logger.getLogger(EDocLitePostProcessor.class);
 59  0
     private static final Timer TIMER = new Timer();
 60  
     public static final int SUBMIT_URL_MILLISECONDS_WAIT = 60000;
 61  
     public static final String EVENT_TYPE_ACTION_TAKEN = "actionTaken";
 62  
     public static final String EVENT_TYPE_DELETE_ROUTE_HEADER = "deleteRouteHeader";
 63  
     public static final String EVENT_TYPE_ROUTE_LEVEL_CHANGE = "routeLevelChange";
 64  
     public static final String EVENT_TYPE_ROUTE_STATUS_CHANGE = "statusChange";
 65  
 
 66  
     private static String getURL(Document edlDoc) throws XPathExpressionException {
 67  0
         XPath xpath = XPathFactory.newInstance().newXPath();
 68  0
         return (String) xpath.evaluate("//edlContent/edl/eventNotificationURL", edlDoc, XPathConstants.STRING);
 69  
     }
 70  
 
 71  
     /**
 72  
      * @param urlstring
 73  
      * @param eventDoc
 74  
      */
 75  
     private static void submitURL(String urlstring, Document eventDoc) throws IOException {
 76  
         String content;
 77  
         try {
 78  0
             content = XmlJotter.jotNode(eventDoc, true);
 79  0
         } catch (XmlException te) {
 80  0
             LOG.error("Error writing serializing event doc: " + eventDoc);
 81  0
             throw te;
 82  0
         }
 83  0
         byte[] contentBytes = content.getBytes("UTF-8");
 84  
 
 85  0
         LOG.debug("submitURL: " + urlstring);
 86  0
         URL url = new URL(urlstring);
 87  
 
 88  0
         String message = "POST " + url.getFile() + " HTTP/1.0\r\n" +
 89  
                          "Content-Length: " + contentBytes.length + "\r\n" +
 90  
                          "Cache-Control: no-cache\r\n" +
 91  
                          "Pragma: no-cache\r\n" +
 92  
                          "User-Agent: Java/1.4.2; EDocLitePostProcessor\r\n" +
 93  
                          "Host: " + url.getHost() + "\r\n" +
 94  
                          "Connection: close\r\n" +
 95  
                          "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
 96  
                          content;
 97  
 
 98  0
         byte[] buf = message.getBytes("UTF-8");
 99  0
         Socket s = new Socket(url.getHost(), url.getPort());
 100  
 
 101  
         /*URLConnection con = url.openConnection();
 102  
         LOG.debug("got connection: " + con);
 103  
         con.setDoOutput(true);
 104  
         con.setDoInput(true);
 105  
         LOG.debug("setDoOutput(true)");
 106  
 
 107  
         con.setRequestProperty("Connection", "close");
 108  
         con.setRequestProperty("Content-Length", String.valueOf(buf.length));*/
 109  
 
 110  0
         OutputStream os = s.getOutputStream();
 111  
         try {
 112  
             try {
 113  0
                 os.write(buf, 0, buf.length);
 114  0
                 os.flush();
 115  0
             } catch (InterruptedIOException ioe) {
 116  0
                 LOG.error("IO was interrupted while posting event to url " + urlstring + ": " + ioe.getMessage());
 117  0
             } catch (IOException ioe) {
 118  0
                 LOG.error("Error posting EDocLite content to url " + urlstring + ioe.getMessage());
 119  
             } finally {
 120  0
                 try {
 121  0
                     LOG.debug("Shutting down output stream");
 122  0
                     s.shutdownOutput();
 123  0
                 } catch (IOException ioe) {
 124  0
                     LOG.error("Error shutting down output stream for url " + urlstring + ": " + ioe.getMessage());
 125  0
                 }
 126  0
             }
 127  
 
 128  0
             InputStream is = s.getInputStream();
 129  
             try {
 130  
 
 131  0
                 buf = new byte[1024];
 132  0
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 133  
                 // this is what actually forces the write on the URLConnection!
 134  0
                 int read = is.read(buf);
 135  0
                 if (read != -1) {
 136  0
                     baos.write(buf, 0, read);
 137  
                 }
 138  0
                 LOG.debug("EDocLite post processor response:\n" + new String(baos.toByteArray()));
 139  0
             } catch (InterruptedIOException ioe) {
 140  0
                 LOG.error("IO was interrupted while reading response from url " + urlstring + ": " + ioe.getMessage());
 141  0
             } catch (IOException ioe) {
 142  0
                 LOG.error("Error reading response from EDocLite handler url " + urlstring + ioe.getMessage());
 143  
             } finally {
 144  0
                 try {
 145  0
                     LOG.debug("Shutting down input stream");
 146  0
                     s.shutdownInput();
 147  0
                 } catch (IOException ioe) {
 148  0
                     LOG.error("Error shutting down input stream for url " + urlstring + ": " + ioe.getMessage());
 149  0
                 }
 150  0
             }
 151  
         } finally {
 152  0
             try {
 153  0
                 s.close();
 154  0
             } catch (IOException ioe) {
 155  0
                 LOG.error("Error closing socket", ioe);
 156  0
             }
 157  0
         }
 158  0
     }
 159  
 
 160  
     protected static void postEvent(String docId, Object event, String eventName) {
 161  
             try {
 162  0
                     Document doc = getEDLContent(docId);
 163  0
                     if(LOG.isDebugEnabled()){
 164  0
                             LOG.debug("Submitting doc: " + XmlJotter.jotNode(doc));
 165  
                     }
 166  
 
 167  0
                     String urlstring = getURL(doc);
 168  0
                     if (org.apache.commons.lang.StringUtils.isEmpty(urlstring)) {
 169  0
                             LOG.warn("No eventNotificationURL defined in EDLContent");
 170  0
                             return;
 171  
                     }
 172  
 
 173  0
                     Document eventDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
 174  0
                     Element eventE = eventDoc.createElement("event");
 175  0
                     eventE.setAttribute("type", eventName);
 176  0
                     eventDoc.appendChild(eventE);
 177  
 
 178  0
                     Element infoE = (Element) eventDoc.importNode(propertiesToXml(event, "info"), true);
 179  0
                     Element docIdE = eventDoc.createElement("docId");
 180  0
                     docIdE.appendChild(eventDoc.createTextNode(String.valueOf(docId)));
 181  0
                     infoE.appendChild(docIdE);
 182  
 
 183  0
                     eventE.appendChild(infoE);
 184  0
                     eventE.appendChild(eventDoc.importNode(doc.getDocumentElement(), true));
 185  
 
 186  0
                     String query = "docId=" + docId;
 187  0
                     if (urlstring.indexOf('?') != -1) {
 188  0
                             urlstring += "&" + query;
 189  
                     } else {
 190  0
                             urlstring += "?" + query;
 191  
                     }
 192  
 
 193  0
                     final String _urlstring = urlstring;
 194  0
                     final Document _eventDoc = eventDoc;
 195  
                     // a super cheesy way to enforce asynchronicity/timeout follows:
 196  0
                     final Thread t = new Thread(new Runnable() {
 197  
                             public void run() {
 198  
                                     try {
 199  0
                                             LOG.debug("Post Event calling url: " + _urlstring);
 200  0
                                             submitURL(_urlstring, _eventDoc);
 201  0
                                             LOG.debug("Post Event done calling url: " + _urlstring);
 202  0
                                     } catch (Exception e) {
 203  0
                                             LOG.error(e);
 204  0
                                     }
 205  0
                             }
 206  
                     });
 207  0
                     t.setDaemon(true);
 208  0
                     t.start();
 209  
 
 210  
                     // kill the submission thread if it hasn't completed after 1 minute
 211  0
                     TIMER.schedule(new TimerTask() {
 212  
                             public void run() {
 213  0
                                     t.interrupt();
 214  0
                             }
 215  
                     }, SUBMIT_URL_MILLISECONDS_WAIT);
 216  0
             } catch (Exception e) {
 217  0
                     if (e instanceof RuntimeException) {
 218  0
                             throw (RuntimeException)e;
 219  
                     }
 220  0
                     throw new RuntimeException(e);
 221  0
             }
 222  0
     }
 223  
 
 224  
     public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange event) throws RemoteException {
 225  0
         LOG.debug("doRouteStatusChange: " + event);
 226  0
         postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_STATUS_CHANGE);
 227  0
         return new ProcessDocReport(true, "");
 228  
     }
 229  
 
 230  
     public ProcessDocReport doActionTaken(ActionTakenEvent event) throws RemoteException {
 231  0
         LOG.debug("doActionTaken: " + event);
 232  0
         postEvent(event.getDocumentId(), event, EVENT_TYPE_ACTION_TAKEN);
 233  0
         return new ProcessDocReport(true, "");
 234  
     }
 235  
 
 236  
     public ProcessDocReport doDeleteRouteHeader(DeleteEvent event) throws RemoteException {
 237  0
         LOG.debug("doDeleteRouteHeader: " + event);
 238  0
         postEvent(event.getDocumentId(), event, EVENT_TYPE_DELETE_ROUTE_HEADER);
 239  0
         return new ProcessDocReport(true, "");
 240  
     }
 241  
 
 242  
     public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange event) throws RemoteException {
 243  0
         LOG.debug("doRouteLevelChange: " + event);
 244  0
         postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_LEVEL_CHANGE);
 245  0
         return new ProcessDocReport(true, "");
 246  
     }
 247  
 
 248  
     public static Document getEDLContent(String documentId) {
 249  
             try {
 250  0
                     DocumentContent documentContent = KewApiServiceLocator.getWorkflowDocumentService().getDocumentContent(documentId);
 251  0
                     String content = documentContent.getFullContent();
 252  0
                     Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(content)));
 253  0
                     return doc;
 254  0
             } catch (Exception e) {
 255  0
                     if (e instanceof RuntimeException) {
 256  0
                             throw (RuntimeException)e;
 257  
                     }
 258  0
                     throw new RuntimeException(e);
 259  
             }
 260  
     }
 261  
 
 262  
     public static DocumentBuilder getDocumentBuilder() throws Exception {
 263  0
         return DocumentBuilderFactory.newInstance().newDocumentBuilder();
 264  
     }
 265  
 
 266  
     private static String lowerCaseFirstChar(String s) {
 267  0
         if (s.length() == 0 || Character.isLowerCase(s.charAt(0)))
 268  0
             return s;
 269  0
         StringBuffer sb = new StringBuffer(s.length());
 270  0
         sb.append(Character.toLowerCase(s.charAt(0)));
 271  0
         if (s.length() > 1) {
 272  0
             sb.append(s.substring(1));
 273  
         }
 274  0
         return sb.toString();
 275  
     }
 276  
 
 277  
     public static Element propertiesToXml(Object o, String elementName) throws Exception {
 278  0
         Class c = o.getClass();
 279  0
         Document doc = getDocumentBuilder().newDocument();
 280  0
         Element wrapper = doc.createElement(elementName);
 281  0
         Method[] methods = c.getMethods();
 282  0
         for (int i = 0; i < methods.length; i++) {
 283  0
             String name = methods[i].getName();
 284  0
             if ("getClass".equals(name))
 285  0
                 continue;
 286  0
             if (!name.startsWith("get") || methods[i].getParameterTypes().length > 0)
 287  0
                 continue;
 288  0
             name = name.substring("get".length());
 289  0
             name = lowerCaseFirstChar(name);
 290  0
             String value = null;
 291  
             try {
 292  0
                 Object result = methods[i].invoke(o, null);
 293  0
                 if (result == null) {
 294  0
                     LOG.debug("value of " + name + " method on object " + o.getClass() + " is null");
 295  0
                     value = "";
 296  
                 } else {
 297  0
                     value = result.toString();
 298  
                 }
 299  0
                 Element fieldE = doc.createElement(name);
 300  0
                 fieldE.appendChild(doc.createTextNode(value));
 301  0
                 wrapper.appendChild(fieldE);
 302  0
             } catch (RuntimeException e) {
 303  0
                 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
 304  0
                 throw e;
 305  0
             } catch (Exception e) {
 306  0
                 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
 307  0
             }
 308  
         }
 309  0
         return wrapper;
 310  
     }
 311  
 }