From fcb9a80bbcc337f1acc873904e9fc66f3185e3fe Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sat, 8 Nov 2014 12:17:42 -0500 Subject: [PATCH] DateRangeParam now correctly handles requests containing a single date --- .../uhn/fhir/rest/param/DateRangeParam.java | 26 ++-- .../fhir/rest/param/DateRangeParamTest.java | 75 +++++----- .../rest/server/DateRangeParamSearchTest.java | 131 ++++++++++++++++++ src/changes/changes.xml | 17 ++- src/site/xdoc/doc_narrative.xml | 2 +- src/site/xdoc/doc_server_tester.xml | 2 +- 6 files changed, 204 insertions(+), 49 deletions(-) create mode 100644 hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DateRangeParamSearchTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java index 7772ce0324d..68a3e7eec70 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java @@ -95,10 +95,10 @@ public class DateRangeParam implements IQueryParameterAnd { * Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends) * * @param theLowerBound - * A qualified date param representing the lower date bound (optionally may include time), e.g. + * An unqualified date param representing the lower date bound (optionally may include time), e.g. * "2011-02-22" or "2011-02-22T13:12:00" * @param theUpperBound - * A qualified date param representing the upper date bound (optionally may include time), e.g. + * An unqualified date param representing the upper date bound (optionally may include time), e.g. * "2011-02-22" or "2011-02-22T13:12:00" */ public DateRangeParam(String theLowerBound, String theUpperBound) { @@ -211,7 +211,9 @@ public class DateRangeParam implements IQueryParameterAnd { @Override public void setValuesAsQueryTokens(List theParameters) throws InvalidRequestException { - for (List paramList : theParameters) { + + boolean haveHadUnqualifiedParameter = false; + for (QualifiedParamList paramList : theParameters) { if (paramList.size() == 0) { continue; } @@ -220,9 +222,18 @@ public class DateRangeParam implements IQueryParameterAnd { } String param = paramList.get(0); DateParam parsed = new DateParam(); - parsed.setValueAsQueryToken(null, param); + parsed.setValueAsQueryToken(paramList.getQualifier(), param); addParam(parsed); + + if (parsed.getComparator() == null) { + if (haveHadUnqualifiedParameter) { + throw new InvalidRequestException("Multiple date parameters with the same name and no qualifier (>, <, etc.) is not supported"); + } + haveHadUnqualifiedParameter=true; + } + } + } private void addParam(DateParam theParsed) throws InvalidRequestException { @@ -231,10 +242,9 @@ public class DateRangeParam implements IQueryParameterAnd { throw new InvalidRequestException("Can not have multiple date range parameters for the same param without a qualifier"); } - myLowerBound = theParsed; - myUpperBound = theParsed; - // TODO: in this case, should set lower and upper to exact moments - // using specified precision + myLowerBound = new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theParsed.getValueAsString()); + myUpperBound = new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theParsed.getValueAsString()); + } else { switch (theParsed.getComparator()) { diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java index 083aa17826b..f66604ce866 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import org.junit.BeforeClass; import org.junit.Test; import ca.uhn.fhir.rest.method.QualifiedParamList; @@ -16,13 +15,40 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class DateRangeParamTest { + private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException { + DateRangeParam p = new DateRangeParam(); + List tokens = new ArrayList(); + tokens.add(QualifiedParamList.singleton(null, theLower)); + if (theUpper != null) { + tokens.add(QualifiedParamList.singleton(null, theUpper)); + } + p.setValuesAsQueryTokens(tokens); + return p; + } + + public static Date parse(String theString) throws ParseException { + return ourFmt.parse(theString); + } + + public static Date parseM1(String theString) throws ParseException { + return new Date(ourFmt.parse(theString).getTime() - 1L); + } + private static SimpleDateFormat ourFmt; + static { + ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS"); + } + + private DateRangeParam create(String theString) { + return new DateRangeParam(new DateParam(theString)); + } + @Test public void testDay() throws Exception { assertEquals(parse("2011-01-01 00:00:00.0000"), create(">=2011-01-01", "<2011-01-02").getLowerBoundAsInstant()); assertEquals(parseM1("2011-01-02 00:00:00.0000"), create(">=2011-01-01", "<2011-01-02").getUpperBoundAsInstant()); - + assertEquals(parse("2011-01-02 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getLowerBoundAsInstant()); assertEquals(parseM1("2011-01-03 00:00:00.0000"), create(">2011-01-01", "<=2011-01-02").getUpperBoundAsInstant()); } @@ -31,7 +57,7 @@ public class DateRangeParamTest { public void testFromQualifiedDateParam() throws Exception { assertEquals(parse("2011-01-01 00:00:00.0000"), create("2011-01-01").getLowerBoundAsInstant()); assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("2011-01-01").getUpperBoundAsInstant()); - + assertEquals(parse("2011-01-01 00:00:00.0000"), create(">=2011-01-01").getLowerBoundAsInstant()); assertEquals(null, create(">=2011-01-01").getUpperBoundAsInstant()); @@ -43,16 +69,22 @@ public class DateRangeParamTest { public void testMonth() throws Exception { assertEquals(parse("2011-01-01 00:00:00.0000"), create(">=2011-01", "<2011-02").getLowerBoundAsInstant()); assertEquals(parseM1("2011-02-01 00:00:00.0000"), create(">=2011-01", "<2011-02").getUpperBoundAsInstant()); - + assertEquals(parse("2011-02-01 00:00:00.0000"), create(">2011-01", "<=2011-02").getLowerBoundAsInstant()); assertEquals(parseM1("2011-03-01 00:00:00.0000"), create(">2011-01", "<=2011-02").getUpperBoundAsInstant()); } + @Test + public void testOnlyOneParam() throws Exception { + assertEquals(parse("2011-01-01 00:00:00.0000"), create("2011-01-01").getLowerBoundAsInstant()); + assertEquals(parseM1("2011-01-02 00:00:00.0000"), create("2011-01-01").getUpperBoundAsInstant()); + } + @Test public void testSecond() throws Exception { assertEquals(parse("2011-01-01 00:00:00.0000"), create(">=2011-01-01T00:00:00", "<2011-01-01T01:00:00").getLowerBoundAsInstant()); assertEquals(parseM1("2011-01-01 02:00:00.0000"), create(">=2011-01-01T00:00:00", "<2011-01-01T02:00:00").getUpperBoundAsInstant()); - + assertEquals(parse("2011-01-01 00:00:01.0000"), create(">2011-01-01T00:00:00", "<=2011-01-01T02:00:00").getLowerBoundAsInstant()); assertEquals(parseM1("2011-01-01 02:00:01.0000"), create(">2011-01-01T00:00:00", "<=2011-01-01T02:00:00").getUpperBoundAsInstant()); } @@ -61,40 +93,9 @@ public class DateRangeParamTest { public void testYear() throws Exception { assertEquals(parse("2011-01-01 00:00:00.0000"), create(">=2011", "<2012").getLowerBoundAsInstant()); assertEquals(parseM1("2012-01-01 00:00:00.0000"), create(">=2011", "<2012").getUpperBoundAsInstant()); - + assertEquals(parse("2012-01-01 00:00:00.0000"), create(">2011", "<=2012").getLowerBoundAsInstant()); assertEquals(parseM1("2014-01-01 00:00:00.0000"), create(">2011", "<=2013").getUpperBoundAsInstant()); } - private DateRangeParam create(String theString) { - return new DateRangeParam(new QualifiedDateParam(theString)); - } - - private Date parse(String theString) throws ParseException { - return ourFmt.parse(theString); - } - - private Date parseM1(String theString) throws ParseException { - return new Date(ourFmt.parse(theString).getTime() - 1L); - } - - private Date parseP1(String theString) throws ParseException { - return new Date(ourFmt.parse(theString).getTime() + 1L); - } - - @BeforeClass - public static void beforeClass() { - ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS"); - } - - - private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException { - DateRangeParam p = new DateRangeParam(); - List tokens=new ArrayList(); - tokens.add(QualifiedParamList.singleton(null,theLower)); - tokens.add(QualifiedParamList.singleton(null,theUpper)); - p.setValuesAsQueryTokens(tokens); - return p; - } - } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DateRangeParamSearchTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DateRangeParamSearchTest.java new file mode 100644 index 00000000000..a7fa5aa0a40 --- /dev/null +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/DateRangeParamSearchTest.java @@ -0,0 +1,131 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.*; + +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.HttpGet; +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.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.DateRangeParamTest; +import ca.uhn.fhir.util.PortUtil; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class DateRangeParamSearchTest { + + private static CloseableHttpClient ourClient; + private static int ourPort; + private static Server ourServer; + + @Test + public void testSearchForOneUnqualifiedDate() throws Exception { + String baseUrl = "http://localhost:" + ourPort + "/Patient?" + Patient.SP_BIRTHDATE + "="; + HttpGet httpGet = new HttpGet(baseUrl + "2012-01-01"); + HttpResponse status = ourClient.execute(httpGet); + IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("2012-01-01", ourLastDateRange.getLowerBound().getValueAsString()); + assertEquals("2012-01-01", ourLastDateRange.getUpperBound().getValueAsString()); + + assertEquals(DateRangeParamTest.parse("2012-01-01 00:00:00.0000"), ourLastDateRange.getLowerBoundAsInstant()); + assertEquals(DateRangeParamTest.parseM1("2012-01-02 00:00:00.0000"), ourLastDateRange.getUpperBoundAsInstant()); + + } + + @Test + public void testSearchForMultipleUnqualifiedDate() throws Exception { + String baseUrl = "http://localhost:" + ourPort + "/Patient?" + Patient.SP_BIRTHDATE + "="; + HttpGet httpGet = new HttpGet(baseUrl + "2012-01-01&" + Patient.SP_BIRTHDATE + "=2012-02-03"); + HttpResponse status = ourClient.execute(httpGet); + IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(400, status.getStatusLine().getStatusCode()); + + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + + @Before + public void before() { + ourLastDateRange = null; + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = PortUtil.findFreePort(); + ourServer = new Server(ourPort); + + DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); + + servlet.setResourceProviders(patientProvider); + 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(); + + } + + private static DateRangeParam ourLastDateRange; + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class DummyPatientResourceProvider implements IResourceProvider { + + @Search() + public List search(@RequiredParam(name=Patient.SP_BIRTHDATE) DateRangeParam theDateRange) { + ourLastDateRange = theDateRange; + + ArrayList retVal = new ArrayList(); + + Patient patient = new Patient(); + patient.setId("1"); + patient.addIdentifier("system", "hello"); + retVal.add(patient); + return retVal; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a4fccdcecb3..0646875e968 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -36,16 +36,29 @@ returns a default Patient, but sometimes uses a custom subclass). Thanks to Bill de Beaubien for the pull request! - + Add a new method to the server interceptor framework which allows interceptors to be notified of any exceptions and runtime errors within server methods. Interceptors may optionally also override the default error handling behaviour of the RestfulServer. - + Add constants to BaseResource for the "_id" search parameter which all resources should support. + + DateRangeParam parameters on the server now return correct + getLowerBoundAsInstant()]]> + and + getUpperBoundAsInstant()]]> + values if a single unqualified value is passed in. For example, if + a query containing + &birthdate=2012-10-01]]> + is received, previously these two methods would both return the same + value, but with this fix + getUpperBoundAsInstant()]]> + now returns the instant at 23:59:59.9999. + diff --git a/src/site/xdoc/doc_narrative.xml b/src/site/xdoc/doc_narrative.xml index 5f772163fae..150eb316e28 100644 --- a/src/site/xdoc/doc_narrative.xml +++ b/src/site/xdoc/doc_narrative.xml @@ -120,7 +120,7 @@

- +

diff --git a/src/site/xdoc/doc_server_tester.xml b/src/site/xdoc/doc_server_tester.xml index f6a94132116..3dd651cdf76 100644 --- a/src/site/xdoc/doc_server_tester.xml +++ b/src/site/xdoc/doc_server_tester.xml @@ -86,7 +86,7 @@ and copy in the following contents:

- +