/**
* XML Parsing library for the key-value store
*
* @author Mosharaf Chowdhury (http://www.mosharaf.com)
* @author Prashanth Mohan (http://www.cs.berkeley.edu/~prmohan)
*
* Copyright (c) 2012, University of California at Berkeley
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.berkeley.cs162;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.net.Socket;
import java.net.SocketException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* This is the object that is used to generate messages the XML based messages
* for communication between clients and servers.
*/
public class KVMessage implements Serializable {
public static final long serialVersionUID = 6473128480951955693L;
public String tpcOpId = null;
private String msgType = null;
private String key = null;
private String value = null;
private String message = null;
private Document duck;
public String getTpcOpId() {
return tpcOpId;
}
public void setTpcOpId(String tpcOpId) {
this.tpcOpId = tpcOpId;
}
public Document getDuck() {
return duck;
}
public final String getKey() {
return key;
}
public final void setKey(String key) {
this.key = key;
}
public final String getValue() {
return value;
}
public final void setValue(String value) {
this.value = value;
}
public final String getMessage() {
return message;
}
public final void setMessage(String message) {
this.message = message;
}
public String getMsgType() {
return msgType;
}
/*
* Solution from
* http://weblogs.java.net/blog/kohsuke/archive/2005/07/socket_xml_pitf.html
*/
private class NoCloseInputStream extends FilterInputStream {
public NoCloseInputStream(InputStream in) {
super(in);
}
public void close() {
} // ignore close
}
public KVMessage(KVMessage kvm) {
msgType = kvm.msgType;
key = kvm.key;
value = kvm.value;
message = kvm.message;
tpcOpId = kvm.tpcOpId;
}
/***
*
* @param msgType
* @throws KVException
* of type "resp" with message "Message format incorrect" if
* msgType is unknown
*/
public KVMessage(String msgType) throws KVException {
String m = msgType;
if (!(m.equals("getreq") || m.equals("putreq") || m.equals("delreq")
|| m.equals("resp") || m.equals("register")
|| m.equals("ignoreNext") || m.equals("ready")
|| m.equals("commit") || m.equals("abort") || m.equals("ack"))) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
this.msgType = msgType;
}
public KVMessage(String msgType, String message) throws KVException {
String m = msgType;
if (!(m.equals("getreq") || m.equals("putreq") || m.equals("delreq")
|| m.equals("resp") || m.equals("register")
|| m.equals("ignoreNext") || m.equals("ready")
|| m.equals("commit") || m.equals("abort") || m.equals("ack"))) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
this.msgType = msgType;
this.message = message;
}
/***
* Parse KVMessage from socket's input stream
*
* @param sock
* Socket to receive from
* @throws KVException
* if there is an error in parsing the message. The exception
* should be of type "resp and message should be : a.
* "XML Error: Received unparseable message" - if the received
* message is not valid XML. b.
* "Network Error: Could not receive data" - if there is a
* network error causing an incomplete parsing of the message.
* c. "Message format incorrect" - if there message does not
* conform to the required specifications. Examples include
* incorrect message type.
*/
public KVMessage(Socket sock) throws KVException {
DocumentBuilder db = null;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
Document doc = null;
try {
doc = db.parse(new NoCloseInputStream(sock.getInputStream()));
} catch (SAXException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
} catch (IOException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
NodeList n = doc.getElementsByTagName("KVMessage");
if (n.getLength() == 0) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
Element e = (Element) n.item(0);
String m = e.getAttribute("type");
if (m == null) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
if (!(m.equals("getreq") || m.equals("putreq") || m.equals("delreq")
|| m.equals("resp") || m.equals("register")
|| m.equals("ready") || m.equals("abort") || m.equals("commit") || m
.equals("ack"))) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
msgType = m;
NodeList maybeKey = e.getElementsByTagName("Key");
NodeList maybeValue = e.getElementsByTagName("Value");
NodeList maybeMessage = e.getElementsByTagName("Message");
NodeList maybeTPC = e.getElementsByTagName("TPCOpId");
if (maybeKey.getLength() > 0) {
key = maybeKey.item(0).getTextContent();
}
if (maybeValue.getLength() > 0) {
value = maybeValue.item(0).getTextContent();
}
if (maybeMessage.getLength() > 0) {
message = maybeMessage.item(0).getTextContent();
}
if (maybeTPC.getLength() > 0) {
tpcOpId = maybeTPC.item(0).getTextContent();
}
}
public KVMessage(Socket sock, int timeout) throws KVException {
try {
sock.setSoTimeout(timeout);
DocumentBuilder db = null;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
Document doc = null;
try {
doc = db.parse(new NoCloseInputStream(sock.getInputStream()));
} catch (SAXException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
} catch (IOException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
NodeList n = doc.getElementsByTagName("KVMessage");
if (n.getLength() == 0) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
Element e = (Element) n.item(0);
String m = e.getAttribute("type");
if (m == null) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
if (!(m.equals("getreq") || m.equals("putreq")
|| m.equals("delreq") || m.equals("resp")
|| m.equals("register") || m.equals("ready")
|| m.equals("abort") || m.equals("commit") || m
.equals("ack"))) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
msgType = m;
NodeList maybeKey = e.getElementsByTagName("Key");
NodeList maybeValue = e.getElementsByTagName("Value");
NodeList maybeMessage = e.getElementsByTagName("Message");
NodeList maybeTPC = e.getElementsByTagName("TPCOpId");
if (maybeKey.getLength() > 0) {
key = maybeKey.item(0).getTextContent();
}
if (maybeValue.getLength() > 0) {
value = maybeValue.item(0).getTextContent();
}
if (maybeMessage.getLength() > 0) {
message = maybeMessage.item(0).getTextContent();
}
if (maybeTPC.getLength() > 0) {
tpcOpId = maybeTPC.item(0).getTextContent();
}
} catch (SocketException e) {
throw new KVException(new KVMessage("resp", "Socket timeout"));
}
}
/**
* Generate the XML representation for this message.
*
* @return the XML String
* @throws KVException
* if not enough data is available to generate a valid KV XML
* message
*/
public String toXML() throws KVException {
if (msgType == null || (msgType.equals("delreq") && key == null)
|| (msgType.equals("putreq") && (value == null || key == null))
|| (msgType.equals("resp") && key != null && value == null)) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
DocumentBuilder db = null;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new KVException(new KVMessage("resp",
"Message format incorrect"));
}
Document doc = db.newDocument();
Element kv = doc.createElement("KVMessage");
Element keey, valoo, massage, toy;
kv.setAttribute("type", msgType);
if (key != null) {
keey = doc.createElement("Key");
keey.setTextContent(key);
kv.appendChild(keey);
}
if (value != null) {
valoo = doc.createElement("Value");
valoo.setTextContent(value);
kv.appendChild(valoo);
}
if (message != null) {
massage = doc.createElement("Message");
massage.setTextContent(message);
kv.appendChild(massage);
}
if (tpcOpId != null) {
toy = doc.createElement("TPCOpId");
toy.setTextContent(tpcOpId);
kv.appendChild(toy);
}
doc.appendChild(kv);
duck = doc;
// adapted from
// http://stackoverflow.com/questions/5456680/xml-document-to-string
doc.setXmlStandalone(true);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = null;
try {
transformer = tf.newTransformer();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
}
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
try {
transformer.transform(new DOMSource(doc), new StreamResult(writer));
} catch (TransformerException e) {
e.printStackTrace();
}
String output = writer.getBuffer().toString();
return output;
}
public void sendMessage(Socket sock) throws KVException {
PrintWriter p = null;
try {
p = new PrintWriter(sock.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
p.write(this.toXML());
p.flush();
try {
sock.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
}
}
public String toString() {
return "A KVMessage with type " + msgType + " and key " + key
+ " and value " + value + " and TPCOPID " + tpcOpId
+ " and message " + message;
}
}