1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.rice.kew.edl;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.InterruptedIOException;
23 import java.io.OutputStream;
24 import java.io.StringReader;
25 import java.lang.reflect.Method;
26 import java.net.Socket;
27 import java.net.URL;
28 import java.util.Timer;
29 import java.util.TimerTask;
30
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.transform.TransformerException;
34 import javax.xml.xpath.XPath;
35 import javax.xml.xpath.XPathConstants;
36 import javax.xml.xpath.XPathExpressionException;
37 import javax.xml.xpath.XPathFactory;
38
39 import org.apache.log4j.Logger;
40 import org.kuali.rice.kew.postprocessor.ActionTakenEvent;
41 import org.kuali.rice.kew.postprocessor.DefaultPostProcessor;
42 import org.kuali.rice.kew.postprocessor.DeleteEvent;
43 import org.kuali.rice.kew.postprocessor.DocumentRouteLevelChange;
44 import org.kuali.rice.kew.postprocessor.DocumentRouteStatusChange;
45 import org.kuali.rice.kew.postprocessor.ProcessDocReport;
46 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
47 import org.kuali.rice.kew.service.KEWServiceLocator;
48 import org.kuali.rice.kew.util.Utilities;
49 import org.kuali.rice.kew.util.XmlHelper;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.Element;
52 import org.xml.sax.InputSource;
53
54
55
56
57
58
59 public class EDocLitePostProcessor extends DefaultPostProcessor {
60 private static final Logger LOG = Logger.getLogger(EDocLitePostProcessor.class);
61 private static final Timer TIMER = new Timer();
62 public static final int SUBMIT_URL_MILLISECONDS_WAIT = 60000;
63 public static final String EVENT_TYPE_ACTION_TAKEN = "actionTaken";
64 public static final String EVENT_TYPE_DELETE_ROUTE_HEADER = "deleteRouteHeader";
65 public static final String EVENT_TYPE_ROUTE_LEVEL_CHANGE = "routeLevelChange";
66 public static final String EVENT_TYPE_ROUTE_STATUS_CHANGE = "statusChange";
67
68 private static String getURL(Document edlDoc) throws XPathExpressionException {
69 XPath xpath = XPathFactory.newInstance().newXPath();
70 return (String) xpath.evaluate("//edlContent/edl/eventNotificationURL", edlDoc, XPathConstants.STRING);
71 }
72
73
74
75
76
77 private static void submitURL(String urlstring, Document eventDoc) throws IOException, TransformerException {
78 String content;
79 try {
80 content = XmlHelper.writeNode(eventDoc, true);
81 } catch (TransformerException te) {
82 LOG.error("Error writing serializing event doc: " + eventDoc);
83 throw te;
84 }
85 byte[] contentBytes = content.getBytes("UTF-8");
86
87 LOG.debug("submitURL: " + urlstring);
88 URL url = new URL(urlstring);
89
90 String message = "POST " + url.getFile() + " HTTP/1.0\r\n" +
91 "Content-Length: " + contentBytes.length + "\r\n" +
92 "Cache-Control: no-cache\r\n" +
93 "Pragma: no-cache\r\n" +
94 "User-Agent: Java/1.4.2; EDocLitePostProcessor\r\n" +
95 "Host: " + url.getHost() + "\r\n" +
96 "Connection: close\r\n" +
97 "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
98 content;
99
100 byte[] buf = message.getBytes("UTF-8");
101 Socket s = new Socket(url.getHost(), url.getPort());
102
103
104
105
106
107
108
109
110
111
112 OutputStream os = s.getOutputStream();
113 try {
114 try {
115 os.write(buf, 0, buf.length);
116 os.flush();
117 } catch (InterruptedIOException ioe) {
118 LOG.error("IO was interrupted while posting event to url " + urlstring + ": " + ioe.getMessage());
119 } catch (IOException ioe) {
120 LOG.error("Error posting EDocLite content to url " + urlstring + ioe.getMessage());
121 } finally {
122 try {
123 LOG.debug("Shutting down output stream");
124 s.shutdownOutput();
125 } catch (IOException ioe) {
126 LOG.error("Error shutting down output stream for url " + urlstring + ": " + ioe.getMessage());
127 }
128 }
129
130 InputStream is = s.getInputStream();
131 try {
132
133 buf = new byte[1024];
134 ByteArrayOutputStream baos = new ByteArrayOutputStream();
135
136 int read = is.read(buf);
137 if (read != -1) {
138 baos.write(buf, 0, read);
139 }
140 LOG.debug("EDocLite post processor response:\n" + new String(baos.toByteArray()));
141 } catch (InterruptedIOException ioe) {
142 LOG.error("IO was interrupted while reading response from url " + urlstring + ": " + ioe.getMessage());
143 } catch (IOException ioe) {
144 LOG.error("Error reading response from EDocLite handler url " + urlstring + ioe.getMessage());
145 } finally {
146 try {
147 LOG.debug("Shutting down input stream");
148 s.shutdownInput();
149 } catch (IOException ioe) {
150 LOG.error("Error shutting down input stream for url " + urlstring + ": " + ioe.getMessage());
151 }
152 }
153 } finally {
154 try {
155 s.close();
156 } catch (IOException ioe) {
157 LOG.error("Error closing socket", ioe);
158 }
159 }
160 }
161
162 protected static void postEvent(Long docId, Object event, String eventName) throws Exception {
163 DocumentRouteHeaderValue val = KEWServiceLocator.getRouteHeaderService().getRouteHeader(docId);
164 Document doc = getEDLContent(val);
165 if(LOG.isDebugEnabled()){
166 LOG.debug("Submitting doc: " + XmlHelper.jotNode(doc));
167 }
168
169 String urlstring = getURL(doc);
170 if (Utilities.isEmpty(urlstring)) {
171 LOG.warn("No eventNotificationURL defined in EDLContent");
172 return;
173 }
174
175 Document eventDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
176 Element eventE = eventDoc.createElement("event");
177 eventE.setAttribute("type", eventName);
178 eventDoc.appendChild(eventE);
179
180 Element infoE = (Element) eventDoc.importNode(propertiesToXml(event, "info"), true);
181 Element docIdE = eventDoc.createElement("docId");
182 docIdE.appendChild(eventDoc.createTextNode(String.valueOf(docId)));
183 infoE.appendChild(docIdE);
184
185 eventE.appendChild(infoE);
186 eventE.appendChild(eventDoc.importNode(doc.getDocumentElement(), true));
187
188 String query = "docId=" + docId;
189 if (urlstring.indexOf('?') != -1) {
190 urlstring += "&" + query;
191 } else {
192 urlstring += "?" + query;
193 }
194
195 final String _urlstring = urlstring;
196 final Document _eventDoc = eventDoc;
197
198 final Thread t = new Thread(new Runnable() {
199 public void run() {
200 try {
201 LOG.debug("Post Event calling url: " + _urlstring);
202 submitURL(_urlstring, _eventDoc);
203 LOG.debug("Post Event done calling url: " + _urlstring);
204 } catch (Exception e) {
205 LOG.error(e);
206 }
207 }
208 });
209 t.setDaemon(true);
210 t.start();
211
212
213 TIMER.schedule(new TimerTask() {
214 public void run() {
215 t.interrupt();
216 }
217 }, SUBMIT_URL_MILLISECONDS_WAIT);
218 }
219
220 public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange event) throws Exception {
221 LOG.debug("doRouteStatusChange: " + event);
222 postEvent(event.getRouteHeaderId(), event, EVENT_TYPE_ROUTE_STATUS_CHANGE);
223 return super.doRouteStatusChange(event);
224 }
225
226 public ProcessDocReport doActionTaken(ActionTakenEvent event) throws Exception {
227 LOG.debug("doActionTaken: " + event);
228 postEvent(event.getRouteHeaderId(), event, EVENT_TYPE_ACTION_TAKEN);
229 return super.doActionTaken(event);
230 }
231
232 public ProcessDocReport doDeleteRouteHeader(DeleteEvent event) throws Exception {
233 LOG.debug("doDeleteRouteHeader: " + event);
234 postEvent(event.getRouteHeaderId(), event, EVENT_TYPE_DELETE_ROUTE_HEADER);
235 return super.doDeleteRouteHeader(event);
236 }
237
238 public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange event) throws Exception {
239 LOG.debug("doRouteLevelChange: " + event);
240 postEvent(event.getRouteHeaderId(), event, EVENT_TYPE_ROUTE_LEVEL_CHANGE);
241 return super.doRouteLevelChange(event);
242 }
243
244 public static Document getEDLContent(DocumentRouteHeaderValue routeHeader) throws Exception {
245 String content = routeHeader.getDocContent();
246 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(content)));
247 return doc;
248 }
249
250 public static DocumentBuilder getDocumentBuilder() throws Exception {
251 return DocumentBuilderFactory.newInstance().newDocumentBuilder();
252 }
253
254 private static String lowerCaseFirstChar(String s) {
255 if (s.length() == 0 || Character.isLowerCase(s.charAt(0)))
256 return s;
257 StringBuffer sb = new StringBuffer(s.length());
258 sb.append(Character.toLowerCase(s.charAt(0)));
259 if (s.length() > 1) {
260 sb.append(s.substring(1));
261 }
262 return sb.toString();
263 }
264
265 public static Element propertiesToXml(Object o, String elementName) throws Exception {
266 Class c = o.getClass();
267 Document doc = getDocumentBuilder().newDocument();
268 Element wrapper = doc.createElement(elementName);
269 Method[] methods = c.getMethods();
270 for (int i = 0; i < methods.length; i++) {
271 String name = methods[i].getName();
272 if ("getClass".equals(name))
273 continue;
274 if (!name.startsWith("get") || methods[i].getParameterTypes().length > 0)
275 continue;
276 name = name.substring("get".length());
277 name = lowerCaseFirstChar(name);
278 String value = null;
279 try {
280 Object result = methods[i].invoke(o, null);
281 if (result == null) {
282 LOG.debug("value of " + name + " method on object " + o.getClass() + " is null");
283 value = "";
284 } else {
285 value = result.toString();
286 }
287 Element fieldE = doc.createElement(name);
288 fieldE.appendChild(doc.createTextNode(value));
289 wrapper.appendChild(fieldE);
290 } catch (RuntimeException e) {
291 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
292 throw e;
293 } catch (Exception e) {
294 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
295 }
296 }
297 return wrapper;
298 }
299 }