diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index 343aa595808..fe0a0d34b7f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -192,10 +192,10 @@ public class ParameterUtil { } public static Object fromInteger(Class theType, IntegerDt theArgument) { + if (theArgument == null) { + return null; + } if (theType.equals(Integer.class)) { - if (theArgument == null) { - return null; - } return theArgument.getValue(); } IPrimitiveType retVal = (IPrimitiveType) ReflectionUtil.newInstance(theType); diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Enumeration.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Enumeration.java index 243c46a8d6f..191460685d2 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Enumeration.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Enumeration.java @@ -1,134 +1,134 @@ -package org.hl7.fhir.dstu3.model; - -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; - -import org.hl7.fhir.instance.model.api.IBaseEnumeration; - -import ca.uhn.fhir.model.api.annotation.DatatypeDef; - -/* -Copyright (c) 2011+, HL7, Inc -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -*/ - -/** - * Primitive type "code" in FHIR, where the code is tied to an enumerated list of possible values - * - */ -@DatatypeDef(name = "code", isSpecialization = true) -public class Enumeration> extends PrimitiveType implements IBaseEnumeration { - - private static final long serialVersionUID = 1L; - private EnumFactory myEnumFactory; - - /** - * Constructor - * - * @deprecated This no-arg constructor is provided for serialization only - Do not use - */ - @Deprecated - public Enumeration() { - // nothing - } - - /** - * Constructor - */ - public Enumeration(EnumFactory theEnumFactory) { - if (theEnumFactory == null) - throw new IllegalArgumentException("An enumeration factory must be provided"); - myEnumFactory = theEnumFactory; - } - - /** - * Constructor - */ - public Enumeration(EnumFactory theEnumFactory, String theValue) { - if (theEnumFactory == null) - throw new IllegalArgumentException("An enumeration factory must be provided"); - myEnumFactory = theEnumFactory; - setValueAsString(theValue); - } - - /** - * Constructor - */ - public Enumeration(EnumFactory theEnumFactory, T theValue) { - if (theEnumFactory == null) - throw new IllegalArgumentException("An enumeration factory must be provided"); - myEnumFactory = theEnumFactory; - setValue(theValue); - } - - @Override - public Enumeration copy() { - return new Enumeration(myEnumFactory, getValue()); - } - - @Override - protected String encode(T theValue) { - return myEnumFactory.toCode(theValue); - } - - public String fhirType() { - return "code"; - } - - /** - * Provides the enum factory which binds this enumeration to a specific ValueSet - */ - public EnumFactory getEnumFactory() { - return myEnumFactory; - } - - @Override - protected T parse(String theValue) { - if (myEnumFactory != null) { - return myEnumFactory.fromCode(theValue); - } - return null; - } - - @SuppressWarnings("unchecked") - @Override - public void readExternal(ObjectInput theIn) throws IOException, ClassNotFoundException { - myEnumFactory = (EnumFactory) theIn.readObject(); - super.readExternal(theIn); - } - - public String toSystem() { - return getEnumFactory().toSystem(getValue()); - } - - @Override - public void writeExternal(ObjectOutput theOut) throws IOException { - theOut.writeObject(myEnumFactory); - super.writeExternal(theOut); - } -} +package org.hl7.fhir.dstu3.model; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + +import org.hl7.fhir.instance.model.api.IBaseEnumeration; + +import ca.uhn.fhir.model.api.annotation.DatatypeDef; + +/* +Copyright (c) 2011+, HL7, Inc +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +/** + * Primitive type "code" in FHIR, where the code is tied to an enumerated list of possible values + * + */ +@DatatypeDef(name = "code", isSpecialization = true) +public class Enumeration> extends PrimitiveType implements IBaseEnumeration { + + private static final long serialVersionUID = 1L; + private EnumFactory myEnumFactory; + + /** + * Constructor + * + * @deprecated This no-arg constructor is provided for serialization only - Do not use + */ + @Deprecated + public Enumeration() { + super(); + } + + /** + * Constructor + */ + public Enumeration(EnumFactory theEnumFactory) { + if (theEnumFactory == null) + throw new IllegalArgumentException("An enumeration factory must be provided"); + myEnumFactory = theEnumFactory; + } + + /** + * Constructor + */ + public Enumeration(EnumFactory theEnumFactory, String theValue) { + if (theEnumFactory == null) + throw new IllegalArgumentException("An enumeration factory must be provided"); + myEnumFactory = theEnumFactory; + setValueAsString(theValue); + } + + /** + * Constructor + */ + public Enumeration(EnumFactory theEnumFactory, T theValue) { + if (theEnumFactory == null) + throw new IllegalArgumentException("An enumeration factory must be provided"); + myEnumFactory = theEnumFactory; + setValue(theValue); + } + + @Override + public Enumeration copy() { + return new Enumeration(myEnumFactory, getValue()); + } + + @Override + protected String encode(T theValue) { + return myEnumFactory.toCode(theValue); + } + + public String fhirType() { + return "code"; + } + + /** + * Provides the enum factory which binds this enumeration to a specific ValueSet + */ + public EnumFactory getEnumFactory() { + return myEnumFactory; + } + + @Override + protected T parse(String theValue) { + if (myEnumFactory != null) { + return myEnumFactory.fromCode(theValue); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void readExternal(ObjectInput theIn) throws IOException, ClassNotFoundException { + myEnumFactory = (EnumFactory) theIn.readObject(); + super.readExternal(theIn); + } + + public String toSystem() { + return getEnumFactory().toSystem(getValue()); + } + + @Override + public void writeExternal(ObjectOutput theOut) throws IOException { + theOut.writeObject(myEnumFactory); + super.writeExternal(theOut); + } +} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java index c5f4213969e..d0650db4ef5 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/JsonParserDstu3Test.java @@ -728,6 +728,7 @@ public class JsonParserDstu3Test { Observation obs = new Observation(); obs.setId("1"); obs.getMeta().addProfile("http://profile"); + obs.setStatus(ObservationStatus.FINAL); Extension ext = obs.addExtension(); ext.setUrl("http://exturl").setValue(new StringType("ext_url_value")); @@ -758,6 +759,9 @@ public class JsonParserDstu3Test { assertEquals(1, obs.getExtension().size()); assertEquals("http://exturl", obs.getExtension().get(0).getUrl()); assertEquals("ext_url_value", ((StringType) obs.getExtension().get(0).getValue()).getValue()); + assertEquals("final", obs.getStatusElement().getValueAsString()); + assertEquals(ObservationStatus.FINAL, obs.getStatusElement().getValue()); + } @Test diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/PlainProviderR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/PlainProviderR4Test.java new file mode 100644 index 00000000000..61dcd0b341e --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/PlainProviderR4Test.java @@ -0,0 +1,267 @@ +package ca.uhn.fhir.rest.server; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Count; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.util.PortUtil; +import ca.uhn.fhir.util.TestUtil; +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.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.hamcrest.core.IsEqual; +import org.hamcrest.core.StringStartsWith; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class PlainProviderR4Test { + + private static FhirContext ourCtx = FhirContext.forR4(); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PlainProviderR4Test.class); + private CloseableHttpClient myClient; + private int myPort; + private RestfulServer myRestfulServer; + private Server myServer; + + @After + public void after() throws Exception { + myServer.stop(); + } + + @Before + public void before() throws Exception { + myPort = PortUtil.findFreePort(); + myServer = new Server(myPort); + + ServletHandler proxyHandler = new ServletHandler(); + ServletHolder servletHolder = new ServletHolder(); + myRestfulServer = new RestfulServer(ourCtx); + servletHolder.setServlet(myRestfulServer); + proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*"); + myServer.setHandler(proxyHandler); + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + HttpClientBuilder builder = HttpClientBuilder.create(); + + builder.setConnectionManager(connectionManager); + myClient = builder.build(); + + } + + @Test + public void testGlobalHistory() throws Exception { + GlobalHistoryProvider provider = new GlobalHistoryProvider(); + myRestfulServer.setProviders(provider); + myServer.start(); + + String baseUri = "http://localhost:" + myPort + "/fhir/context"; + HttpResponse status = myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02&_count=12")); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent); + assertEquals(3, bundle.getEntry().size()); + + assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02")); + assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12")); + + status = myClient.execute(new HttpGet(baseUri + "/_history?&_count=12")); + responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent); + assertEquals(3, bundle.getEntry().size()); + assertNull(provider.myLastSince); + assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12")); + + status =myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02")); + responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent); + assertEquals(3, bundle.getEntry().size()); + assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02")); + assertNull(provider.myLastCount); + } + + @Test + public void testGlobalHistoryNoParams() throws Exception { + GlobalHistoryProvider provider = new GlobalHistoryProvider(); + myRestfulServer.setProviders(provider); + myServer.start(); + + String baseUri = "http://localhost:" + myPort + "/fhir/context"; + CloseableHttpResponse status = myClient.execute(new HttpGet(baseUri + "/_history")); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent); + assertEquals(3, bundle.getEntry().size()); + assertNull(provider.myLastSince); + assertNull(provider.myLastCount); + + } + + @Test + public void testSearchByParamIdentifier() throws Exception { + myRestfulServer.setProviders(new SearchProvider()); + myServer.start(); + + String baseUri = "http://localhost:" + myPort + "/fhir/context"; + String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001"; + HttpGet httpGet = new HttpGet(uri); + try (CloseableHttpResponse status = myClient.execute(httpGet)) { + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent); + + assertEquals(1, bundle.getEntry().size()); + + Patient patient = (Patient) bundle.getEntry().get(0).getResource(); + assertEquals("PatientOne", patient.getName().get(0).getGiven().get(0).getValue()); + + assertEquals(uri.replace(":hapitest:", "%3Ahapitest%3A"), bundle.getLink("self").getUrl()); + } + + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + + private static Organization createOrganization() { + Organization retVal = new Organization(); + retVal.setId("1"); + retVal.addIdentifier(); + retVal.getIdentifier().get(0).setUse(Identifier.IdentifierUse.OFFICIAL); + retVal.getIdentifier().get(0).setSystem(("urn:hapitest:mrns")); + retVal.getIdentifier().get(0).setValue("00001"); + retVal.setName("Test Org"); + return retVal; + } + + private static Patient createPatient() { + Patient patient = new Patient(); + patient.setId("1"); + patient.addIdentifier(); + patient.getIdentifier().get(0).setUse(Identifier.IdentifierUse.OFFICIAL); + patient.getIdentifier().get(0).setSystem(("urn:hapitest:mrns")); + patient.getIdentifier().get(0).setValue("00001"); + patient.addName(); + patient.getName().get(0).setFamily("Test"); + patient.getName().get(0).addGiven("PatientOne"); + patient.getGenderElement().setValueAsString("male"); + return patient; + } + + public static class GlobalHistoryProvider { + + private IntegerType myLastCount; + private InstantType myLastSince; + + @History + public List getGlobalHistory(@Since InstantType theSince, @Count IntegerType theCount) { + myLastSince = theSince; + myLastCount = theCount; + ArrayList retVal = new ArrayList<>(); + + Resource p = createPatient(); + p.setId(new IdType("1")); + p.getMeta().setVersionId("A"); + p.getMeta().getLastUpdatedElement().setValueAsString("2012-01-01T01:00:01"); + retVal.add(p); + + p = createPatient(); + p.setId(new IdType("1")); + p.getMeta().setVersionId("B"); + p.getMeta().getLastUpdatedElement().setValueAsString("2012-01-01T01:00:03"); + retVal.add(p); + + p = createOrganization(); + p.setId(new IdType("1")); + p.getMeta().setVersionId("A"); + p.getMeta().getLastUpdatedElement().setValueAsString("2013-01-01T01:00:01"); + retVal.add(p); + + return retVal; + } + + } + + + public static class SearchProvider { + + @Search(type = Patient.class) + public Patient findPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) { + for (Patient next : getIdToPatient().values()) { + for (Identifier nextId : next.getIdentifier()) { + if (nextId.getSystem().equals(theIdentifier.getSystem()) && nextId.getValue().equals(theIdentifier.getValue())) { + return next; + } + } + } + return null; + } + + public Map getIdToPatient() { + Map idToPatient = new HashMap<>(); + { + Patient patient = createPatient(); + idToPatient.put("1", patient); + } + { + Patient patient = new Patient(); + patient.getIdentifier().add(new Identifier()); + patient.getIdentifier().get(0).setUse(Identifier.IdentifierUse.OFFICIAL); + patient.getIdentifier().get(0).setSystem(("urn:hapitest:mrns")); + patient.getIdentifier().get(0).setValue("00002"); + patient.getName().add(new HumanName()); + patient.getName().get(0).setFamily("Test"); + patient.getName().get(0).addGiven("PatientTwo"); + patient.getGenderElement().setValueAsString("female"); + idToPatient.put("2", patient); + } + return idToPatient; + } + + /** + * Retrieve the resource by its identifier + * + * @param theId + * The resource identity + * @return The resource + */ + @Read(type = Patient.class) + public Patient getPatientById(@IdParam IdType theId) { + return getIdToPatient().get(theId.getValue()); + } + + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index aef584b73b2..f7dd9be4657 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -109,6 +109,12 @@ application/json+fhir]]>. Thanks to John Grimes for reporting! + + Fixed a regression in server where a count parameter in the form + @Count IntegerType theCount]]> + caused an exception if the client made a request with + no count parameter included. Thanks to Viviana Sanz for reporting! +