package altk.comm.engine; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Vector; import java.util.zip.ZipInputStream; import javax.servlet.http.HttpServletRequest; 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 org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import altk.comm.engine.exception.BroadcastError; import altk.comm.engine.exception.BroadcastException; import altk.comm.engine.exception.BroadcastMsgException; import altk.comm.engine.exception.EngineException; import altk.comm.engine.exception.InternalErrorException; import altk.comm.engine.exception.NonExistentException; import altk.comm.engine.exception.PlatformError; import altk.comm.engine.exception.PlatformException; /** * Abstract class extending Broadcast by providing an implementation of the decode * method to parse HTTP POST input into XML DOM, and extracting the required class * attributes broadcast_id, launch_record_id, expire_time, postback and recipientList. * @author Yuk-Ming * */ public abstract class XMLDOMBroadcast extends Broadcast { protected XPath xpathEngine; protected DocumentBuilder builder; protected Element broadcastNode; /** * * @param broadcastType * @param activityRecordIdParamName - if null, default is used * @param jobReportRootNodeName - job report root node name */ protected XMLDOMBroadcast(String broadcastType, String activityRecordIdParamName, String jobReportRootNodeName) { super(broadcastType, activityRecordIdParamName, jobReportRootNodeName); } /** * Sets up XMLDoc and parses broadcast_id, expire_time, postBackUrl and recipientList. * Derived class cannot override this method, which is final. * @throws EngineException */ @Override protected final void decode(HttpServletRequest request, boolean notInService) throws EngineException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); builder = factory.newDocumentBuilder(); xpathEngine = XPathFactory.newInstance().newXPath(); } catch (Exception e) { CommonLogger.alarm.error(e.getMessage()); throw new PlatformException(PlatformError.RUNTIME_ERROR, e.getMessage()); } // parse request into multiple BroadcastSMS pass then to Dispatcher Document xmlDoc; try { InputStream inb; String contentType = request.getContentType(); if (contentType != null && contentType.split("/")[1].equalsIgnoreCase("zip")) { ZipInputStream zip = new ZipInputStream(request.getInputStream()); zip.getNextEntry(); inb = zip; } else { inb = request.getInputStream(); } int contentLength = request.getContentLength(); CommonLogger.activity.info("Receiving " + contentLength + " bytes of data"); byte[] content = new byte[contentLength]; int offset = 0; int length = contentLength; int read; while (length > 0) { read = inb.read(content, offset, length); if (read < 0) break; offset += read; length -= read; } CommonLogger.activity.info("Received: " + new String(content, "UTF-8")); xmlDoc = builder.parse(new ByteArrayInputStream(content), "UTF-8"); } catch (SAXException e) { throw new BroadcastException(BroadcastError.BAD_REQUEST_DATA, e.getMessage()); } catch (IOException e) { throw new BroadcastException(BroadcastError.READ_REQUEST_ERROR, "Problem in reading request"); } //String xpath = "//" + broadcastName; // get all first level elements NodeList broadcastNodes = null; broadcastNodes = xmlDoc.getElementsByTagName(broadcastType); if (broadcastNodes.getLength() == 0) { throw new BroadcastException(BroadcastError.BAD_REQUEST_DATA, "No <" + broadcastType + "> tag"); } if (notInService) return; // Only one broadcast node is processed. The rest are silently ignored. broadcastNode = (Element)broadcastNodes.item(0); if (!broadcastType.equals(broadcastNode.getNodeName())) { throw new BroadcastMsgException("Broadcast node name does not match " + broadcastType); } // broadcast_id String broadcastId = broadcastNode.getAttribute("broadcast_id"); if (broadcastId.length() == 0) { throw new BroadcastMsgException("Missing broadcast_id"); } setBroadcastId(broadcastId); // expireTime long now = System.currentTimeMillis(); try { long expireTime = getLongValue("expire_time", broadcastNode); // defaults to 20 minutes myLogger.debug("expire_time decoded to be " + expireTime); if (expireTime == 0) expireTime = now + 20 * 60 * 1000; myLogger.debug("expire time adjusted to be " + expireTime); setExpireTime(expireTime); setLaunchRecordId(broadcastNode.getAttribute("launch_record_id")); } catch (Exception e) { throw new BroadcastMsgException("Missing or bad expire_time"); } // Postback postBackURL = getStringValue("async_status_post_back", broadcastNode); if (postBackURL != null && (postBackURL=postBackURL.trim()).length() == 0) postBackURL = null; if (postBackURL == null) { CommonLogger.alarm.warn("Missing asyn_status_post_back in POST data"); } NodeList recipientNodes = null; String xpath = "recipient_list/recipient"; try { recipientNodes = (NodeList) xpathEngine.evaluate(xpath, broadcastNode, XPathConstants.NODESET); } catch (XPathExpressionException e) { throw new InternalErrorException("Bad xpath 'recipient_list/recipient", e); } Element recipientNode; String contact_id; String activity_record_id; xpath = "email"; for (int i = 0; i < recipientNodes.getLength(); i++) { recipientNode = (Element)recipientNodes.item(i); contact_id = recipientNode.getAttribute("contact_id"); if (contact_id == null || (contact_id=contact_id.trim()).length() == 0) { myLogger.warn("Missing or empty contact_id for a recipient in broadcast " + getBroadcastId()); continue; } activity_record_id = recipientNode.getAttribute(activityRecordIdParamName); if (activity_record_id == null || (activity_record_id = activity_record_id.trim()).length() == 0) { throw new BroadcastMsgException("Missing or empty " + getActivityRecordIdName() + " attribute for a recipient in broadcast " + getBroadcastId()); } Map attributes = new HashMap(); NodeList children = recipientNode.getChildNodes(); for (int j = 0; j < children.getLength(); j++) { Node child = children.item(j); if (child.getNodeType() != Node.ELEMENT_NODE) continue; attributes.put(child.getNodeName(), child.getTextContent()); } recipientList.add(new Recipient(contact_id, activity_record_id, attributes)); } } /** * Gets String value of child with given name. * @param childNodeName * @param element * @return null if no such child exists. * @throws IllegalArgumentException when element is null */ protected String getStringValue(String childNodeName, Element element) throws IllegalArgumentException { if (element == null) { throw new IllegalArgumentException("Element cannot be null"); } NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(childNodeName)) { return child.getTextContent(); } } return null; } /** * * @param childNodeName * @param element * @return * @throws IllegalArgumentException when element is null */ protected String getNonNullStringValue(String childNodeName, Element element) throws IllegalArgumentException { String value = getStringValue(childNodeName, element); return value==null?"":value; } /** * * @param childNodeName * @param element * @return * @throws IllegalArgumentException when element is null */ public boolean getBooleanValue(String childNodeName, Element element) throws IllegalArgumentException { String str = getStringValue(childNodeName, element); if (str == null) return false; return Boolean.parseBoolean(str.trim()); } /** * * @param childNodeName * @param element * @return * @throws NonExistentException * @throws NumberFormatException * @throws IllegalArgumentException when element is null */ protected int getIntValue(String childNodeName, Element element) throws NonExistentException, NumberFormatException, IllegalArgumentException { if (element == null) { throw new IllegalArgumentException("Element cannot be null, when invoking getIntValue method"); } try { String str = getStringValue(childNodeName, element); if (str == null) { throw new NonExistentException("No child \"" + childNodeName + "\" exists for element \"" + xml2Str(element) + "\""); } return Integer.parseInt(str); } catch (NumberFormatException e) { throw new NumberFormatException("Value of child \"" + childNodeName + "\" not integer for element \"" + xml2Str(element) + "\""); } } /** * * @param childNodeName * @param element * @return * @throws NonExistentException * @throws NumberFormatException * @throws IllegalArgumentException when element is null */ protected Long getLongValue(String childNodeName, Element element) throws NonExistentException, NumberFormatException, IllegalArgumentException { if (element == null) { throw new IllegalArgumentException("Element cannot be null, when invoking getIntValue method"); } try { String str = getStringValue(childNodeName, element); if (str == null) { throw new NonExistentException("No child \"" + childNodeName + "\" exists for element \"" + xml2Str(element) + "\""); } return Long.parseLong(str); } catch (NumberFormatException e) { throw new NumberFormatException("Value of child \"" + childNodeName + "\" not integer for element \"" + xml2Str(element) + "\""); } } protected NodeList getNodeList(String xpath, Element element) { try { NodeList nodes = (NodeList) xpathEngine.evaluate(xpath, element, XPathConstants.NODESET); return nodes; } catch (XPathExpressionException e) { throw new InternalErrorException("Improper xpath \"" + xpath + "\"", e); } } protected Node getNode(String xpath, Element element) { try { Node node = (Node) xpathEngine.evaluate(xpath, element, XPathConstants.NODE); return node; } catch (XPathExpressionException e) { throw new InternalErrorException("Improper xpath \"" + xpath + "\"", e); } } static public void appendXML(Element topElement, StringBuffer buf) { // starting tag buf.append('<'); String elementName = topElement.getNodeName(); buf.append(elementName); // handle attributes first NamedNodeMap attributes = topElement.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Node attr = attributes.item(i); buf.append(' '); buf.append(attr.getNodeName()); buf.append("=\""); buf.append(attr.getNodeValue()); buf.append("\""); } buf.append('>'); // handling text Vector childElements = new Vector(); NodeList children = topElement.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); switch (node.getNodeType()) { case Node.ELEMENT_NODE: // defer handling childElements.add(node); break; case Node.TEXT_NODE: buf.append(node.getNodeValue()); break; default: } } // handling children elements for (Node child : childElements) { appendXML((Element)child, buf); } // ending tag buf.append("'); } public String xml2Str(Element element) { StringBuffer buf = new StringBuffer(); appendXML(element, buf); return buf.toString(); } /** * Derived class may override this method to redefine the name of activity_record_id, * e.g. email_record_id, sms_record_id, etc. * @return name of activity_record_9d. */ protected String getActivityRecordIdName() { return "activity_record_id"; } }