package org.mitre.rhex;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.lang.StringUtils;
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.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.mitre.test.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* Test for section ATOM feeds
*
* <pre>
* 6.4 baseURL/sectionpath
*
* 6.4.1 GET
*
* This operation MUST return an Atom 1.0 compliant feed of all section documents and child sections contained in this
* section. Each entry MUST contain a link to a resource that uniquely identifies the section document or child section.
*
* If the section document type defines a creation time, is RECOMMENDED to set the Created node to that datetime.
* For section documents, the Atom Content element MUST contain the XML representation of its metadata (see Section 2.4.1).
*
* Status Code: 200
* </pre>
*
* @author Jason Mathews, MITRE Corp.
* Date: 2/20/12 10:45 AM
*/
public class BaseSectionFromRootXml extends BaseXmlTest {
private final List<String> sectionList = new ArrayList<String>();
/**
* Map of each section ATOM feed by section path
*/
private final Map<String,Document> documentMap = new HashMap<String, Document>();
private boolean keepSectionDocs;
// property
public static final String PROP_KEEP_SECTION_DOM_BOOL = "keepSectionDocs";
public BaseSectionFromRootXml() {
// forces BaseUrlRootXml test to keep its Document object after it executes
setProperty(BaseUrlRootXml.class, BaseUrlRootXml.PROP_KEEP_DOCUMENT_BOOL, Boolean.TRUE);
}
@NonNull
public String getId() {
return "6.4.1.1";
}
@NonNull
public String getName() {
return "GET operation MUST return an Atom 1.0 compliant feed of all section documents and child sections contained in this section";
}
@Override
public boolean isRequired() {
return true; // implied MUST
}
@NonNull
public List<Class<? extends TestUnit>> getDependencyClasses() {
return Collections.<Class<? extends TestUnit>> singletonList(BaseUrlRootXml.class); // 6.3.1.1
}
public void execute() throws TestException {
// pre-conditions: for this test to be executed the prerequisite test BaseUrlOptions must have passed
// with 200 HTTP response and a valid root.xml doc
TestUnit baseTest = getDependency(BaseUrlRootXml.class);
if (baseTest == null) {
// assertion failed: this should never be null
log.error("Failed to retrieve prerequisite test");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test");
return;
}
Document doc = ((BaseUrlRootXml)baseTest).getDocument();
if (doc == null) {
log.error("Failed to retrieve prerequisite test");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test results");
return;
}
/*
expecting:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://projecthdata.org/hdata/schemas/2009/06/core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
<sections>
<section path="c32" name="C32" extensionId="1"/>
<section path="allergies" name="Allergies" extensionId="2"/>
<section path="care_goals" name="Care Goals" extensionId="3"/>
<section path="conditions" name="Conditions" extensionId="4"/>
<section path="encounters" name="Encounters" extensionId="5"/>
<section path="immunizations" name="Immunizations" extensionId="6"/>
<section path="medical_equipment" name="Medical Equipment" extensionId="7"/>
<section path="medications" name="Medications" extensionId="8"/>
<section path="procedures" name="Procedures" extensionId="9"/>
<section path="results" name="Lab Results" extensionId="10"/>
<section path="social_history" name="Social History" extensionId="11"/>
<section path="vital_signs" name="Vital Signs" extensionId="10"/>
</sections>
*/
final Namespace ns = Namespace.getNamespace(NAMESPACE_HDATA_SCHEMAS_2009_06_CORE);
Element sections = doc.getRootElement().getChild("sections", ns);
if (sections == null) {
log.warn("rootXML has no sections defined");
setStatus(StatusEnumType.SKIPPED, "rootXML has no sections defined");
return;
}
final Context context = Loader.getInstance().getContext();
// TODO possibly move sections to Context for dependent tests for documents ??
//final List<String> sectionList = context.getSectionList();
try {
for(Object child : sections.getChildren("section", ns)) {
if (!(child instanceof Element)) continue;
Element e = (Element)child;
String path = e.getAttributeValue("path"); // required
if (StringUtils.isNotBlank(path)) {
System.out.println("XXX: path=" + path); // debug
if (!checkSection(context, path)) {
// test failed in last section
return;
}
sectionList.add(path); // added validated sections to the list
}
}
} catch (URISyntaxException e) {
throw new TestException(e);
} catch (IOException e) {
throw new TestException(e);
} catch (JDOMException e) {
throw new TestException(e);
}
if (sectionList.isEmpty()) {
log.warn("rootXML has no sections defined");
setStatus(StatusEnumType.SKIPPED, "rootXML has no sections defined");
} else {
setStatus(StatusEnumType.SUCCESS);
}
}
private boolean checkSection(Context context, String path) throws URISyntaxException, IOException, JDOMException, TestException {
URI baseURL = context.getBaseURL(path);
HttpClient client = context.getHttpClient();
try {
System.out.println("\nGET URL=" + baseURL);
HttpGet req = new HttpGet(baseURL);
req.setHeader("Accept", "application/atom+xml, application/xml, text/xml");
HttpResponse response = context.executeRequest(client, req);
int code = response.getStatusLine().getStatusCode();
if (code != 200 || log.isDebugEnabled()) {
dumpResponse(req, response, code != 200);
}
return validateContent(code, path, context, response);
} finally {
client.getConnectionManager().shutdown();
}
}
private boolean validateContent(int code, String path, Context context, HttpResponse response)
throws TestException, IOException, JDOMException {
if (code != 200) {
setStatus(StatusEnumType.FAILED, "Unexpected HTTP response: " + code);
return false;
}
final HttpEntity entity = response.getEntity();
if (entity == null) {
// no body
log.info("no BODY in response for section: " + path);
addWarning("encountered non-body response to section request");
return true;
}
final String contentType = ClientHelper.getContentType(entity);
// content-type = text/xml OR application/xml
if (!MIME_APPLICATION_ATOM_XML.equals(contentType)) {
addLogWarning("Expected " + MIME_APPLICATION_ATOM_XML + " content-type for section but was: " + contentType);
}
long len = entity.getContentLength();
// minimum length expected is 43 bytes or a negative number if unknown
// assertTrue(len < 0 || len >= 66, "Expecting valid XML document for baseURL/root.xml; returned length was " + len);
if (len > 43) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
entity.writeTo(bos);
/*
expecting:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
...
</feed>
*/
Document doc = getValidatingAtom(context, bos);
// assertTrue(xmlErrors == 0, "Content has errors in ATOM feed"); // leave as warning for now
final Element root = doc.getRootElement();
assertEquals(NAMESPACE_W3_ATOM_2005, root.getNamespace().getURI());
if (keepSectionDocs) {
documentMap.put(path, doc);
}
} else log.warn("section content length=" + len + ", expecting len > 43 bytes");
return true;
}
/**
* Set property on this test.
*
* @param key
* @param value
* @exception ClassCastException if target type does not match expected type typically indicated
* as ending of the property name constant (e.g. PROP_KEEP_SECTION_DOM_BOOL)
*/
public void setProperty(String key, Object value) {
if (PROP_KEEP_SECTION_DOM_BOOL.equals(key))
keepSectionDocs = (Boolean)value;
else super.setProperty(key, value);
}
public void cleanup() {
super.cleanup();
if (!keepSectionDocs || getStatus() != StatusEnumType.SUCCESS) {
documentMap.clear();
}
}
/**
* Get Map of section ATOM feeds by section path as DOM objects
* @return Map
*/
@NonNull
public Map<String, Document> getDocumentMap() {
return documentMap;
}
/**
* Get list of sections as defined in root.xml
* @return section list
*/
@NonNull
public List<String> getSectionList() {
return sectionList;
}
}