diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java index 56bcf1d47d9..f55ce75e9b2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java @@ -87,7 +87,7 @@ public @interface OperationParam { * repetitions. See {@link #MAX_DEFAULT} for a description of the default * behaviour. */ - int max() default MAX_UNLIMITED; + int max() default MAX_DEFAULT; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index c66f7a3e60a..d805de9da67 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -1775,7 +1775,11 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public IReadExecutable withId(String theId) { Validate.notBlank(theId, "The ID can not be blank"); - myId = new IdDt(myType.getName(), theId); + if (theId.indexOf('/') == -1) { + myId = new IdDt(myType.getName(), theId); + } else { + myId = new IdDt(theId); + } return this; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java index 81b832bee22..5e1e2739cdb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IReadTyped.java @@ -25,10 +25,26 @@ import org.hl7.fhir.instance.model.api.IIdType; public interface IReadTyped { + /** + * Perform a search by resource ID + * + * @param theId The resource ID, e.g. "123" + */ IReadExecutable withId(String theId); + /** + * Perform a search by resource ID and version + * + * @param theId The resource ID, e.g. "123" + * @param theVersion The resource version, eg. "5" + */ IReadExecutable withIdAndVersion(String theId, String theVersion); + /** + * Perform a search by resource ID + * + * @param theId The resource ID, e.g. "123" + */ IReadExecutable withId(Long theId); /** diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java index 9581ee57f39..64d0e5c22f4 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerDstu2Test.java @@ -1,6 +1,11 @@ package ca.uhn.fhir.rest.server; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.containsInRelativeOrder; +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; @@ -33,10 +38,11 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.composite.MoneyDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Conformance; -import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; import ca.uhn.fhir.model.dstu2.resource.Conformance.RestOperation; +import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.valueset.OperationParameterUseEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IntegerDt; @@ -65,6 +71,7 @@ public class OperationServerDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerDstu2Test.class); private static int ourPort; private static Server ourServer; + private IGenericClient myFhirClient; @Before public void before() { @@ -75,22 +82,23 @@ public class OperationServerDstu2Test { ourLastParamMoney1 = null; ourLastId = null; ourLastMethod = ""; + + myFhirClient = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort); } @Test public void testConformance() throws Exception { - IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort); LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); loggingInterceptor.setLogResponseBody(true); - client.registerInterceptor(loggingInterceptor); + myFhirClient.registerInterceptor(loggingInterceptor); - Conformance p = client.fetchConformance().ofType(Conformance.class).prettyPrint().execute(); + Conformance p = myFhirClient.fetchConformance().ofType(Conformance.class).prettyPrint().execute(); List ops = p.getRest().get(0).getOperation(); assertThat(ops.size(), greaterThan(1)); assertNull(ops.get(0).getDefinition().getReference().getBaseUrl()); assertThat(ops.get(0).getDefinition().getReference().getValue(), startsWith("OperationDefinition/")); - OperationDefinition def = client.read().resource(OperationDefinition.class).withId(ops.get(0).getDefinition().getReference()).execute(); + OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId(ops.get(0).getDefinition().getReference()).execute(); assertThat(def.getCode(), not(blankOrNullString())); List opNames = toOpNames(ops); @@ -99,6 +107,44 @@ public class OperationServerDstu2Test { assertEquals("OperationDefinition/OP_TYPE", ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getReference().getValue()); } + /** + * See #380 + */ + @Test + public void testOperationDefinition() { + OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/OP_TYPE").execute(); + + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def)); + +// @OperationParam(name="PARAM1") StringType theParam1, +// @OperationParam(name="PARAM2") Patient theParam2, +// @OperationParam(name="PARAM3", min=2, max=5) List theParam3, +// @OperationParam(name="PARAM4", min=1) List theParam4, + + assertEquals(4, def.getParameter().size()); + assertEquals("PARAM1", def.getParameter().get(0).getName()); + assertEquals(OperationParameterUseEnum.IN.getCode(), def.getParameter().get(0).getUse()); + assertEquals(0, def.getParameter().get(0).getMin().intValue()); + assertEquals("1", def.getParameter().get(0).getMax()); + + assertEquals("PARAM2", def.getParameter().get(1).getName()); + assertEquals(OperationParameterUseEnum.IN.getCode(), def.getParameter().get(1).getUse()); + assertEquals(0, def.getParameter().get(1).getMin().intValue()); + assertEquals("1", def.getParameter().get(1).getMax()); + + assertEquals("PARAM3", def.getParameter().get(2).getName()); + assertEquals(OperationParameterUseEnum.IN.getCode(), def.getParameter().get(2).getUse()); + assertEquals(2, def.getParameter().get(2).getMin().intValue()); + assertEquals("5", def.getParameter().get(2).getMax()); + + assertEquals("PARAM4", def.getParameter().get(3).getName()); + assertEquals(OperationParameterUseEnum.IN.getCode(), def.getParameter().get(3).getUse()); + assertEquals(1, def.getParameter().get(3).getMin().intValue()); + assertEquals("*", def.getParameter().get(3).getMax()); + + } + + private List toOpNames(List theOps) { ArrayList retVal = new ArrayList(); for (RestOperation next : theOps) { @@ -613,7 +659,9 @@ public class OperationServerDstu2Test { @Operation(name="$OP_TYPE", idempotent=true) public Parameters opType( @OperationParam(name="PARAM1") StringDt theParam1, - @OperationParam(name="PARAM2") Patient theParam2 + @OperationParam(name="PARAM2") Patient theParam2, + @OperationParam(name="PARAM3", min=2, max=5) List theParam3, + @OperationParam(name="PARAM4", min=1) List theParam4 ) { //@formatter:on diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerConformanceProvider.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerConformanceProvider.java index e9c401905ba..4b852021281 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/ServerConformanceProvider.java @@ -289,7 +289,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider ops = p.getRest().get(0).getOperation(); assertThat(ops.size(), greaterThan(1)); List opNames = toOpNames(ops); assertThat(opNames, containsInRelativeOrder("OP_TYPE")); - OperationDefinition def = (OperationDefinition) ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getResource(); +// OperationDefinition def = (OperationDefinition) ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getResource(); + OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId(ops.get(opNames.indexOf("OP_TYPE")).getDefinition().getReferenceElement()).execute(); assertEquals("OP_TYPE", def.getCode()); } + + /** + * See #380 + */ + @Test + public void testOperationDefinition() { + OperationDefinition def = myFhirClient.read().resource(OperationDefinition.class).withId("OperationDefinition/OP_TYPE").execute(); + + ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(def)); + +// @OperationParam(name="PARAM1") StringType theParam1, +// @OperationParam(name="PARAM2") Patient theParam2, +// @OperationParam(name="PARAM3", min=2, max=5) List theParam3, +// @OperationParam(name="PARAM4", min=1) List theParam4, + + assertEquals(4, def.getParameter().size()); + assertEquals("PARAM1", def.getParameter().get(0).getName()); + assertEquals(OperationParameterUse.IN, def.getParameter().get(0).getUse()); + assertEquals(0, def.getParameter().get(0).getMin()); + assertEquals("1", def.getParameter().get(0).getMax()); + + assertEquals("PARAM2", def.getParameter().get(1).getName()); + assertEquals(OperationParameterUse.IN, def.getParameter().get(1).getUse()); + assertEquals(0, def.getParameter().get(1).getMin()); + assertEquals("1", def.getParameter().get(1).getMax()); + + assertEquals("PARAM3", def.getParameter().get(2).getName()); + assertEquals(OperationParameterUse.IN, def.getParameter().get(2).getUse()); + assertEquals(2, def.getParameter().get(2).getMin()); + assertEquals("5", def.getParameter().get(2).getMax()); + + assertEquals("PARAM4", def.getParameter().get(3).getName()); + assertEquals(OperationParameterUse.IN, def.getParameter().get(3).getUse()); + assertEquals(1, def.getParameter().get(3).getMin()); + assertEquals("*", def.getParameter().get(3).getMax()); + + } private List toOpNames(List theOps) { ArrayList retVal = new ArrayList(); @@ -530,6 +569,7 @@ public class OperationServerDstu3Test { } + public static void main(String[] theValue) { Parameters p = new Parameters(); p.addParameter().setName("start").setValue(new DateTimeType("2001-01-02")); @@ -617,7 +657,9 @@ public class OperationServerDstu3Test { @Operation(name="$OP_TYPE", idempotent=true) public Parameters opType( @OperationParam(name="PARAM1") StringType theParam1, - @OperationParam(name="PARAM2") Patient theParam2 + @OperationParam(name="PARAM2") Patient theParam2, + @OperationParam(name="PARAM3", min=2, max=5) List theParam3, + @OperationParam(name="PARAM4", min=1) List theParam4 ) { //@formatter:on diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c94648ad377..de912d34c91 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -324,6 +324,14 @@ server conformance statement should not include the $ prefix in the operation name or code. Thanks to Dion McMurtrie for reporting! + + OperationDefinition resources generated automatically by the server for operations + that are defined within resource/plain providers incorrectly stated that + the maximum cardinality was "*" for non-collection types with no explicit + maximum stated, which is not the behaviour that the JavaDoc on the +