001: /*
002: * Copyright 2005-2006 The Kuali Foundation.
003: *
004: *
005: * Licensed under the Educational Community License, Version 1.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.opensource.org/licenses/ecl1.php
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package edu.iu.uis.eden.edl;
018:
019: import java.io.ByteArrayOutputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.InterruptedIOException;
023: import java.io.OutputStream;
024: import java.io.StringReader;
025: import java.lang.reflect.Method;
026: import java.net.Socket;
027: import java.net.URL;
028: import java.util.Timer;
029: import java.util.TimerTask;
030:
031: import javax.xml.parsers.DocumentBuilder;
032: import javax.xml.parsers.DocumentBuilderFactory;
033: import javax.xml.transform.TransformerException;
034: import javax.xml.xpath.XPath;
035: import javax.xml.xpath.XPathConstants;
036: import javax.xml.xpath.XPathExpressionException;
037: import javax.xml.xpath.XPathFactory;
038:
039: import org.apache.log4j.Logger;
040: import org.w3c.dom.Document;
041: import org.w3c.dom.Element;
042: import org.xml.sax.InputSource;
043:
044: import edu.iu.uis.eden.ActionTakenEvent;
045: import edu.iu.uis.eden.DocumentRouteLevelChange;
046: import edu.iu.uis.eden.DocumentRouteStatusChange;
047: import edu.iu.uis.eden.KEWServiceLocator;
048: import edu.iu.uis.eden.clientapp.DeleteEvent;
049: import edu.iu.uis.eden.postprocessor.DefaultPostProcessor;
050: import edu.iu.uis.eden.postprocessor.ProcessDocReport;
051: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
052: import edu.iu.uis.eden.util.Utilities;
053: import edu.iu.uis.eden.util.XmlHelper;
054:
055: /**
056: * PostProcessor responsible for posting events to a url defined in the EDL doc definition.
057: * @author Aaron Hamid (arh14 at cornell dot edu)
058: */
059: public class EDocLitePostProcessor extends DefaultPostProcessor {
060: private static final Logger LOG = Logger
061: .getLogger(EDocLitePostProcessor.class);
062: private static final Timer TIMER = new Timer();
063:
064: private static String getURL(Document edlDoc)
065: throws XPathExpressionException {
066: XPath xpath = XPathFactory.newInstance().newXPath();
067: return (String) xpath.evaluate(
068: "//edlContent/edl/eventNotificationURL", edlDoc,
069: XPathConstants.STRING);
070: }
071:
072: /**
073: * @param urlstring
074: * @param eventDoc
075: */
076: private static void submitURL(String urlstring, Document eventDoc)
077: throws IOException, TransformerException {
078: String content;
079: try {
080: content = XmlHelper.writeNode(eventDoc, true);
081: } catch (TransformerException te) {
082: LOG.error("Error writing serializing event doc: "
083: + eventDoc);
084: throw te;
085: }
086: byte[] contentBytes = content.getBytes("UTF-8");
087:
088: LOG.debug("submitURL: " + urlstring);
089: URL url = new URL(urlstring);
090:
091: String message = "POST "
092: + url.getFile()
093: + " HTTP/1.0\r\n"
094: + "Content-Length: "
095: + contentBytes.length
096: + "\r\n"
097: + "Cache-Control: no-cache\r\n"
098: + "Pragma: no-cache\r\n"
099: + "User-Agent: Java/1.4.2; EDocLitePostProcessor\r\n"
100: + "Host: "
101: + url.getHost()
102: + "\r\n"
103: + "Connection: close\r\n"
104: + "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
105: + content;
106:
107: byte[] buf = message.getBytes("UTF-8");
108: Socket s = new Socket(url.getHost(), url.getPort());
109:
110: /*URLConnection con = url.openConnection();
111: LOG.debug("got connection: " + con);
112: con.setDoOutput(true);
113: con.setDoInput(true);
114: LOG.debug("setDoOutput(true)");
115:
116: con.setRequestProperty("Connection", "close");
117: con.setRequestProperty("Content-Length", String.valueOf(buf.length));*/
118:
119: OutputStream os = s.getOutputStream();
120: try {
121: try {
122: os.write(buf, 0, buf.length);
123: os.flush();
124: } catch (InterruptedIOException ioe) {
125: LOG
126: .error("IO was interrupted while posting event to url "
127: + urlstring + ": " + ioe.getMessage());
128: } catch (IOException ioe) {
129: LOG.error("Error posting EDocLite content to url "
130: + urlstring + ioe.getMessage());
131: } finally {
132: try {
133: LOG.debug("Shutting down output stream");
134: s.shutdownOutput();
135: } catch (IOException ioe) {
136: LOG
137: .error("Error shutting down output stream for url "
138: + urlstring
139: + ": "
140: + ioe.getMessage());
141: }
142: }
143:
144: InputStream is = s.getInputStream();
145: try {
146:
147: buf = new byte[1024];
148: ByteArrayOutputStream baos = new ByteArrayOutputStream();
149: // this is what actually forces the write on the URLConnection!
150: int read = is.read(buf);
151: if (read != -1) {
152: baos.write(buf, 0, read);
153: }
154: LOG.debug("EDocLite post processor response:\n"
155: + new String(baos.toByteArray()));
156: } catch (InterruptedIOException ioe) {
157: LOG
158: .error("IO was interrupted while reading response from url "
159: + urlstring + ": " + ioe.getMessage());
160: } catch (IOException ioe) {
161: LOG
162: .error("Error reading response from EDocLite handler url "
163: + urlstring + ioe.getMessage());
164: } finally {
165: try {
166: LOG.debug("Shutting down input stream");
167: s.shutdownInput();
168: } catch (IOException ioe) {
169: LOG
170: .error("Error shutting down input stream for url "
171: + urlstring
172: + ": "
173: + ioe.getMessage());
174: }
175: }
176: } finally {
177: try {
178: s.close();
179: } catch (IOException ioe) {
180: LOG.error("Error closing socket", ioe);
181: }
182: }
183: }
184:
185: private static void postEvent(Long docId, Object event,
186: String eventName) throws Exception {
187: DocumentRouteHeaderValue val = KEWServiceLocator
188: .getRouteHeaderService().getRouteHeader(docId);
189: Document doc = getEDLContent(val);
190: LOG.debug("Submitting doc: " + XmlHelper.jotNode(doc));
191:
192: String urlstring = getURL(doc);
193: if (Utilities.isEmpty(urlstring)) {
194: LOG.warn("No eventNotificationURL defined in EDLContent");
195: }
196:
197: Document eventDoc = DocumentBuilderFactory.newInstance()
198: .newDocumentBuilder().newDocument();
199: Element eventE = eventDoc.createElement("event");
200: eventE.setAttribute("type", eventName);
201: eventDoc.appendChild(eventE);
202:
203: Element infoE = (Element) eventDoc.importNode(propertiesToXml(
204: event, "info"), true);
205: Element docIdE = eventDoc.createElement("docId");
206: docIdE.appendChild(eventDoc.createTextNode(String
207: .valueOf(docId)));
208: infoE.appendChild(docIdE);
209:
210: eventE.appendChild(infoE);
211: eventE.appendChild(eventDoc.importNode(
212: doc.getDocumentElement(), true));
213:
214: String query = "docId=" + docId;
215: if (urlstring.indexOf('?') != -1) {
216: urlstring += "&" + query;
217: } else {
218: urlstring += "?" + query;
219: }
220:
221: final String _urlstring = urlstring;
222: final Document _eventDoc = eventDoc;
223: // a super cheesy way to enforce asynchronicity/timeout follows:
224: final Thread t = new Thread(new Runnable() {
225: public void run() {
226: try {
227: submitURL(_urlstring, _eventDoc);
228: } catch (Exception e) {
229: LOG.error(e);
230: }
231: }
232: });
233: t.setDaemon(true);
234: t.start();
235:
236: // kill the submission thread if it hasn't completed after 1 minute
237: TIMER.schedule(new TimerTask() {
238: public void run() {
239: t.interrupt();
240: }
241: }, 60000);
242: }
243:
244: public ProcessDocReport doRouteStatusChange(
245: DocumentRouteStatusChange event) throws Exception {
246: LOG.debug("doRouteStatusChange: " + event);
247: // postEvent(event.getRouteHeaderId(), event, "statusChange");
248: return super .doRouteStatusChange(event);
249: }
250:
251: public ProcessDocReport doActionTaken(ActionTakenEvent event)
252: throws Exception {
253: LOG.debug("doActionTaken: " + event);
254: // postEvent(event.getRouteHeaderId(), event, "actionTaken");
255: return super .doActionTaken(event);
256: }
257:
258: public ProcessDocReport doDeleteRouteHeader(DeleteEvent event)
259: throws Exception {
260: LOG.debug("doDeleteRouteHeader: " + event);
261: // postEvent(event.getRouteHeaderId(), event, "deleteRouteHeader");
262: return super .doDeleteRouteHeader(event);
263: }
264:
265: public ProcessDocReport doRouteLevelChange(
266: DocumentRouteLevelChange event) throws Exception {
267: LOG.debug("doRouteLevelChange: " + event);
268: // postEvent(event.getRouteHeaderId(), event, "routeLevelChange");
269: return super .doRouteLevelChange(event);
270: }
271:
272: public static Document getEDLContent(
273: DocumentRouteHeaderValue routeHeader) throws Exception {
274: String content = routeHeader.getDocContent();
275: Document doc = DocumentBuilderFactory.newInstance()
276: .newDocumentBuilder().parse(
277: new InputSource(new StringReader(content)));
278: return doc;
279: }
280:
281: public static DocumentBuilder getDocumentBuilder() throws Exception {
282: return DocumentBuilderFactory.newInstance()
283: .newDocumentBuilder();
284: }
285:
286: private static String lowerCaseFirstChar(String s) {
287: if (s.length() == 0 || Character.isLowerCase(s.charAt(0)))
288: return s;
289: StringBuffer sb = new StringBuffer(s.length());
290: sb.append(Character.toLowerCase(s.charAt(0)));
291: if (s.length() > 1) {
292: sb.append(s.substring(1));
293: }
294: return sb.toString();
295: }
296:
297: public static Element propertiesToXml(Object o, String elementName)
298: throws Exception {
299: Class c = o.getClass();
300: Document doc = getDocumentBuilder().newDocument();
301: Element wrapper = doc.createElement(elementName);
302: Method[] methods = c.getMethods();
303: for (int i = 0; i < methods.length; i++) {
304: String name = methods[i].getName();
305: if ("getClass".equals(name))
306: continue;
307: if (!name.startsWith("get")
308: || methods[i].getParameterTypes().length > 0)
309: continue;
310: name = name.substring("get".length());
311: name = lowerCaseFirstChar(name);
312: String value = null;
313: try {
314: Object result = methods[i].invoke(o, null);
315: if (result == null) {
316: LOG.debug("value of " + name + " method on object "
317: + o.getClass() + " is null");
318: value = "";
319: } else {
320: value = result.toString();
321: }
322: Element fieldE = doc.createElement(name);
323: fieldE.appendChild(doc.createTextNode(value));
324: wrapper.appendChild(fieldE);
325: } catch (RuntimeException e) {
326: LOG
327: .error("Error accessing method '"
328: + methods[i].getName()
329: + " of instance of " + c);
330: throw e;
331: } catch (Exception e) {
332: LOG
333: .error("Error accessing method '"
334: + methods[i].getName()
335: + " of instance of " + c);
336: }
337: }
338: return wrapper;
339: }
340: }
|