1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
55
56
57 public class EDocLitePostProcessor extends DefaultPostProcessor {
58 private static final Logger LOG = Logger.getLogger(EDocLitePostProcessor.class);
59 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 XPath xpath = XPathFactory.newInstance().newXPath();
68 return (String) xpath.evaluate("//edlContent/edl/eventNotificationURL", edlDoc, XPathConstants.STRING);
69 }
70
71
72
73
74
75 private static void submitURL(String urlstring, Document eventDoc) throws IOException {
76 String content;
77 try {
78 content = XmlJotter.jotNode(eventDoc, true);
79 } catch (XmlException te) {
80 LOG.error("Error writing serializing event doc: " + eventDoc);
81 throw te;
82 }
83 byte[] contentBytes = content.getBytes("UTF-8");
84
85 LOG.debug("submitURL: " + urlstring);
86 URL url = new URL(urlstring);
87
88 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 byte[] buf = message.getBytes("UTF-8");
99 Socket s = new Socket(url.getHost(), url.getPort());
100
101
102
103
104
105
106
107
108
109
110 OutputStream os = s.getOutputStream();
111 try {
112 try {
113 os.write(buf, 0, buf.length);
114 os.flush();
115 } catch (InterruptedIOException ioe) {
116 LOG.error("IO was interrupted while posting event to url " + urlstring + ": " + ioe.getMessage());
117 } catch (IOException ioe) {
118 LOG.error("Error posting EDocLite content to url " + urlstring + ioe.getMessage());
119 } finally {
120 try {
121 LOG.debug("Shutting down output stream");
122 s.shutdownOutput();
123 } catch (IOException ioe) {
124 LOG.error("Error shutting down output stream for url " + urlstring + ": " + ioe.getMessage());
125 }
126 }
127
128 InputStream is = s.getInputStream();
129 try {
130
131 buf = new byte[1024];
132 ByteArrayOutputStream baos = new ByteArrayOutputStream();
133
134 int read = is.read(buf);
135 if (read != -1) {
136 baos.write(buf, 0, read);
137 }
138 LOG.debug("EDocLite post processor response:\n" + new String(baos.toByteArray()));
139 } catch (InterruptedIOException ioe) {
140 LOG.error("IO was interrupted while reading response from url " + urlstring + ": " + ioe.getMessage());
141 } catch (IOException ioe) {
142 LOG.error("Error reading response from EDocLite handler url " + urlstring + ioe.getMessage());
143 } finally {
144 try {
145 LOG.debug("Shutting down input stream");
146 s.shutdownInput();
147 } catch (IOException ioe) {
148 LOG.error("Error shutting down input stream for url " + urlstring + ": " + ioe.getMessage());
149 }
150 }
151 } finally {
152 try {
153 s.close();
154 } catch (IOException ioe) {
155 LOG.error("Error closing socket", ioe);
156 }
157 }
158 }
159
160 protected static void postEvent(String docId, Object event, String eventName) {
161 try {
162 Document doc = getEDLContent(docId);
163 if(LOG.isDebugEnabled()){
164 LOG.debug("Submitting doc: " + XmlJotter.jotNode(doc));
165 }
166
167 String urlstring = getURL(doc);
168 if (org.apache.commons.lang.StringUtils.isEmpty(urlstring)) {
169 LOG.warn("No eventNotificationURL defined in EDLContent");
170 return;
171 }
172
173 Document eventDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
174 Element eventE = eventDoc.createElement("event");
175 eventE.setAttribute("type", eventName);
176 eventDoc.appendChild(eventE);
177
178 Element infoE = (Element) eventDoc.importNode(propertiesToXml(event, "info"), true);
179 Element docIdE = eventDoc.createElement("docId");
180 docIdE.appendChild(eventDoc.createTextNode(String.valueOf(docId)));
181 infoE.appendChild(docIdE);
182
183 eventE.appendChild(infoE);
184 eventE.appendChild(eventDoc.importNode(doc.getDocumentElement(), true));
185
186 String query = "docId=" + docId;
187 if (urlstring.indexOf('?') != -1) {
188 urlstring += "&" + query;
189 } else {
190 urlstring += "?" + query;
191 }
192
193 final String _urlstring = urlstring;
194 final Document _eventDoc = eventDoc;
195
196 final Thread t = new Thread(new Runnable() {
197 public void run() {
198 try {
199 LOG.debug("Post Event calling url: " + _urlstring);
200 submitURL(_urlstring, _eventDoc);
201 LOG.debug("Post Event done calling url: " + _urlstring);
202 } catch (Exception e) {
203 LOG.error(e);
204 }
205 }
206 });
207 t.setDaemon(true);
208 t.start();
209
210
211 TIMER.schedule(new TimerTask() {
212 public void run() {
213 t.interrupt();
214 }
215 }, SUBMIT_URL_MILLISECONDS_WAIT);
216 } catch (Exception e) {
217 if (e instanceof RuntimeException) {
218 throw (RuntimeException)e;
219 }
220 throw new RuntimeException(e);
221 }
222 }
223
224 public ProcessDocReport doRouteStatusChange(DocumentRouteStatusChange event) throws RemoteException {
225 LOG.debug("doRouteStatusChange: " + event);
226 postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_STATUS_CHANGE);
227 return new ProcessDocReport(true, "");
228 }
229
230 public ProcessDocReport doActionTaken(ActionTakenEvent event) throws RemoteException {
231 LOG.debug("doActionTaken: " + event);
232 postEvent(event.getDocumentId(), event, EVENT_TYPE_ACTION_TAKEN);
233 return new ProcessDocReport(true, "");
234 }
235
236 public ProcessDocReport doDeleteRouteHeader(DeleteEvent event) throws RemoteException {
237 LOG.debug("doDeleteRouteHeader: " + event);
238 postEvent(event.getDocumentId(), event, EVENT_TYPE_DELETE_ROUTE_HEADER);
239 return new ProcessDocReport(true, "");
240 }
241
242 public ProcessDocReport doRouteLevelChange(DocumentRouteLevelChange event) throws RemoteException {
243 LOG.debug("doRouteLevelChange: " + event);
244 postEvent(event.getDocumentId(), event, EVENT_TYPE_ROUTE_LEVEL_CHANGE);
245 return new ProcessDocReport(true, "");
246 }
247
248 public static Document getEDLContent(String documentId) {
249 try {
250 DocumentContent documentContent = KewApiServiceLocator.getWorkflowDocumentService().getDocumentContent(documentId);
251 String content = documentContent.getFullContent();
252 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(content)));
253 return doc;
254 } catch (Exception e) {
255 if (e instanceof RuntimeException) {
256 throw (RuntimeException)e;
257 }
258 throw new RuntimeException(e);
259 }
260 }
261
262 public static DocumentBuilder getDocumentBuilder() throws Exception {
263 return DocumentBuilderFactory.newInstance().newDocumentBuilder();
264 }
265
266 private static String lowerCaseFirstChar(String s) {
267 if (s.length() == 0 || Character.isLowerCase(s.charAt(0)))
268 return s;
269 StringBuffer sb = new StringBuffer(s.length());
270 sb.append(Character.toLowerCase(s.charAt(0)));
271 if (s.length() > 1) {
272 sb.append(s.substring(1));
273 }
274 return sb.toString();
275 }
276
277 public static Element propertiesToXml(Object o, String elementName) throws Exception {
278 Class c = o.getClass();
279 Document doc = getDocumentBuilder().newDocument();
280 Element wrapper = doc.createElement(elementName);
281 Method[] methods = c.getMethods();
282 for (int i = 0; i < methods.length; i++) {
283 String name = methods[i].getName();
284 if ("getClass".equals(name))
285 continue;
286 if (!name.startsWith("get") || methods[i].getParameterTypes().length > 0)
287 continue;
288 name = name.substring("get".length());
289 name = lowerCaseFirstChar(name);
290 String value = null;
291 try {
292 Object result = methods[i].invoke(o, null);
293 if (result == null) {
294 LOG.debug("value of " + name + " method on object " + o.getClass() + " is null");
295 value = "";
296 } else {
297 value = result.toString();
298 }
299 Element fieldE = doc.createElement(name);
300 fieldE.appendChild(doc.createTextNode(value));
301 wrapper.appendChild(fieldE);
302 } catch (RuntimeException e) {
303 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
304 throw e;
305 } catch (Exception e) {
306 LOG.error("Error accessing method '" + methods[i].getName() + " of instance of " + c);
307 }
308 }
309 return wrapper;
310 }
311 }