package org.wiztools.restclient.util;
import java.io.*;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.XMLEvent;
import nu.xom.*;
import org.wiztools.commons.MultiValueMap;
import org.wiztools.commons.StringUtil;
import org.wiztools.restclient.RCConstants;
import org.wiztools.restclient.XMLException;
import org.wiztools.restclient.bean.*;
/**
*
* @author rsubramanian
*/
public final class XMLUtil {
private XMLUtil() {
}
private static final Logger LOG = Logger.getLogger(XMLUtil.class.getName());
private static final String[] VERSIONS = new String[]{
"3.0", "3.1", "3.2", "3.2.1", "3.2.2", "3.2.3", "3.3", "3.3.1", "3.4",
"3.4.1", RCConstants.VERSION
};
public static final String XML_MIME = "application/xml";
static {
// Sort the version array for binary search
Arrays.sort(VERSIONS);
}
protected static void checkIfVersionValid(final String restVersion)
throws XMLException {
if (restVersion == null) {
throw new XMLException("Attribute `version' not available for root element");
}
int res = Arrays.binarySearch(VERSIONS, restVersion);
if (res == -1) {
throw new XMLException("Version not supported");
}
}
private static Element getRootElement() {
Element eRoot = new Element("rest-client");
// set version attributes to rest-client root tag
eRoot.addAttribute(new Attribute("version", RCConstants.VERSION));
return eRoot;
}
protected static Element getRequestElement(final Request bean) {
Element reqElement = new Element("request");
{ // HTTP Version
Element e = new Element("http-version");
e.appendChild(bean.getHttpVersion().versionNumber());
reqElement.appendChild(e);
}
if(bean.isFollowRedirect()) { // HTTP Follow Redirect
Element e = new Element("http-follow-redirects");
reqElement.appendChild(e);
}
if(bean.isIgnoreResponseBody()) { // Response body ignored
Element e = new Element("ignore-response-body");
reqElement.appendChild(e);
}
{ // creating the URL child element
Element e = new Element("URL");
e.appendChild(bean.getUrl().toString());
reqElement.appendChild(e);
}
{ // creating the method child element
Element e = new Element("method");
e.appendChild(bean.getMethod().name());
reqElement.appendChild(e);
}
{ // auth
Auth auth = bean.getAuth();
if(auth != null) {
Element eAuth = XmlAuthUtil.getAuthElement(auth);
reqElement.appendChild(eAuth);
}
}
// Creating SSL elements
if(bean.getSslReq() != null) {
Element eSsl = XmlSslUtil.getSslReq(bean.getSslReq());
reqElement.appendChild(eSsl);
}
// creating the headers child element
MultiValueMap<String, String> headers = bean.getHeaders();
if (!headers.isEmpty()) {
Element e = new Element("headers");
for (String key : headers.keySet()) {
for(String value: headers.get(key)) {
Element ee = new Element("header");
ee.addAttribute(new Attribute("key", key));
ee.addAttribute(new Attribute("value", value));
e.appendChild(ee);
}
}
reqElement.appendChild(e);
}
// Cookies
List<HttpCookie> cookies = bean.getCookies();
if(!cookies.isEmpty()) {
Element e = new Element("cookies");
for(HttpCookie cookie: cookies) {
Element ee = new Element("cookie");
ee.addAttribute(new Attribute("name", cookie.getName()));
ee.addAttribute(new Attribute("value", cookie.getValue()));
ee.addAttribute(new Attribute("version", String.valueOf(cookie.getVersion())));
e.appendChild(ee);
}
reqElement.appendChild(e);
}
{ // creating the body child element
ReqEntity entityBean = bean.getBody();
if(entityBean != null) {
Element e = XmlBodyUtil.getReqEntity(entityBean);
reqElement.appendChild(e);
}
}
// creating the test-script child element
String testScript = bean.getTestScript();
if (testScript != null) {
Element e = new Element("test-script");
e.appendChild(testScript);
reqElement.appendChild(e);
}
return reqElement;
}
protected static Document request2XML(final Request bean)
throws XMLException {
Element reqRootElement = getRootElement();
reqRootElement.appendChild(getRequestElement(bean));
Document xomDocument = new Document(reqRootElement);
return xomDocument;
}
private static Map<String, String> getHeadersFromHeaderNode(final Element node)
throws XMLException {
Map<String, String> m = new LinkedHashMap<>();
for (int i = 0; i < node.getChildElements().size(); i++) {
Element headerElement = node.getChildElements().get(i);
if (!"header".equals(headerElement.getQualifiedName())) {
throw new XMLException("<headers> element should contain only <header> elements");
}
m.put(headerElement.getAttributeValue("key"),
headerElement.getAttributeValue("value"));
}
return m;
}
private static List<HttpCookie> getCookiesFromCookiesNode(final Element node)
throws XMLException {
List<HttpCookie> out = new ArrayList<>();
for (int i = 0; i < node.getChildElements().size(); i++) {
Element e = node.getChildElements().get(i);
if(!"cookie".equals(e.getQualifiedName())) {
throw new XMLException("<cookies> element should contain only <cookie> elements");
}
HttpCookie cookie = new HttpCookie(e.getAttributeValue("name"),
e.getAttributeValue("value"));
final String cookieVerStr = e.getAttributeValue("version");
if(StringUtil.isNotEmpty(cookieVerStr)) {
cookie.setVersion(Integer.parseInt(cookieVerStr));
}
else {
cookie.setVersion(CookieVersion.DEFAULT_VERSION.getIntValue());
}
out.add(cookie);
}
return out;
}
protected static Request getRequestBean(Element requestNode)
throws MalformedURLException, XMLException {
RequestBean requestBean = new RequestBean();
for (int i = 0; i < requestNode.getChildElements().size(); i++) {
Element tNode = requestNode.getChildElements().get(i);
String nodeName = tNode.getQualifiedName();
if ("http-version".equals(nodeName)) {
String t = tNode.getValue();
HTTPVersion httpVersion = "1.1".equals(t) ? HTTPVersion.HTTP_1_1 : HTTPVersion.HTTP_1_0;
requestBean.setHttpVersion(httpVersion);
}
else if("http-follow-redirects".equals(nodeName)) {
requestBean.setFollowRedirect(true);
}
else if("ignore-response-body".equals(nodeName)) {
requestBean.setIgnoreResponseBody(true);
}
else if ("URL".equals(nodeName)) {
URL url = new URL(tNode.getValue());
requestBean.setUrl(url);
}
else if ("method".equals(nodeName)) {
requestBean.setMethod(HTTPMethod.get(tNode.getValue()));
}
else if("auth".equals(nodeName)) {
requestBean.setAuth(XmlAuthUtil.getAuth(tNode));
}
else if("ssl".equals(nodeName)) {
requestBean.setSslReq(XmlSslUtil.getSslReq(tNode));
}
else if ("headers".equals(nodeName)) {
Map<String, String> m = getHeadersFromHeaderNode(tNode);
for (String key : m.keySet()) {
requestBean.addHeader(key, m.get(key));
}
}
else if ("cookies".equals(nodeName)) {
List<HttpCookie> cookies = getCookiesFromCookiesNode(tNode);
for (HttpCookie cookie: cookies) {
requestBean.addCookie(cookie);
}
}
else if ("body".equals(nodeName)) {
ReqEntity body = XmlBodyUtil.getReqEntity(tNode);
requestBean.setBody(body);
}
else if ("test-script".equals(nodeName)) {
requestBean.setTestScript(tNode.getValue());
}
else {
throw new XMLException("Invalid element encountered: <" + nodeName + ">");
}
}
return requestBean;
}
protected static Request xml2Request(final Document doc)
throws MalformedURLException, XMLException {
// get the rootNode
Element rootNode = doc.getRootElement();
if (!"rest-client".equals(rootNode.getQualifiedName())) {
throw new XMLException("Root node is not <rest-client>");
}
// checking correct rest version
final String rcVersion = rootNode.getAttributeValue("version");
checkIfVersionValid(rcVersion);
// if more than two request element is present then throw the exception
if (rootNode.getChildElements().size() != 1) {
throw new XMLException("There can be only one child node for root node: <request>");
}
// minimum one request element is present in xml
if (rootNode.getFirstChildElement("request") == null) {
throw new XMLException("The child node of <rest-client> should be <request>");
}
Element requestNode = rootNode.getFirstChildElement("request");
return getRequestBean(requestNode);
}
protected static Element getResponseElement(final Response bean) {
Element respElement = new Element("response");
Element respChildSubElement = null;
Element respChildSubSubElement = null;
// adding first sub child element - execution-time and append to response child element
respChildSubElement = new Element("execution-time");
respChildSubElement.appendChild(String.valueOf(bean.getExecutionTime()));
respElement.appendChild(respChildSubElement);
// adding second sub child element - status and code attributes and append to response child element
respChildSubElement = new Element("status");
Attribute codeAttributes = new Attribute("code", String.valueOf(bean.getStatusCode()));
respChildSubElement.addAttribute(codeAttributes);
respChildSubElement.appendChild(bean.getStatusLine());
respElement.appendChild(respChildSubElement);
// adding third sub child element - headers
MultiValueMap<String, String> headers = bean.getHeaders();
if (!headers.isEmpty()) {
Attribute keyAttribute = null;
Attribute valueAttribute = null;
// creating sub child-child element
respChildSubElement = new Element("headers");
for (String key : headers.keySet()) {
for(String value: headers.get(key)) {
respChildSubSubElement = new Element("header");
keyAttribute = new Attribute("key", key);
valueAttribute = new Attribute("value", value);
respChildSubSubElement.addAttribute(keyAttribute);
respChildSubSubElement.addAttribute(valueAttribute);
respChildSubElement.appendChild(respChildSubSubElement);
}
}
// add response child element - headers
respElement.appendChild(respChildSubElement);
}
byte[] responseBody = bean.getResponseBody();
if (responseBody != null) {
//creating the body child element and append to response child element
respChildSubElement = new Element("body");
final String base64encodedBody = Util.base64encode(responseBody);
respChildSubElement.appendChild(base64encodedBody);
respElement.appendChild(respChildSubElement);
}
// test result
TestResult testResult = bean.getTestResult();
if (testResult != null) {
//creating the test-result child element
respChildSubElement = new Element("test-result");
// Counts:
Element e_runCount = new Element("run-count");
e_runCount.appendChild(String.valueOf(testResult.getRunCount()));
Element e_failureCount = new Element("failure-count");
e_failureCount.appendChild(String.valueOf(testResult.getFailureCount()));
Element e_errorCount = new Element("error-count");
e_errorCount.appendChild(String.valueOf(testResult.getErrorCount()));
respChildSubElement.appendChild(e_runCount);
respChildSubElement.appendChild(e_failureCount);
respChildSubElement.appendChild(e_errorCount);
// Failures
if (testResult.getFailureCount() > 0) {
Element e_failures = new Element("failures");
List<TestExceptionResult> l = testResult.getFailures();
for (TestExceptionResult b : l) {
Element e_message = new Element("message");
e_message.appendChild(b.getExceptionMessage());
Element e_line = new Element("line-number");
e_line.appendChild(String.valueOf(b.getLineNumber()));
Element e_failure = new Element("failure");
e_failure.appendChild(e_message);
e_failure.appendChild(e_line);
e_failures.appendChild(e_failure);
}
respChildSubElement.appendChild(e_failures);
}
//Errors
if (testResult.getErrorCount() > 0) {
Element e_errors = new Element("errors");
List<TestExceptionResult> l = testResult.getErrors();
for (TestExceptionResult b : l) {
Element e_message = new Element("message");
e_message.appendChild(b.getExceptionMessage());
Element e_line = new Element("line-number");
e_line.appendChild(String.valueOf(b.getLineNumber()));
Element e_error = new Element("error");
e_error.appendChild(e_message);
e_error.appendChild(e_line);
e_errors.appendChild(e_error);
}
respChildSubElement.appendChild(e_errors);
}
// Trace
Element e_trace = new Element("trace");
e_trace.appendChild(testResult.toString());
respChildSubElement.appendChild(e_trace);
respElement.appendChild(respChildSubElement);
}
return respElement;
}
protected static Document response2XML(final Response bean)
throws XMLException {
Element respRootElement = getRootElement();
respRootElement.appendChild(getResponseElement(bean));
Document xomDocument = new Document(respRootElement);
return xomDocument;
}
protected static Response xml2Response(final Document doc)
throws XMLException {
ResponseBean responseBean = new ResponseBean();
// get the rootNode
Element rootNode = doc.getRootElement();
if (!"rest-client".equals(rootNode.getQualifiedName())) {
throw new XMLException("Root node is not <rest-client>");
}
// checking correct rest version
checkIfVersionValid(rootNode.getAttributeValue("version"));
// assign rootnode to current node and also finding 'response' node
Element tNode = null;
Element responseNode = null;
// if more than two request element is present then throw the exception
if (rootNode.getChildElements().size() != 1) {
throw new XMLException("There can be only one child node for root node: <response>");
}
// minimum one response element is present in xml
if (rootNode.getFirstChildElement("response") == null) {
throw new XMLException("The child node of <rest-client> should be <response>");
}
responseNode = rootNode.getFirstChildElement("response");
for (int i = 0; i < responseNode.getChildElements().size(); i++) {
tNode = responseNode.getChildElements().get(i);
String nodeName = tNode.getQualifiedName();
if ("execution-time".equals(nodeName)) {
responseBean.setExecutionTime(Long.parseLong(tNode.getValue()));
} else if ("status".equals(nodeName)) {
responseBean.setStatusLine(tNode.getValue());
responseBean.setStatusCode(Integer.parseInt(tNode.getAttributeValue("code")));
} else if ("headers".equals(nodeName)) {
Map<String, String> m = getHeadersFromHeaderNode(tNode);
for (String key : m.keySet()) {
responseBean.addHeader(key, m.get(key));
}
} else if ("body".equals(nodeName)) {
final String base64body = tNode.getValue();
responseBean.setResponseBody(Util.base64decodeByteArray(base64body));
} else if ("test-result".equals(nodeName)) {
TestResultBean testResultBean = new TestResultBean();
for (int j = 0; j < tNode.getChildCount(); j++) {
String nn = tNode.getQualifiedName();
if ("run-count".equals(nn)) {
throw new XMLException("<headers> element should contain only <header> elements");
} else if ("failure-count".equals(nn)) {
throw new XMLException("<headers> element should contain only <header> elements");
} else if ("error-count".equals(nn)) {
throw new XMLException("<headers> element should contain only <header> elements");
} else if ("failures".equals(nn)) {
throw new XMLException("<headers> element should contain only <header> elements");
} else if ("errors".equals(nn)) {
throw new XMLException("<headers> element should contain only <header> elements");
}
}
responseBean.setTestResult(testResultBean);
} else {
throw new XMLException("Unrecognized element found: <" + nodeName + ">");
}
}
return responseBean;
}
protected static void writeXML(final Document doc, final File f)
throws IOException, XMLException {
try {
OutputStream out = new FileOutputStream(f);
out = new BufferedOutputStream(out);
// getDocumentCharset(f) - to retrieve the charset encoding attribute
Serializer serializer = new Serializer(out, getDocumentCharset(f));
serializer.write(doc);
out.close();
} catch (IOException ex) {
throw new XMLException(ex.getMessage(), ex);
}
}
protected static Document getDocumentFromFile(final File f)
throws IOException, XMLException {
try {
Builder parser = new Builder();
Document doc = parser.build(f);
return doc;
}
catch (ParsingException | IOException ex) {
throw new XMLException(ex.getMessage(), ex);
}
}
public static String getDocumentCharset(final File f)
throws IOException, XMLException {
XMLEventReader reader = null;
try {
// using stax to get xml factory objects and read the input file
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
reader = inputFactory.createXMLEventReader(new FileInputStream(f));
XMLEvent event = reader.nextEvent();
// Always the first element is StartDocument
// even if the XML does not have explicit declaration:
StartDocument document = (StartDocument) event;
return document.getCharacterEncodingScheme();
}
catch (XMLStreamException ex) {
throw new XMLException(ex.getMessage(), ex);
}
finally{
if(reader != null){
try{
reader.close();
}
catch(XMLStreamException ex){
LOG.warning(ex.getMessage());
}
}
}
}
public static void writeRequestXML(final Request bean, final File f)
throws IOException, XMLException {
Document doc = request2XML(bean);
writeXML(doc, f);
}
public static void writeResponseXML(final Response bean, final File f)
throws IOException, XMLException {
Document doc = response2XML(bean);
writeXML(doc, f);
}
public static Request getRequestFromXMLFile(final File f)
throws IOException, XMLException {
Document doc = getDocumentFromFile(f);
return xml2Request(doc);
}
public static Response getResponseFromXMLFile(final File f)
throws IOException, XMLException {
Document doc = getDocumentFromFile(f);
return xml2Response(doc);
}
// Deprecation reason:
// http://code.google.com/p/rest-client/issues/detail?id=158
@Deprecated
public static String indentXML(final String in)
throws XMLException, IOException {
try {
Builder parser = new Builder();
Document doc = parser.build(in, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Serializer serializer = new Serializer(baos);
serializer.setIndent(4);
serializer.setMaxLength(69);
serializer.write(doc);
return new String(baos.toByteArray());
} catch (ParsingException ex) {
// LOG.log(Level.SEVERE, null, ex);
throw new XMLException("XML indentation failed.", ex);
}
}
}