Improve error messages for invalid validate request

This commit is contained in:
James Agnew 2016-05-29 22:23:30 -04:00
parent a306e3136f
commit f45ad117fe
16 changed files with 1212 additions and 31 deletions

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.model.api;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.CoverageIgnore;
public abstract class BaseIdentifiableElement extends BaseElement implements IIdentifiableElement {
@ -36,6 +37,7 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #getElementSpecificId()} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Deprecated
@Override
public IdDt getId() {
@ -55,6 +57,7 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #setElementSpecificId(String)} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Deprecated
@Override
public void setId(IdDt theId) {
@ -69,27 +72,33 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #setElementSpecificId(String)} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Override
@Deprecated
public void setId(String theId) {
myElementSpecificId = theId;
}
@CoverageIgnore
private static class LockedId extends IdDt {
@CoverageIgnore
public LockedId() {
}
@CoverageIgnore
public LockedId(String theElementSpecificId) {
super(theElementSpecificId);
}
@Override
@CoverageIgnore
public IdDt setValue(String theValue) throws DataFormatException {
throw new UnsupportedOperationException("Use IElement#setElementSpecificId(String) to set the element ID for an element");
}
@Override
@CoverageIgnore
public void setValueAsString(String theValue) throws DataFormatException {
throw new UnsupportedOperationException("Use IElement#setElementSpecificId(String) to set the element ID for an element");
}

View File

@ -1,5 +1,9 @@
package ca.uhn.fhir.rest.api;
import java.util.HashMap;
import org.apache.commons.lang3.Validate;
/*
* #%L
* HAPI FHIR - Core Library
@ -27,18 +31,41 @@ public enum ValidationModeEnum {
/**
* The server checks the content, and then checks that the content would be acceptable as a create (e.g. that the content would not validate any uniqueness constraints)
*/
CREATE,
CREATE("create"),
/**
* The server checks the content, and then checks that it would accept it as an update against the nominated specific resource (e.g. that there are no changes to immutable fields the server does not allow to change, and checking version integrity if appropriate)
*/
UPDATE,
UPDATE("update"),
/**
* The server ignores the content, and checks that the nominated resource is allowed to be deleted (e.g. checking referential integrity rules)
*/
DELETE;
DELETE("delete");
private static HashMap<String, ValidationModeEnum> myCodeToValue;
private String myCode;
static {
myCodeToValue = new HashMap<String, ValidationModeEnum>();
for (ValidationModeEnum next : values()) {
myCodeToValue.put(next.getCode(), next);
}
}
public static ValidationModeEnum forCode(String theCode) {
Validate.notBlank(theCode, "theCode must not be blank");
return myCodeToValue.get(theCode);
}
public String getCode() {
return myCode;
}
private ValidationModeEnum(String theCode) {
myCode = theCode;
}
// @Override
// public boolean isEmpty() {
// return false;

View File

@ -498,12 +498,20 @@ public class MethodUtil {
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IConverter() {
@Override
public Object incomingServer(Object theObject) {
return ValidationModeEnum.valueOf(theObject.toString().toUpperCase());
if (isNotBlank(theObject.toString())) {
ValidationModeEnum retVal = ValidationModeEnum.forCode(theObject.toString());
if (retVal == null) {
OperationParameter.throwInvalidMode(theObject.toString());
}
return retVal;
} else {
return null;
}
}
@Override
public Object outgoingClient(Object theObject) {
return new StringDt(((ValidationModeEnum)theObject).name().toLowerCase());
return new StringDt(((ValidationModeEnum)theObject).getCode());
}
});
} else if (nextAnnotation instanceof Validate.Profile) {

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -179,7 +181,7 @@ public class OperationParameter implements IParameter {
*/
isSearchParam &= typeIsConcrete && !IBase.class.isAssignableFrom(myParameterType);
myAllowGet = IPrimitiveType.class.isAssignableFrom(myParameterType) || String.class.equals(myParameterType) || isSearchParam;
myAllowGet = IPrimitiveType.class.isAssignableFrom(myParameterType) || String.class.equals(myParameterType) || isSearchParam || ValidationModeEnum.class.equals(myParameterType);
/*
* The parameter can be of type string for validation methods - This is a bit weird. See ValidateDstu2Test. We
@ -291,7 +293,17 @@ public class OperationParameter implements IParameter {
for (String next : paramValues) {
matchingParamValues.add(next);
}
} else if (ValidationModeEnum.class.equals(myParameterType)) {
if (isNotBlank(paramValues[0])) {
ValidationModeEnum validationMode = ValidationModeEnum.forCode(paramValues[0]);
if (validationMode != null) {
matchingParamValues.add(validationMode);
} else {
throwInvalidMode(paramValues[0]);
}
}
} else {
for (String nextValue : paramValues) {
FhirContext ctx = theRequest.getServer().getFhirContext();
@ -374,6 +386,10 @@ public class OperationParameter implements IParameter {
}
}
public static void throwInvalidMode(String paramValues) {
throw new InvalidRequestException("Invalid mode value: \"" + paramValues + "\"");
}
@SuppressWarnings("unchecked")
private void tryToAddValues(List<IBase> theParamValues, List<Object> theMatchingParamValues) {
for (Object nextValue : theParamValues) {
@ -436,4 +452,5 @@ public class OperationParameter implements IParameter {
}
}

View File

@ -26,6 +26,7 @@ import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.util.CoverageIgnore;
/**
* Utility methods for working with {@link IBundleProvider}
@ -33,6 +34,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
public class BundleProviders {
/** Non instantiable */
@CoverageIgnore
private BundleProviders() {
//nothing
}

View File

@ -45,6 +45,7 @@ import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
@ -138,7 +139,7 @@ public class UploadTerminologyCommand extends BaseCommand {
IBaseParameters inputParameters;
if (ctx.getVersion().getVersion() == FhirVersionEnum.DSTU3) {
Parameters p = new Parameters();
p.addParameter().setName("url").setValue(new StringType(termUrl));
p.addParameter().setName("url").setValue(new UriType(termUrl));
p.addParameter().setName("localfile").setValue(new StringType(datafile));
inputParameters = p;
} else {
@ -154,6 +155,7 @@ public class UploadTerminologyCommand extends BaseCommand {
.execute();
ourLog.info("Upload complete!");
ourLog.info("Response:\n{}", ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
}
}

View File

@ -1428,16 +1428,6 @@ public class SearchBuilder {
return singleCode;
}
private Set<String> toCodes(Set<TermConcept> theCodeConcepts) {
HashSet<String> retVal = Sets.newHashSet();
for (TermConcept next : theCodeConcepts) {
if (isNotBlank(next.getCode())) {
retVal.add(next.getCode());
}
}
return retVal;
}
private Predicate createResourceLinkPathPredicate(String theParamName, Root<? extends ResourceLink> from) {
return createResourceLinkPathPredicate(myContext, theParamName, from, myResourceType);
}

View File

@ -131,7 +131,9 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
validator.registerValidatorModule(new IdChecker(theMode));
ValidationResult result;
if (isNotBlank(theRawResource)) {
if (theResource == null) {
throw new InvalidRequestException("No resource supplied for $validate operation (resource is required unless mode is \"delete\")");
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else {
result = validator.validateWithResult(theResource);

View File

@ -217,12 +217,13 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
}
}
ourLog.info("Validating code system");
ourLog.info("Validating all codes in CodeSystem for storage (this can take some time for large sets)");
// Validate the code system
IdentityHashMap<TermConcept, Object> conceptsStack = new IdentityHashMap<TermConcept, Object>();
int totalCodeCount = 0;
for (TermConcept next : theCodeSystem.getConcepts()) {
validateConceptForStorage(next, theCodeSystem, conceptsStack);
totalCodeCount += validateConceptForStorage(next, theCodeSystem, conceptsStack);
}
ourLog.info("Saving version");
@ -234,7 +235,7 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
codeSystem.setCurrentVersion(theCodeSystem);
myCodeSystemDao.save(codeSystem);
ourLog.info("Saving concepts...");
ourLog.info("Saving {} concepts...", totalCodeCount);
conceptsStack = new IdentityHashMap<TermConcept, Object>();
for (TermConcept next : theCodeSystem.getConcepts()) {
@ -275,20 +276,24 @@ public abstract class BaseHapiTerminologySvc implements IHapiTerminologySvc {
return retVal;
}
private void validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack) {
private int validateConceptForStorage(TermConcept theConcept, TermCodeSystemVersion theCodeSystem, IdentityHashMap<TermConcept, Object> theConceptsStack) {
ValidateUtil.isNotNullOrThrowInvalidRequest(theConcept.getCodeSystem() == theCodeSystem, "Codesystem contains a code which does not reference the codesystem");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theConcept.getCode(), "Codesystem contains a code which does not reference the codesystem");
if (theConceptsStack.put(theConcept, PLACEHOLDER_OBJECT) != null) {
throw new InvalidRequestException("CodeSystem contains circular reference around code " + theConcept.getCode());
}
int retVal = 1;
for (TermConceptParentChildLink next : theConcept.getChildren()) {
next.setCodeSystem(theCodeSystem);
validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack);
retVal += validateConceptForStorage(next.getChild(), theCodeSystem, theConceptsStack);
}
theConceptsStack.remove(theConcept);
return retVal;
}
public TermConcept findCode(String theCodeSystem, String theCode) {

View File

@ -34,6 +34,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@ -46,6 +47,7 @@ import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.annotations.VisibleForTesting;
@ -68,20 +70,35 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
@Autowired
private IHapiTerminologySvc myTermSvc;
private void dropCircularRefs(TermConcept theConcept, HashSet<String> theChain) {
private void dropCircularRefs(TermConcept theConcept, LinkedHashSet<String> theChain, Map<String, TermConcept> theCode2concept) {
theChain.add(theConcept.getCode());
for (Iterator<TermConceptParentChildLink> childIter = theConcept.getChildren().iterator(); childIter.hasNext();) {
TermConceptParentChildLink next = childIter.next();
TermConcept nextChild = next.getChild();
if (theChain.contains(nextChild.getCode())) {
ourLog.info("Removing circular reference code {} from parent {}", nextChild.getCode(), theConcept.getCode());
StringBuilder b = new StringBuilder();
b.append("Removing circular reference code ");
b.append(nextChild.getCode());
b.append(" from parent ");
b.append(nextChild.getCode());
b.append(". Chain was: ");
for (String nextInChain : theChain) {
TermConcept nextCode = theCode2concept.get(nextInChain);
b.append(nextCode.getCode());
b.append('[');
b.append(StringUtils.substring(nextCode.getDisplay(), 0, 20).replace("[", "").replace("]", "").trim());
b.append("] ");
}
ourLog.info(b.toString(), theConcept.getCode());
childIter.remove();
} else {
theChain.add(theConcept.getCode());
dropCircularRefs(nextChild, theChain);
theChain.remove(theConcept.getCode());
dropCircularRefs(nextChild, theChain, theCode2concept);
}
}
theChain.remove(theConcept.getCode());
}
private TermConcept getOrCreateConcept(TermCodeSystemVersion codeSystemVersion, Map<String, TermConcept> id2concept, String id) {
@ -152,7 +169,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
continue;
}
ourLog.debug("Streaming ZIP entry {} into temporary file", nextEntry.getName());
ourLog.info("Streaming ZIP entry {} into temporary file", nextEntry.getName());
File nextOutFile = File.createTempFile("hapi_fhir", ".csv");
nextOutFile.deleteOnExit();
@ -206,7 +223,7 @@ public class TerminologyLoaderSvc implements IHapiTerminologyLoaderSvc {
ourLog.info("Done loading SNOMED CT files - {} root codes, {} total codes", rootConcepts.size(), code2concept.size());
for (TermConcept next : rootConcepts.values()) {
dropCircularRefs(next, new HashSet<String>());
dropCircularRefs(next, new LinkedHashSet<String>(), code2concept);
}
codeSystemVersion.getConcepts().addAll(rootConcepts.values());

View File

@ -51,6 +51,7 @@ import org.hl7.fhir.dstu3.model.Bundle.BundleType;
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.Bundle.SearchEntryMode;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.DateTimeType;
@ -2693,6 +2694,46 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
}
}
@Test
public void testValidateBadInputViaPost() throws IOException {
Parameters input = new Parameters();
input.addParameter().setName("mode").setValue(new CodeType("create"));
String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input);
ourLog.info(inputStr);
HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate");
post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertThat(resp, containsString("No resource supplied for $validate operation (resource is required unless mode is &quot;delete&quot;)"));
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}
@Test
public void testValidateBadInputViaGet() throws IOException {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$validate?mode=create");
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertThat(resp, containsString("No resource supplied for $validate operation (resource is required unless mode is &quot;delete&quot;)"));
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}
@Test
public void testValidateResourceWithId() throws IOException {

View File

@ -150,6 +150,24 @@ public class OperationServerDstu2Test {
assertThat(response, containsString("HTTP Method GET is not allowed"));
}
@Test
public void testOperationWrongParameterType() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new IntegerDt(123));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String response = IOUtils.toString(status.getEntity().getContent());
assertThat(response, containsString("Request has parameter PARAM1 of type IntegerDt but method expects type StringDt"));
ourLog.info(response);
} finally {
IOUtils.closeQuietly(status);
}
}
@Test
public void testOperationOnInstance() throws Exception {
Parameters p = new Parameters();

View File

@ -200,6 +200,18 @@ public class SearchDstu2Test {
assertThat(responseContent, containsString("SYSTEM"));
}
@Test
public void testSearchMethodReturnsNull() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchReturnNull");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(responseContent);
assertEquals("searchReturnNull", ourLastMethod);
assertThat(responseContent, containsString("<total value=\"0\"/>"));
}
@Test
public void testSearchByPostWithBodyAndUrlParams() throws Exception {
HttpPost httpGet = new HttpPost("http://localhost:" + ourPort + "/Patient/_search?_format=json");
@ -513,6 +525,14 @@ public class SearchDstu2Test {
}
//@formatter:on
//@formatter:off
@Search(queryName="searchReturnNull")
public List<Patient> searchNoList() {
ourLastMethod = "searchReturnNull";
return null;
}
//@formatter:on
//@formatter:off
@Search()
public List<Patient> searchQuantity(

View File

@ -0,0 +1,721 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.blankOrNullString;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
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.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Conformance;
import org.hl7.fhir.dstu3.model.Conformance.ConformanceRestOperationComponent;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Money;
import org.hl7.fhir.dstu3.model.OperationDefinition;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UnsignedIntType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class OperationServerDstu3Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private static IdType ourLastId;
private static String ourLastMethod;
private static StringType ourLastParam1;
private static Patient ourLastParam2;
private static List<StringType> ourLastParam3;
private static Money ourLastParamMoney1;
private static UnsignedIntType ourLastParamUnsignedInt1;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerDstu3Test.class);
private static int ourPort;
private static Server ourServer;
@Before
public void before() {
ourLastParam1 = null;
ourLastParam2 = null;
ourLastParam3 = null;
ourLastParamUnsignedInt1 = null;
ourLastParamMoney1 = null;
ourLastId = null;
ourLastMethod = "";
}
@Test
public void testConformance() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
Conformance p = client.fetchConformance().ofType(Conformance.class).execute();
List<ConformanceRestOperationComponent> ops = p.getRest().get(0).getOperation();
assertThat(ops.size(), greaterThan(1));
assertThat(ops.get(0).getDefinition().getReferenceElement().getValue(), startsWith("#"));
}
@Test
public void testInstanceEverythingGet() throws Exception {
// Try with a GET
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$everything");
CloseableHttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("instance $everything", ourLastMethod);
assertThat(response, startsWith("<Bundle"));
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
}
@Test
public void testInstanceEverythingHapiClient() throws Exception {
Parameters p = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort).operation().onInstance(new IdType("Patient/123")).named("$everything").withParameters(new Parameters()).execute();
Bundle b = (Bundle) p.getParameterFirstRep().getResource();
assertEquals("instance $everything", ourLastMethod);
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
}
@Test
public void testInstanceEverythingPost() throws Exception {
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(new Parameters());
// Try with a POST
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$everything");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("instance $everything", ourLastMethod);
assertThat(response, startsWith("<Bundle"));
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
}
@Test
public void testOperationCantUseGetIfItIsntIdempotent() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("POST", status.getFirstHeader(Constants.HEADER_ALLOW).getValue());
assertThat(response, containsString("HTTP Method GET is not allowed"));
}
@Test
public void testOperationWrongParameterType() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new IntegerType(123));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String response = IOUtils.toString(status.getEntity().getContent());
assertThat(response, containsString("Request has parameter PARAM1 of type IntegerType but method expects type StringType"));
ourLog.info(response);
} finally {
IOUtils.closeQuietly(status);
}
}
@Test
public void testOperationOnInstance() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("123", ourLastId.getIdPart());
assertEquals("$OP_INSTANCE", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
/*
* Against type should fail
*/
httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
status = ourClient.execute(httpPost);
response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(response);
assertEquals(400, status.getStatusLine().getStatusCode());
}
@Test
public void testOperationOnInstanceAndType_Instance() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE_OR_TYPE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("123", ourLastId.getIdPart());
assertEquals("$OP_INSTANCE_OR_TYPE", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnInstanceAndType_Type() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE_OR_TYPE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals(null, ourLastId);
assertEquals("$OP_INSTANCE_OR_TYPE", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnServer() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("$OP_SERVER", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnType() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("$OP_TYPE", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationOnTypeReturnBundle() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RET_BUNDLE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertEquals(true, ourLastParam2.getActive());
assertEquals("$OP_TYPE_RET_BUNDLE", ourLastMethod);
Bundle resp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
assertEquals("100", resp.getEntryFirstRep().getResponse().getStatus());
}
@Test
public void testOperationWithBundleProviderResponse() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/$OP_INSTANCE_BUNDLE_PROVIDER?_pretty=true");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(response);
Bundle resp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
}
@Test
public void testOperationWithGetUsingParams() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val");
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("PARAM1val", ourLastParam1.getValue());
assertNull(ourLastParam2);
assertEquals("$OP_TYPE", ourLastMethod);
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationWithGetUsingParamsFailsWithNonPrimitive() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val&PARAM2=foo");
HttpResponse status = ourClient.execute(httpGet);
assertEquals(405, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("POST", status.getFirstHeader(Constants.HEADER_ALLOW).getValue());
assertThat(response, containsString("Can not invoke operation $OP_TYPE using HTTP GET because parameter PARAM2 is not a primitive datatype"));
}
@Test
public void testOperationWithListParam() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
p.addParameter().setName("PARAM3").setValue(new StringType("PARAM3val1"));
p.addParameter().setName("PARAM3").setValue(new StringType("PARAM3val2"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER_LIST_PARAM");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("$OP_SERVER_LIST_PARAM", ourLastMethod);
assertEquals(true, ourLastParam2.getActive());
assertEquals(null, ourLastParam1);
assertEquals(2, ourLastParam3.size());
assertEquals("PARAM3val1", ourLastParam3.get(0).getValue());
assertEquals("PARAM3val2", ourLastParam3.get(1).getValue());
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationWithProfileDatatypeParams() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new IntegerType("123"));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("$OP_PROFILE_DT", ourLastMethod);
assertEquals("123", ourLastParamUnsignedInt1.getValueAsString());
}
@Test
public void testOperationWithProfileDatatypeParams2() throws Exception {
Parameters p = new Parameters();
Money money = new Money();
money.setCode("CODE");
money.setSystem("SYSTEM");
money.setValue(123L);
p.addParameter().setName("PARAM1").setValue(money);
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT2");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("$OP_PROFILE_DT2", ourLastMethod);
assertEquals("CODE", ourLastParamMoney1.getCode());
assertEquals("SYSTEM", ourLastParamMoney1.getSystem());
assertEquals("123", ourLastParamMoney1.getValue().toString());
}
@Test
public void testOperationWithProfileDatatypeUrl() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_PROFILE_DT?PARAM1=123");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("$OP_PROFILE_DT", ourLastMethod);
assertEquals("123", ourLastParamUnsignedInt1.getValueAsString());
}
@Test
public void testOperationWrongParamType() throws Exception {
Parameters p = new Parameters();
p.addParameter().setName("PARAM1").setValue(new IntegerType("123"));
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(400, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(status.getStatusLine().toString());
ourLog.info(response);
assertThat(response, containsString("Request has parameter PARAM1 of type IntegerType but method expects type StringType"));
}
@Test
public void testReadWithOperations() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123");
HttpResponse status = ourClient.execute(httpGet);
assertEquals(200, status.getStatusLine().getStatusCode());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals("read", ourLastMethod);
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourCtx = FhirContext.forDstu3();
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(new PatientProvider());
servlet.setPlainProviders(new PlainProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static void main(String[] theValue) {
Parameters p = new Parameters();
p.addParameter().setName("start").setValue(new DateTimeType("2001-01-02"));
p.addParameter().setName("end").setValue(new DateTimeType("2015-07-10"));
String inParamsStr = FhirContext.forDstu2().newXmlParser().encodeResourceToString(p);
ourLog.info(inParamsStr.replace("\"", "\\\""));
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
//@formatter:off
@Operation(name="$OP_INSTANCE")
public Parameters opInstance(
@IdParam IdType theId,
@OperationParam(name="PARAM1") StringType theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_INSTANCE";
ourLastId = theId;
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_INSTANCE_OR_TYPE")
public Parameters opInstanceOrType(
@IdParam(optional=true) IdType theId,
@OperationParam(name="PARAM1") StringType theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_INSTANCE_OR_TYPE";
ourLastId = theId;
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_PROFILE_DT2", idempotent=true)
public Bundle opProfileType(
@OperationParam(name="PARAM1") Money theParam1
) {
//@formatter:on
ourLastMethod = "$OP_PROFILE_DT2";
ourLastParamMoney1 = theParam1;
Bundle retVal = new Bundle();
retVal.addEntry().getResponse().setStatus("100");
return retVal;
}
//@formatter:off
@Operation(name="$OP_PROFILE_DT", idempotent=true)
public Bundle opProfileType(
@OperationParam(name="PARAM1") UnsignedIntType theParam1
) {
//@formatter:on
ourLastMethod = "$OP_PROFILE_DT";
ourLastParamUnsignedInt1 = theParam1;
Bundle retVal = new Bundle();
retVal.addEntry().getResponse().setStatus("100");
return retVal;
}
//@formatter:off
@Operation(name="$OP_TYPE", idempotent=true)
public Parameters opType(
@OperationParam(name="PARAM1") StringType theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_TYPE";
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_TYPE_ONLY_STRING", idempotent=true)
public Parameters opTypeOnlyString(
@OperationParam(name="PARAM1") StringType theParam1
) {
//@formatter:on
ourLastMethod = "$OP_TYPE";
ourLastParam1 = theParam1;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_TYPE_RET_BUNDLE")
public Bundle opTypeRetBundle(
@OperationParam(name="PARAM1") StringType theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_TYPE_RET_BUNDLE";
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
Bundle retVal = new Bundle();
retVal.addEntry().getResponse().setStatus("100");
return retVal;
}
@Operation(name = "$everything", idempotent=true)
public Bundle patientEverything(@IdParam IdType thePatientId) {
ourLastMethod = "instance $everything";
ourLastId = thePatientId;
return new Bundle();
}
/**
* Just to make sure this method doesn't "steal" calls
*/
@Read
public Patient read(@IdParam IdType theId) {
ourLastMethod = "read";
Patient retVal = new Patient();
retVal.setId(theId);
return retVal;
}
}
public static class PlainProvider {
//@formatter:off
@Operation(name="$OP_INSTANCE_BUNDLE_PROVIDER", idempotent=true)
public IBundleProvider opInstanceReturnsBundleProvider() {
ourLastMethod = "$OP_INSTANCE_BUNDLE_PROVIDER";
List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i =0; i < 100;i++) {
Patient p = new Patient();
p.setId("Patient/" + i);
p.addName().addFamily("Patient " + i);
resources.add(p);
}
return new SimpleBundleProvider(resources);
}
//@formatter:off
@Operation(name="$OP_SERVER")
public Parameters opServer(
@OperationParam(name="PARAM1") StringType theParam1,
@OperationParam(name="PARAM2") Patient theParam2
) {
//@formatter:on
ourLastMethod = "$OP_SERVER";
ourLastParam1 = theParam1;
ourLastParam2 = theParam2;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
//@formatter:off
@Operation(name="$OP_SERVER_LIST_PARAM")
public Parameters opServerListParam(
@OperationParam(name="PARAM2") Patient theParam2,
@OperationParam(name="PARAM3") List<StringType> theParam3
) {
//@formatter:on
ourLastMethod = "$OP_SERVER_LIST_PARAM";
ourLastParam2 = theParam2;
ourLastParam3 = theParam3;
Parameters retVal = new Parameters();
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
return retVal;
}
}
}

View File

@ -0,0 +1,298 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Organization;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
public class ValidateDstu3Test {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu3();
private static EncodingEnum ourLastEncoding;
private static ValidationModeEnum ourLastMode;
private static String ourLastProfile;
private static String ourLastResourceBody;
private static OperationOutcome ourOutcomeToReturn;
private static int ourPort;
private static Server ourServer;
public static Patient ourLastPatient;
@Before()
public void before() {
ourLastResourceBody = null;
ourLastEncoding = null;
ourOutcomeToReturn = null;
ourLastMode = null;
ourLastProfile = null;
}
@Test
public void testValidate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome"));
}
@Test
public void testValidateInvalidMode() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
params.addParameter().setName("mode").setValue(new CodeType("AAA"));
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("Invalid mode value: &quot;AAA&quot;"));
}
@Test
public void testValidateBlankMode() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
params.addParameter().setName("mode").setValue(new CodeType(" "));
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
}
@Test
public void testValidateMissingResource() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("mode").setValue(new CodeType("create"));
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(null, ourLastPatient);
assertEquals(200, status.getStatusLine().getStatusCode());
}
@Test
public void testValidateWithNoParsed() throws Exception {
Organization org = new Organization();
org.addIdentifier().setValue("001");
org.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(org);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Organization/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newJsonParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(ourLastResourceBody, stringContainsInOrder("\"resourceType\":\"Organization\"", "\"identifier\"", "\"value\":\"001"));
assertEquals(EncodingEnum.JSON, ourLastEncoding);
}
@Test
public void testValidateWithOptions() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
params.addParameter().setName("profile").setValue(new StringType("http://foo"));
params.addParameter().setName("mode").setValue(new StringType(ValidationModeEnum.CREATE.name()));
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome"));
assertEquals("http://foo", ourLastProfile);
assertEquals(ValidationModeEnum.CREATE, ourLastMode);
}
@Test
public void testValidateWithResults() throws Exception {
ourOutcomeToReturn = new OperationOutcome();
ourOutcomeToReturn.addIssue().setDiagnostics("FOOBAR");
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome", "FOOBAR"));
}
@AfterClass
public static void afterClassClearContext() throws Exception {
ourServer.stop();
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setResourceProviders(patientProvider, new OrganizationProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class OrganizationProvider implements IResourceProvider {
@Override
public Class<Organization> getResourceType() {
return Organization.class;
}
@Validate()
public MethodOutcome validate(@ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
ourLastResourceBody = theResourceBody;
ourLastEncoding = theEncoding;
return new MethodOutcome(new IdType("001"));
}
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Validate()
public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) {
ourLastPatient = thePatient;
IdType id;
if (thePatient != null) {
id = new IdType(thePatient.getIdentifier().get(0).getValue());
if (thePatient.getIdElement().isEmpty() == false) {
id = thePatient.getIdElement();
}
} else {
id = new IdType("1");
}
ourLastMode = theMode;
ourLastProfile = theProfile;
MethodOutcome outcome = new MethodOutcome(id.withVersion("002"));
outcome.setOperationOutcome(ourOutcomeToReturn);
return outcome;
}
}
}

View File

@ -244,6 +244,10 @@
DateTime parser incorrectly parsed times where more than 3 digits of
precision were provided on the seconds after the decimal point
</action>
<action type="add">
Improve error messages when the $validate operation is called but no resource
is actually supplied to validate
</action>
</release>
<release version="1.5" date="2016-04-20">
<action type="fix" issue="339">