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.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.util.EntityUtils;
import org.mitre.test.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
/**
* Test for section document creation
*
* <pre>
* 6.4 baseURL/sectionpath
*
* 6.4.2.2 POST Add new document
*
* When adding a new section document, the request Content Type MUST be �multipart/form-data�
* if including metadata. In this case, the content part MUST contain the section document.
* The content part MUST include a Content-Disposition header with a disposition of �form-data�
* and a name of �content.� The metadata part MUST contain the metadata for this section document.
* The metadata part MUST include a Content-Disposition header with a disposition of �form-data�
* and a name of �metadata.� It is to be treated as informational, since the service MUST compute
* the valid new metadata based on the requirements found in the HRF specification. The content
* media type MUST conform to the media type of either the section or the media type identified
* by metadata of the section document. For XML media types, the document MUST also conform to
* the XML schema identified by the extensionId for the section or the document metadata.
*
* If the content cannot be validated against the media type and 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 201 with a Location header containing
* the URI of the new document.
*
* Status Code: 201, 400
* </pre>
*
* @author Jason Mathews, MITRE Corp.
* Date: 2/20/12 10:45 AM
*/
public class DocumentCreate extends BaseXmlTest {
protected String sectionPath;
private URI documentURL;
@NonNull
@Override
public String getId() {
return "6.4.2.2";
}
@Override
public boolean isRequired() {
return true;
}
@NonNull
public String getName() {
return "POST operation on baseURL/sectionpath adds a new Document";
}
@NonNull
public List<Class<? extends TestUnit>> getDependencyClasses() {
return Collections.<Class<? extends TestUnit>> singletonList(BaseSectionFromRootXml.class); // 6.4.1.1
}
public void execute() throws TestException {
// pre-conditions: for this test to be executed the prerequisite test BaseSectionFromRootXml must have passed
// with 200 HTTP response and valid root.xml content.
TestUnit baseTest = getDependency(BaseSectionFromRootXml.class);
if (baseTest == null) {
// assertion failed: this should never be null
log.error("Failed to retrieve prerequisite test: BaseSectionFromRootXml");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test: 6.4.1.1");
return;
}
List<String> sections = ((BaseSectionFromRootXml)baseTest).getSectionList();
if (sections.isEmpty()) {
log.error("Failed to retrieve prerequisite test results");
setStatus(StatusEnumType.SKIPPED, "Failed to retrieve prerequisite test results: 6.4.1.1");
return;
}
/*
expecting list of section paths from the root.xml document
<?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">
<extensions>
<extension extensionId="1">http://projecthdata.org/extension/c32</extension>
<extension extensionId="2">http://projecthdata.org/hdata/schemas/2009/06/allergy</extension>
...
</extensions>
<sections>
<section path="c32" name="C32" extensionId="1"/>
<section path="allergies" name="Allergies" extensionId="2"/>
...
</sections>
</root>
*/
final Context context = Loader.getInstance().getContext();
sectionPath = context.getString("updateDocument.section");
if (StringUtils.isBlank(sectionPath)) {
// check pre-conditions and setup
log.error("Failed to specify valid updateDocument/section property in configuration");
setStatus(StatusEnumType.SKIPPED, "Failed to specify valid updateDocument/section property in configuration");
return;
}
if (!sections.contains(sectionPath)) {
// test pre-conditions
log.error("Failed to find section " + sectionPath + " in test results");
log.debug("sections: {}", sections);
setStatus(StatusEnumType.SKIPPED, "Failed to find section in test results");
return;
}
System.out.println("section path: " + sectionPath);
sendRequest(context);
}
protected void sendRequest(Context context) throws TestException {
File fileToUpload = context.getPropertyAsFile("updateDocument.file");
if (fileToUpload == null) {
// check pre-conditions and setup
log.error("Failed to specify valid document file property in configuration");
setStatus(StatusEnumType.SKIPPED, "Failed to specify valid document file property in configuration");
return;
}
final HttpClient client = context.getHttpClient();
try {
final URI baseUrl = context.getBaseURL(sectionPath);
if (log.isDebugEnabled()) {
System.out.println("\nURL: " + baseUrl);
System.out.println("file: " + fileToUpload);
}
FileEntity reqEntity = new FileEntity(fileToUpload, ContentType.APPLICATION_XML);
/*
MultipartEntity reqEntity = new MultipartEntity();
FileBody fileBody = new FileBody(fileToUpload, MIME_APPLICATION_XML);
reqEntity.addPart("content", fileBody);
// reqEntity.addPart("metadata", new StringBody(fileToUpload.getName())); // should be a separate XML profile file ??
*/
HttpPost post = new HttpPost(baseUrl);
post.setEntity(reqEntity);
System.out.println("executing request: " + post.getRequestLine());
HttpResponse response = context.executeRequest(client, post);
int code = response.getStatusLine().getStatusCode();
if (log.isDebugEnabled()) {
System.out.println("----------------------------------------");
dumpResponse(post, response, true);
}
/*
Expected response:
HTTP/1.1 201 Created
Location: http://rhex.mitre.org:3000/records/4f735367d7d76a43b2000001/vital_signs/4f7af4efd7d76a292600081a
Content-Type: text/html; charset=utf-8
X-UA-Compatible: IE=Edge
ETag: "56f5afbd5beb6b227c829c06b582dc0c"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: _hdata-server_session=xxx...; path=/; HttpOnly
X-Request-Id: a0da7b61ce2c04a6b61fc17005746f60
X-Runtime: 0.032346
Content-Length: 24
Connection: keep-alive
Server: thin 1.3.1 codename Triple Espresso
*/
if (code != 201) {
if (!log.isDebugEnabled()) {
System.out.println("\nURL: " + baseUrl);
dumpResponse(post, response, true);
}
setStatus(StatusEnumType.FAILED, "Expected 201 HTTP status code but was: " + code);
return;
}
/*
Validate document at location exists
Subsequent GET to retrieve document:
GET /records/4f735367d7d76a43b2000001/vital_signs/4f7af4efd7d76a292600081a HTTP/1.1
Accept: application/xml
Host: rhex.mitre.org:3000
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.3 (java 1.5)
Cookie: _hdata-server_session=xxx...
Cookie2: $Version=1
*/
validateResponse(context, response);
// NOTE: verify document is added to the section ATOM feed in another test
// retrieve & store section ATOM feed for follow-on tests
setDocument(getSectionAtomDocument(context, baseUrl));
setStatus(StatusEnumType.SUCCESS);
setResponse(response);
} catch (IOException e) {
throw new TestException(e);
} catch (URISyntaxException e) {
throw new TestException(e);
} finally {
client.getConnectionManager().shutdown();
}
}
private void validateResponse(Context context, HttpResponse postResponse)
throws TestException, URISyntaxException, IOException
{
Header header = postResponse.getFirstHeader("Location");
if (header == null) {
fail("Expected Location header in response");
return;
}
String location = header.getValue();
assertTrue(StringUtils.isNotBlank(location), "Expected non-empty value for Location");
//=========================================================
// debug start -- workaround for bug
int ind = location.lastIndexOf('/');
// Location: http://rhex.mitre.org:3000/records/4f735367d7d76a43b2000001/vital_signs/4f7c90a2d7d76a2926000b7f
// => http://rhex.mitre.org:3000/records/1/vital_signs/4f7c90a2d7d76a2926000b7f
// [java] <link href="http://rhex.mitre.org:3000/records/1/vital_signs/4f7c90a2d7d76a2926000b7f" type="application/xml"/>
if (ind > 0) {
String oldvalue = location;
location = context.getBaseURL(sectionPath + location.substring(ind)).toASCIIString();
// log.debug("XXX: Location {}", location);
postResponse.setHeader(header.getName(), location);
if (!oldvalue.equals(location)) {
log.warn("required to rewrite invalid location");
log.debug("XXX: rewrite bogus Location {} -> {}", oldvalue, location);
addWarning("invalid location value requires rewrite");
}
}
// debug end
//=========================================================
final HttpClient client = context.getHttpClient();
try {
documentURL = new URI(location);
if ("localhost".equals(documentURL.getHost()) || !documentURL.isAbsolute()) {
addWarning("Invalid document URL " + documentURL);
return;
}
// verifies can get the created document
HttpGet req = new HttpGet(documentURL);
req.setHeader("Accept", MIME_APPLICATION_XML);
System.out.println("executing request: " + req.getRequestLine());
HttpResponse getResponse = context.executeRequest(client, req);
int code = getResponse.getStatusLine().getStatusCode();
if (code != 200 || log.isDebugEnabled()) {
System.out.println("----------------------------------------");
dumpResponse(req, getResponse, false);
}
//assertEquals(200, code);
if (code != 200) {
addWarning("Expected 200 HTTP status code but was: " + code);
}
String content = context.getString("updateDocument.content");
HttpEntity entity = getResponse.getEntity();
if (entity == null) {
if (content != null) addLogWarning("Failed to verify content: no entity");
else addLogWarning("Unable to parse body content");
return;
}
// verifies actual document contents matches
try {
String bodyText = EntityUtils.toString(entity);
boolean dump = log.isDebugEnabled();
if (content != null) {
if (bodyText != null && bodyText.contains(content)) {
log.debug("created document contains target: {}", content);
} else {
addWarning("Created document does not match target content");
log.warn("Created document does not match target content: {}", content);
dump = true;
}
}
if (dump) {
System.out.println("----------------------------------------");
if (bodyText != null && bodyText.length() > 68)
System.out.println("Response body:");
else
System.out.print("Response body: ");
System.out.println(bodyText);
}
} catch(Exception e) {
log.warn("Failed to parse body content", e);
}
} finally {
client.getConnectionManager().shutdown();
}
}
public String getSectionPath() {
return sectionPath;
}
public URI getDocumentURL() {
return documentURL;
}
}