package org.mitre.test;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
/**
* @author Jason Mathews, MITRE Corp.
*
* Date: 2/22/12 5:56 PM
*/
public abstract class BaseXmlTest extends BaseTest implements ErrorHandler {
private static final String ISO_DATE_FMT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
private SimpleDateFormat dateFormatter;
private Document document;
protected boolean keepDocument;
// property
public static final String PROP_KEEP_DOCUMENT_BOOL = "keepDocument";
// private int xmlWarnings;
protected int xmlErrors;
protected boolean fatalXmlError;
public Document getDocument() {
return document;
}
public void setDocument(Document doc) {
this.document = doc;
}
public void warning(SAXParseException exception) throws SAXException {
// xmlWarnings++;
if (log.isDebugEnabled()) {
log.debug("WARN: " + exception.getMessage());
}
// System.out.println("WARN: " + ); // debug
}
public void error(SAXParseException exception) throws SAXException {
xmlErrors++;
final String s = exception.getMessage();
addLogWarning(s);
//addWarning(s);
//log.debug("WARN: {}", s);
// System.out.println("ERROR: " + s);
}
public void fatalError(SAXParseException exception) throws SAXException {
xmlErrors++;
fatalXmlError = true;
final String s = exception.getMessage();
addWarning(s);
//System.out.println("ERROR: " + s);
log.error(s);
}
protected SimpleDateFormat getDateFormatter() {
if (dateFormatter == null) {
dateFormatter = new SimpleDateFormat(ISO_DATE_FMT);
}
return dateFormatter;
}
protected Document getDefaultDocument(Context context, ByteArrayOutputStream bos)
throws JDOMException, IOException
{
return context.getBuilder(this).build(new ByteArrayInputStream(bos.toByteArray()));
}
/**
* Get DOM from byte-array using validating parser if able to rewrite XML
* with target namespace schema location and schema location otherwise
* builds DOM from a non-validating parser.
*
* @param context Context
* @param bos <code>ByteArrayOutputStream</code> to read from
* @param rootElement Expected root element in XML document with start tag '<' but no end tag (e.g. "<feed")
* @param namespaceUri Target namespace URI
* @param namespaceLocation Local location for XML Schema file
* @return <code>Document</code> resultant Document object
*
* @exception JDOMException when errors occur in parsing
* @exception IOException when an I/O error prevents a document
* from being fully parsed.
*/
protected Document getValidatingParser(Context context, ByteArrayOutputStream bos, String rootElement,
String namespaceUri, String namespaceLocation)
throws IOException, JDOMException
{
String content = bos.toString("UTF-8");
if (log.isDebugEnabled()) {
System.out.println("Content:\n" + content); // debug
}
int ind = content.indexOf(rootElement); // find starting position of the root element (e.g. "<feed")
if (ind >= 0) {
int endp = content.indexOf('>', ind + 5);
if (endp > ind) {
//boolean changed = false;
// rewrite XML and add schema location to XML document to force schema validation against target ATOM XSD
// insert xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/2005/Atom file:/C:/xml/atom.xsd"
//if (endp > ind) {
// String xml = content.substring(ind, endp);
// System.out.println();
StringBuilder buf = new StringBuilder();
if (ind > 0) buf.append(content.substring(0, ind));
buf.append(rootElement);
if (!content.substring(ind, endp).contains("xmlns:xsi=")) {
// changed = true;
buf.append(" xmlns:xsi=\"").append(NAMESPACE_W3_XMLSchema_INSTANCE).append("\"\n");
}
if (content.contains(namespaceUri)) {
final File file = new File(namespaceLocation);
if (file.exists()) {
//changed = true;
buf.append(" xsi:schemaLocation=\"").append(namespaceUri).append(' ').
append(file.toURI().toASCIIString()).append("\"\n");
buf.append(content.substring(ind + 5));
content = buf.toString();
// System.out.println(content);
log.trace("Use validating XML parser");
return context.getValidatingBuilder(this).build(new ByteArrayInputStream(content.getBytes("UTF-8")));
}
} // otherwise target namespace URI not found in document
/*
if (changed) {
buf.append(content.substring(ind + 5));
content = buf.toString();
System.out.println(content);
log.debug("Use validating XML parser");
return context.getValidatingBuilder(this).build(new ByteArrayInputStream(content.getBytes("UTF-8")));
//} else {
// return context.getValidatingBuilder(this).build(new ByteArrayInputStream(bos.toByteArray()));
}
*/
}
}
return getDefaultDocument(context, bos);
}
protected Document getValidatingAtom(Context context, ByteArrayOutputStream bos)
throws IOException, JDOMException
{
return getValidatingParser(context, bos, "<feed", NAMESPACE_W3_ATOM_2005, "schemas/atom.xsd");
}
@CheckForNull
public Document getXmlDocument(Context context, URI baseURL)
throws IOException, JDOMException
{
HttpClient client = context.getHttpClient();
try {
HttpGet req = new HttpGet(baseURL);
// Accept definition -> http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
req.setHeader("Accept", MIME_APPLICATION_XML);
// req.setHeader("If-Modified-Since", "Tue, 28 Feb 2012 14:33:15 GMT");
if (log.isDebugEnabled()) {
System.out.println("\nURL: " + req.getURI());
for(Header header : req.getAllHeaders()) {
System.out.println("\t" + header.getName() + ": " + header.getValue());
}
}
HttpResponse response = context.executeRequest(client, req);
int code = response.getStatusLine().getStatusCode();
if (code != 200 || log.isDebugEnabled()) {
if (!log.isDebugEnabled()) {
System.out.println("\nURL: " + req.getURI());
}
dumpResponse(req, response);
}
if (code != 200) {
addLogWarning("Unexpected HTTP response: " + code);
return null;
}
final HttpEntity entity = response.getEntity();
if (entity == null) {
addLogWarning("Expect XML in body of response");
return null;
}
final String contentType = ClientHelper.getContentType(entity, false);
// content-type = text/xml OR application/xml
if (!MIME_TEXT_XML.equals(contentType) && !MIME_APPLICATION_XML.equals(contentType)) {
addLogWarning("Expected supported XML content-type but was: " + contentType);
return null;
}
long len = entity.getContentLength();
// minimum length expected is 66 bytes or a negative number if unknown
if (len <= 0) {
addLogWarning("Expecting valid XML document; returned length was " + len);
return null;
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
entity.writeTo(bos);
if (log.isDebugEnabled()) {
System.out.println("Content=\n" + bos.toString("UTF-8"));
}
/*
typical document:
<vitalSign xmlns="urn:hl7-org:greencda:c32">
<id>4f37e9a12a1002000400008b</id>
<code code="60621009" codeSystem="2.16.840.1.113883.6.96" ><originalText>BMI</originalText></code>
<status code="completed"/>
<effectiveTime>2010-06-27 04:00:00 +0000</effectiveTime>
<value amount="17.358024691358025" unit="" />
</vitalSign>
*/
return getDefaultDocument(context, bos);
} finally {
client.getConnectionManager().shutdown();
}
}
protected Document getSectionAtomDocument(Context context, String sectionPath) {
try {
URI baseUrl = context.getBaseURL(sectionPath);
return getSectionAtomDocument(context, baseUrl);
} catch (URISyntaxException e) {
log.warn("", e);
return null;
}
}
protected Document getSectionAtomDocument(Context context, URI sectionPathUri) {
final HttpClient client = context.getHttpClient();
try {
if (log.isDebugEnabled()) {
System.out.println("\nSection URL: " + sectionPathUri);
}
HttpGet req = new HttpGet(sectionPathUri);
req.setHeader("Accept", MIME_APPLICATION_XML); // "application/atom+xml, text/xml, application/xml");
System.out.println("executing request: " + req.getRequestLine());
HttpResponse response = context.executeRequest(client, req);
int code = response.getStatusLine().getStatusCode();
final HttpEntity entity = response.getEntity();
if (log.isDebugEnabled()) {
System.out.println("----------------------------------------");
System.out.println("GET Response: " + response.getStatusLine());
for (Header header : response.getAllHeaders()) {
System.out.println("\t" + header.getName() + ": " + header.getValue());
}
}
if (code != 200) {
log.warn("Expected 200 HTTP status code for section atom feed but was: " + code);
return null;
}
if (entity == null) {
log.warn("no BODY in response for section feed"); // no body
return null;
}
final String contentType = ClientHelper.getContentType(entity);
if (!MIME_APPLICATION_ATOM_XML.equals(contentType)) {
addLogWarning("Expected " + MIME_APPLICATION_ATOM_XML + " content-type for section but was: " + contentType);
}
/*
example section ATOM feed:
<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>1333458159</id>
<title>/vital_signs</title>
<generator version="1.0">atom feed generator</generator>
<entry>
<id>1</id>
<title>Systolic Blood Pressure</title>
<updated>1292389200</updated>
<link href="http://rhex.mitre.org:3000/records/1/vital_signs/4f735368d7d76a43b200001f" type="application/xml"/>
<link href="http://rhex.mitre.org:3000/records/1/vital_signs/4f735368d7d76a43b200001f" type="application/json"/>
</entry>
...
*/
ByteArrayOutputStream bos = new ByteArrayOutputStream();
entity.writeTo(bos);
return getDefaultDocument(context, bos);
} catch (IOException e) {
log.warn("", e);
} catch (JDOMException e) {
log.warn("", e);
} finally {
client.getConnectionManager().shutdown();
}
return null;
}
/**
* Set property on this test.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @exception ClassCastException if target type does not match expected type typically indicated
* as ending of the property name constant (e.g. PROP_KEEP_DOCUMENT_BOOL)
*/
public void setProperty(String key, Object value) {
// System.out.printf("XXX: setProperty %s: %s%n", key, value);
if (PROP_KEEP_DOCUMENT_BOOL.equals(key))
keepDocument = (Boolean)value;
else super.setProperty(key, value);
}
public void cleanup() {
super.cleanup();
if (!keepDocument) {
// if doc is non-null then status is SUCCESS
// status = FAILED => doc = null
document = null;
}
}
}