diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalUrlParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalUrlParam.java index 2183f18813d..a4778fbbf05 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalUrlParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalUrlParam.java @@ -31,6 +31,9 @@ import java.lang.annotation.Target; * conditional "search" URL for the operation, if an incoming client invocation is * a conditional operation. For non-conditional invocations, the value will be set to * null so it is important to handle null. + *

+ * Parameters annotated with this annotation must be of type {@link String} + *

*/ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java index 500dbda10d8..1d96e2c88e6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java @@ -29,7 +29,9 @@ import java.util.Map; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; +import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -48,7 +50,9 @@ class ConditionalParamBinder implements IParameter { @Override public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { - // nothing + if (theOuterCollectionType != null || theInnerCollectionType != null || theParameterType.equals(String.class) == false) { + throw new ConfigurationException("Parameters annotated with @" + ConditionalUrlParam.class.getSimpleName() + " must be of type String, found incorrect parameteter in method \"" + theMethod + "\""); + } } public boolean isSupportsMultiple() { @@ -57,7 +61,7 @@ class ConditionalParamBinder implements IParameter { @Override public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Can not use @" + getClass().getName() + " annotated parameters in client"); } @Override diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java index 04eec27dd26..76159b8f661 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionDstu2Test.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import javax.servlet.ServletException; @@ -11,8 +12,13 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.param.TokenParam; public class ServerInvalidDefinitionDstu2Test { @@ -33,6 +39,21 @@ public class ServerInvalidDefinitionDstu2Test { } } + @Test + public void testWrongConditionalUrlType() { + RestfulServer srv = new RestfulServer(ourCtx); + srv.setFhirContext(ourCtx); + srv.setResourceProviders(new UpdateWithWrongConditionalUrlType()); + + try { + srv.init(); + fail(); + } catch (ServletException e) { + assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException")); + assertThat(e.getCause().toString(), StringContains.containsString("Parameters annotated with @ConditionalUrlParam must be of type String, found incorrect parameteter in method \"public ca.uhn.fhir.rest.api.MethodOutcome ca.uhn.fhir.rest.server.ServerInvalidDefinitionDstu2Test$UpdateWithWrongConditionalUrlType.update(ca.uhn.fhir.rest.param.TokenParam,ca.uhn.fhir.model.dstu2.resource.Patient)")); + } + } + public static class OperationReturningOldBundleProvider implements IResourceProvider { @Override @@ -47,4 +68,18 @@ public class ServerInvalidDefinitionDstu2Test { } + public static class UpdateWithWrongConditionalUrlType implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Update + public MethodOutcome update(@ConditionalUrlParam TokenParam theToken, @ResourceParam Patient theParam2) { + return null; + } + + } + } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/DeleteConditionalDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/DeleteConditionalDstu3Test.java new file mode 100644 index 00000000000..bad1a18c6dc --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/DeleteConditionalDstu3Test.java @@ -0,0 +1,146 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +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.client.methods.HttpPut; +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.IdType; +import org.hl7.fhir.dstu3.model.Patient; +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.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.util.PortUtil; + +public class DeleteConditionalDstu3Test { + private static CloseableHttpClient ourClient; + private static String ourLastConditionalUrl; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DeleteConditionalDstu3Test.class); + private static int ourPort; + private static FhirContext ourCtx = FhirContext.forDstu3(); + private static Server ourServer; + private static IdType ourLastIdParam; + private static boolean ourLastRequestWasDelete; + private static IGenericClient ourHapiClient; + + + + @Before + public void before() { + ourLastConditionalUrl = null; + ourLastIdParam = null; + ourLastRequestWasDelete = false; + } + + + @Test + public void testSearchStillWorks() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier().setValue("002"); + +// HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true"); +// +// HttpResponse status = ourClient.execute(httpGet); +// +// String responseContent = IOUtils.toString(status.getEntity().getContent()); +// IOUtils.closeQuietly(status.getEntity().getContent()); +// +// ourLog.info("Response was:\n{}", responseContent); + + //@formatter:off + ourHapiClient + .delete() + .resourceConditionalByType(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SOMESYS","SOMEID")) + .execute(); + //@formatter:on + + assertTrue(ourLastRequestWasDelete); + assertEquals(null, ourLastIdParam); + assertEquals("Patient?identifier=SOMESYS%7CSOMEID", ourLastConditionalUrl); + + } + + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + + @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); + 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(); + + ourCtx.getRestfulClientFactory().setSocketTimeout(500 * 1000); + ourHapiClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + ourHapiClient.registerInterceptor(new LoggingInterceptor()); + } + + public static class PatientProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Delete() + public MethodOutcome deletePatient(@IdParam IdType theIdParam, @ConditionalUrlParam String theConditional) { + ourLastRequestWasDelete = true; + ourLastConditionalUrl = theConditional; + ourLastIdParam = theIdParam; + return new MethodOutcome(new IdType("Patient/001/_history/002")); + } + + } + +}