From 2de9b5aa034b0514d3aafbaa6cc6c8c34f39927f Mon Sep 17 00:00:00 2001 From: samuelwlee2 <100384063+samuelwlee2@users.noreply.github.com> Date: Wed, 12 Oct 2022 08:06:22 -0600 Subject: [PATCH] added attribute to Operation annotation (#4120) * added attribute to Operation annotation * add find operation id * update readOperationDefinition method * add example to canonicalUrl * adding more examples --- .../uhn/fhir/rest/annotation/Operation.java | 10 +++++++ ...new-annotation-attribute-to-operation.yaml | 6 ++++ .../r4/BaseJpaResourceProviderPatientR4.java | 6 ++-- .../server/method/OperationMethodBinding.java | 7 ++++- .../ServerCapabilityStatementProvider.java | 25 ++++++++++++---- ...rverCapabilityStatementProviderR4Test.java | 30 +++++++++++++++++++ 6 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/4116-add-new-annotation-attribute-to-operation.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java index d811038550a..eb0018fd088 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java @@ -150,4 +150,14 @@ public @interface Operation { */ boolean global() default false; + /** + * The canonical URL of the operation, e.g. "http://hl7.org/fhir/us/davinci-hrex/OperationDefinition/member-match|1.0.0" + * + *

+ * This may be specified with or without a version. e.g. @Operation(name = "$everything", canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-everything") + * or @Operation(name = "$member-match", canonicalUrl = "http://hl7.org/fhir/us/davinci-hrex/OperationDefinition/member-match|1.0.0") + *

+ */ + String canonicalUrl() default ""; + } diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/4116-add-new-annotation-attribute-to-operation.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/4116-add-new-annotation-attribute-to-operation.yaml new file mode 100644 index 00000000000..684cac3dcb1 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/4116-add-new-annotation-attribute-to-operation.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 4116 +jira: SMILE-5055 +title: "Added new attribute for the @Operation annotation to define the operation's canonical URL. This canonical URL value will populate +the operation definition in the CapabilityStatement resource." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java index 8b56251d1c5..9866aa69469 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/r4/BaseJpaResourceProviderPatientR4.java @@ -66,7 +66,7 @@ public abstract class BaseJpaResourceProviderPatientR4 extends JpaResourceProvid /** * Patient/123/$everything */ - @Operation(name = JpaConstants.OPERATION_EVERYTHING, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET) + @Operation(name = JpaConstants.OPERATION_EVERYTHING, canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-everything", idempotent = true, bundleType = BundleTypeEnum.SEARCHSET) public IBundleProvider patientInstanceEverything( javax.servlet.http.HttpServletRequest theServletRequest, @@ -129,7 +129,7 @@ public abstract class BaseJpaResourceProviderPatientR4 extends JpaResourceProvid /** * /Patient/$everything */ - @Operation(name = JpaConstants.OPERATION_EVERYTHING, idempotent = true, bundleType = BundleTypeEnum.SEARCHSET) + @Operation(name = JpaConstants.OPERATION_EVERYTHING, canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-everything", idempotent = true, bundleType = BundleTypeEnum.SEARCHSET) public IBundleProvider patientTypeEverything( javax.servlet.http.HttpServletRequest theServletRequest, @@ -197,7 +197,7 @@ public abstract class BaseJpaResourceProviderPatientR4 extends JpaResourceProvid * Basic implementation matching by coverage id or by coverage identifier. Not matching by * Beneficiary (Patient) demographics in this version */ - @Operation(name = ProviderConstants.OPERATION_MEMBER_MATCH, idempotent = false, returnParameters = { + @Operation(name = ProviderConstants.OPERATION_MEMBER_MATCH, canonicalUrl = "http://hl7.org/fhir/us/davinci-hrex/OperationDefinition/member-match", idempotent = false, returnParameters = { @OperationParam(name = "MemberIdentifier", typeName = "string") }) public Parameters patientMemberMatch( diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java index 185a3ba0e19..d8b2098e878 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/OperationMethodBinding.java @@ -76,6 +76,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { private List myReturnParams; private boolean myManualRequestMode; private boolean myManualResponseMode; + private String myCanonicalUrl; /** * Constructor - This is the constructor that is called when binding a @@ -85,7 +86,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { Operation theAnnotation) { this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.deleteEnabled(), theAnnotation.name(), theAnnotation.type(), theAnnotation.typeName(), theAnnotation.returnParameters(), theAnnotation.bundleType(), theAnnotation.global()); - + myCanonicalUrl = theAnnotation.canonicalUrl(); myManualRequestMode = theAnnotation.manualRequest(); myManualResponseMode = theAnnotation.manualResponse(); } @@ -382,6 +383,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { return myManualRequestMode; } + public String getCanonicalUrl() { + return myCanonicalUrl; + } + public static class ReturnType { private int myMax; private int myMin; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java index d8d94e2bf3c..0acc56332c2 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ServerCapabilityStatementProvider.java @@ -20,7 +20,6 @@ import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerConfiguration; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.method.BaseMethodBinding; -import ca.uhn.fhir.rest.server.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.method.IParameter; import ca.uhn.fhir.rest.server.method.OperationMethodBinding; import ca.uhn.fhir.rest.server.method.OperationMethodBinding.ReturnType; @@ -101,7 +100,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv private final IValidationSupport myValidationSupport; private String myPublisher = "Not provided"; private boolean myRestResourceRevIncludesEnabled = DEFAULT_REST_RESOURCE_REV_INCLUDES_ENABLED; - + private HashMap operationCanonicalUrlToId = new HashMap<>(); /** * Constructor */ @@ -416,7 +415,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv } String spUri = next.getUri(); - + if (isNotBlank(spUri)) { terser.addElement(searchParam, "definition", spUri); } @@ -555,7 +554,14 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv private void populateOperation(RequestDetails theRequestDetails, FhirTerser theTerser, OperationMethodBinding theMethodBinding, String theOpName, IBase theOperation) { String operationName = theMethodBinding.getName().substring(1); theTerser.addElement(theOperation, "name", operationName); - theTerser.addElement(theOperation, "definition", createOperationUrl(theRequestDetails, theOpName)); + String operationCanonicalUrl = theMethodBinding.getCanonicalUrl(); + if (isNotBlank(operationCanonicalUrl)) { + theTerser.addElement(theOperation, "definition", operationCanonicalUrl); + operationCanonicalUrlToId.put(operationCanonicalUrl, theOpName); + } + else { + theTerser.addElement(theOperation, "definition", createOperationUrl(theRequestDetails, theOpName)); + } if (isNotBlank(theMethodBinding.getDescription())) { theTerser.addElement(theOperation, "documentation", theMethodBinding.getDescription()); } @@ -634,8 +640,8 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv } RestfulServerConfiguration configuration = getServerConfiguration(); Bindings bindings = configuration.provideBindings(); - - List operationBindings = bindings.getOperationIdToBindings().get(theId.getIdPart()); + String operationId = getOperationId(theId); + List operationBindings = bindings.getOperationIdToBindings().get(operationId); if (operationBindings != null && !operationBindings.isEmpty()) { return readOperationDefinitionForOperation(theRequestDetails, bindings, operationBindings); } @@ -647,6 +653,13 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv throw new ResourceNotFoundException(Msg.code(1978) + theId); } + private String getOperationId(IIdType theId) { + if (operationCanonicalUrlToId.get(theId.getValue()) !=null ) { + return operationCanonicalUrlToId.get(theId.getValue()); + } + return theId.getIdPart(); + } + private IBaseResource readOperationDefinitionForNamedSearch(List bindings) { IBaseResource op = myContext.getResourceDefinition("OperationDefinition").newInstance(); FhirTerser terser = myContext.newTerser(); diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java index 3a0aa514c7a..4fe0df5ff17 100644 --- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/ServerCapabilityStatementProviderR4Test.java @@ -1292,6 +1292,23 @@ public class ServerCapabilityStatementProviderR4Test { assertThat(toOperationNames(groupResource.getOperation()),containsInAnyOrder("export", "export-poll-status")); } + @Test + public void testOperationReturningCanonicalUrl() throws Exception { + RestfulServer rs = new RestfulServer(myCtx); + rs.setProviders(new ProviderWithOperationReturningCanonicalUrl()); + ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {}; + rs.setServerConformanceProvider(sc); + rs.init(createServletConfig()); + + CapabilityStatement conformance = (CapabilityStatement) sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs)); + assertEquals(2, conformance.getRest().get(0).getResource().size()); + List res = conformance.getRest().get(0).getResource().get(1).getOperation(); + assertEquals(1, res.size()); + CapabilityStatementRestResourceOperationComponent operationComponent = res.get(0); + assertEquals("everything", operationComponent.getName()); + assertEquals("http://hl7.org/fhir/OperationDefinition/Patient-everything", operationComponent.getDefinition()); + } + private List toOperationIdParts(List theOperation) { ArrayList retVal = Lists.newArrayList(); for (CapabilityStatementRestResourceOperationComponent next : theOperation) { @@ -1534,6 +1551,19 @@ public class ServerCapabilityStatementProviderR4Test { } + public static class ProviderWithOperationReturningCanonicalUrl implements IResourceProvider { + @Operation(name = "everything", canonicalUrl = "http://hl7.org/fhir/OperationDefinition/Patient-everything", idempotent = true) + public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, + @OperationParam(name = "someOpParam1") DateType theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) { + return null; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + } + @SuppressWarnings("unused") public static class ReadProvider {