package org.mitre.rhex;
import edu.umd.cs.findbugs.annotations.NonNull;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.output.XMLOutputter;
import org.mitre.test.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Test for document PUT operation
*
* <pre>
* 6.5 baseURL/sectionpath/documentname
*
* 6.5.2 PUT
*
* This operation is used to update a document by replacing it. The PUT operation
* MUST NOT be used to create a new document; new documents MUST be created by
* POSTing to the section. If the client attempts to create a new document this
* way [via PUT], the server MUST return a 404.
*
* The content MUST conform to the media type identified by the document metadata
* or the section content type. For media type application/xml, the document MUST
* also conform to the XML schema that corresponds to the content type identified
* by the document metadata or the section.
*
* If the parameter is incorrect or the content cannot be validated against
* the correct media type or the XML schema identified by the content type
* of this section, the server MUST return a status code of 400.
*
* If the request is successful, the new section document MUST show up in the
* document feed for the section. The server returns a 200.
*
* Status Code: <B>200</B>, 400, 404
* </pre>
*
* @author Jason Mathews, MITRE Corp.
* Date: 2/20/12 10:45 AM
*/
public class DocumentUpdate extends BaseXmlTest {
private String targetText;
public DocumentUpdate() {
setProperty(DocumentPutPreTest.class, PROP_KEEP_DOCUMENT_BOOL, true);
}
@NonNull
public String getId() {
return "6.5.2.1";
}
@Override
public boolean isRequired() {
return true;
}
@NonNull
public String getName() {
return "PUT operation to update document";
}
@NonNull
public List<Class<? extends TestUnit>> getDependencyClasses() {
return Collections.<Class<? extends TestUnit>> singletonList(DocumentPutPreTest.class); // 6.5.2.0
}
public void execute() throws TestException {
TestUnit dependTest = getDependency(DocumentPutPreTest.class);
if (dependTest == null) {
// assertion failed: this should never be null
log.error("Failed to retrieve prerequisite test");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test");
return;
}
DocumentPutPreTest baseTest = (DocumentPutPreTest) dependTest;
final URI baseURL = baseTest.getBaseURL();
if (baseURL == null) {
// check pre-conditions and setup
log.error("Failed to retrieve prerequisite test results");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test results: 6.5.2.0");
return;
}
final String xmlContent;
try {
xmlContent = getContent(baseTest);
if (xmlContent == null) {
System.out.println("URL=" + baseURL);
setStatus(StatusEnumType.SKIPPED, "Failed to fetch/update test document to perform test");
return;
}
System.out.println("\nUpdated XML content=\n" + xmlContent);
} catch (IOException e) {
System.out.println("URL=" + baseURL);
throw new TestException(e);
} catch (JDOMException e) {
System.out.println("URL=" + baseURL);
throw new TestException(e);
}
if (log.isDebugEnabled()) {
System.out.println("URL=" + baseURL);
}
final Context context = Loader.getInstance().getContext();
HttpClient client = context.getHttpClient();
try {
HttpPut request = createRequest(baseURL, xmlContent);
/*
HTTP Request:
- >> "PUT /records/1547/vital_signs/4f37e9a12a1002000400008b HTTP/1.1[\r][\n]"
- >> "Content-Length: 390[\r][\n]"
- >> "Content-Type: application/xml; charset=UTF-8[\r][\n]"
- >> "Host: hdata.herokuapp.com[\r][\n]"
- >> "Connection: Keep-Alive[\r][\n]"
- >> "User-Agent: Apache-HttpClient/4.1.3 (java 1.5)[\r][\n]"
- >> "[\r][\n]"
- >> "<?xml version="1.0" encoding="UTF-8"?>[\r][\n]"
- >> "<vitalSign xmlns="urn:hl7-org:greencda:c32">[\r][\n]"
- >> " <id>4f37e9a12a1002000400008b</id>[\r][\n]"
- >> " <code code="60621009" codeSystem="2.16.840.1.113883.6.96">[\r][\n]"
- >> " <originalText>BMI</originalText>[\r][\n]"
- >> " </code>[\r][\n]"
- >> " <status code="completed" />[\r][\n]"
- >> " <effectiveTime>2011-06-BaseUrlNotFoundTest27 04:00:00 +0000</effectiveTime>[\r][\n]"
- >> " <value amount="17.358024691358025" unit="" />[\r][\n]"
- >> "</vitalSign>[\r][\n]"
- >> "[\r][\n]"
*/
HttpResponse response = context.executeRequest(client, request);
// next verify change was accepted
if (validateContent(context, request, response, baseTest, baseURL))
setStatus(StatusEnumType.SUCCESS);
else
setStatus(StatusEnumType.FAILED);
} catch (UnsupportedEncodingException e) {
log.error("", e);
addWarning(e.getMessage());
setStatus(StatusEnumType.SKIPPED, "Unable to encode test document");
} catch (IOException e) {
throw new TestException(e);
} catch (JDOMException e) {
throw new TestException(e);
} finally {
client.getConnectionManager().shutdown();
}
}
protected HttpPut createRequest(URI baseURL, String xmlContent) {
HttpPut request = new HttpPut(baseURL);
StringEntity entity = new StringEntity(xmlContent, ContentType.APPLICATION_XML);
request.setEntity(entity);
return request;
}
protected String getContent(DocumentPutPreTest baseTest) throws IOException, JDOMException {
/*
expecting something like:
<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>
*/
Document doc = baseTest.getDocument();
if (doc == null) {
log.error("Failed to retrieve document from prerequisite test");
// setStatus(StatusEnumType.SKIPPED, "Failed to retrieve document from prerequisite test");
return null;
}
final Element rootElement = doc.getRootElement();
final Element effectiveTime = rootElement.getChild("effectiveTime", rootElement.getNamespace());
// System.out.println("target effectiveTime=" + effectiveTime);
if (effectiveTime != null) {
final Element start = effectiveTime.getChild("start", rootElement.getNamespace());
Attribute value = null;
/*
Look for effectiveTime field in one of 3 forms in input document:
1. <effectiveTime>2011-06-27 04:00:00 +0000</effectiveTime>
2. <effectiveTime>
<start value="2005-02-09 03:00:00 -0500" />
</effectiveTime>
3. <effectiveTime>
<start>2009-02-09 02:00:00 -0500</start>
<end></end>
</effectiveTime>
*/
String text;
if (start != null) {
value = start.getAttribute("value");
if (value != null) text = value.getValue(); // #2
else text = start.getTextTrim(); // #3
} else {
text = effectiveTime.getTextTrim(); // #1
}
System.out.println("target effectiveTime=" + text);
if (StringUtils.isBlank(text)) {
log.debug("Blank or empty time field");
return null;
}
if (text.length() == 14 && StringUtils.isNumeric(text)) {
// <start value="20100507060000" /></effectiveTime>
// YYYYMMDDHHMMSS
int hour = Integer.parseInt(text.substring(8, 10)) + 1 % 24;
targetText = String.format("%s%02d%s",
text.substring(0,8), hour,
text.substring(10));
log.debug("effectiveTime text\n\told: {}\n\tnew: {}", text, targetText);
} else {
//Pattern p = Pattern.compile("(\\d\\d\\d\\d)-"); // 2010-06-27 04:00:00 +0000
Pattern p = Pattern.compile("(\\S+) (\\d\\d):"); // 2010-06-27 *04*:00:00 +0000 -- select hour field
Matcher m = p.matcher(text);
if (!m.lookingAt()) {
log.warn("Failed to match target date pattern");
// log.debug("effectiveTime text={}", text);
return null; // TODO try alternative update strategy if applicable
}
// System.out.println("match " + m.group(1));
// increment the hour field
try {
targetText = String.format("%s %02d:%s",
m.group(1),
(Integer.parseInt(m.group(2)) + 1) % 24,
text.substring(m.end()));
} catch (NumberFormatException nfe) {
log.debug("Failed to parse hour field", nfe);
return null;
}
}
// increment the year field
// targetText = (Integer.parseInt(m.group(1)) + 1) + text.substring(4);
if (start != null) {
if (value != null) value.setValue(targetText); // #2
else start.setText(targetText); // #3
} else
effectiveTime.setText(targetText); // #1
// System.out.println("targetText=" + effectiveTime.getText());
XMLOutputter xo = new XMLOutputter();
xo.setFormat(org.jdom.output.Format.getPrettyFormat());
return xo.outputString(doc);
} // TODO try alternative update strategy if applicable
return null;
}
protected boolean validateContent(Context context, HttpPut request, HttpResponse response, DocumentPutPreTest baseTest, URI baseURL)
throws JDOMException, IOException, TestException
{
int code = response.getStatusLine().getStatusCode();
if (code != 200 || log.isDebugEnabled()) {
dumpResponse(request, response);
}
// check return code
assertEquals(200, code);
Document doc = getXmlDocument(context, baseURL);
if (doc == null) return false;
final Element rootElement = doc.getRootElement();
final Element effectiveTime = rootElement.getChild("effectiveTime", rootElement.getNamespace());
// final String effectiveTime = rootElement.getChildTextTrim("effectiveTime", rootElement.getNamespace());
if (effectiveTime == null) {
log.debug("Cannot find effectiveTime element");
return false;
}
final Element start = effectiveTime.getChild("start", rootElement.getNamespace());
String elementValue;
if (start != null) {
Attribute value = start.getAttribute("value");
if (value != null) elementValue = value.getValue(); // #2
else elementValue = start.getText(); // #3
} else {
// <effectiveTime>2011-06-27 04:00:00 +0000</effectiveTime>
elementValue = effectiveTime.getText(); // #1
}
assertEquals(targetText, elementValue);
return true;
}
}