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 extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+ }
+
@SuppressWarnings("unused")
public static class ReadProvider {