Skip to content

Commit 8e54656

Browse files
jdar8jdar
andauthored
support patch history rewrite (#7024)
* initial implementation, tests * changelog, docs * spotless * fix refactor causing test failure * remove todo's and failing test for delete bug - move to new bug ticket * link to docs in changelog * address review comments * undo a review comments * fix test --------- Co-authored-by: jdar <[email protected]>
1 parent 63b8dbc commit 8e54656

File tree

12 files changed

+573
-136
lines changed

12 files changed

+573
-136
lines changed

hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IPatchWithBody.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,9 @@ public interface IPatchWithBody extends IPatchExecutable {
5959
* The resource ID to patch (must include both a resource type and an ID, e.g. <code>Patient/123</code>)
6060
*/
6161
IPatchExecutable withId(String theId);
62+
63+
/**
64+
* Call patch with the history rewrite header
65+
*/
66+
IPatchWithBody historyRewrite();
6267
}

hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,7 @@ private class PatchInternal extends BaseSearch<IPatchExecutable, IPatchWithQuery
17411741
private PreferReturnEnum myPrefer;
17421742
private String myResourceType;
17431743
private String mySearchUrl;
1744+
private boolean myIsHistoryRewrite;
17441745

17451746
@Override
17461747
public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) {
@@ -1775,7 +1776,17 @@ public MethodOutcome execute() {
17751776
}
17761777

17771778
BaseHttpClientInvocation invocation;
1778-
if (isNotBlank(mySearchUrl)) {
1779+
if (myIsHistoryRewrite) {
1780+
if (!myId.hasVersionIdPart()) {
1781+
throw new InvalidRequestException(Msg.code(2716)
1782+
+ "Invalid resource ID for rewrite history: ID must contain a history version");
1783+
}
1784+
// createPatchInvocation() eventually calls HttpPatchClientInvocation
1785+
// passing in the ID as a URL (String) will keep the version whereas passing in the raw ID will remove
1786+
// the version
1787+
invocation = MethodUtil.createPatchInvocation(myContext, myId.getValue(), myPatchType, myPatchBody);
1788+
invocation.addHeader(Constants.HEADER_REWRITE_HISTORY, "true");
1789+
} else if (isNotBlank(mySearchUrl)) {
17791790
invocation = MethodUtil.createPatchInvocation(myContext, mySearchUrl, myPatchType, myPatchBody);
17801791
} else if (myConditional) {
17811792
invocation = MethodUtil.createPatchInvocation(
@@ -1854,6 +1865,12 @@ public IPatchExecutable withId(String theId) {
18541865
}
18551866
return withId(new IdDt(theId));
18561867
}
1868+
1869+
@Override
1870+
public IPatchWithBody historyRewrite() {
1871+
myIsHistoryRewrite = true;
1872+
return this;
1873+
}
18571874
}
18581875

18591876
@SuppressWarnings({"rawtypes", "unchecked"})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
type: add
3+
issue: 7023
4+
jira: SMILE-8730
5+
title: "The HTTP Patch operation now supports the `X-Rewrite-History` header. This enables a specific version of a resource
6+
to be rewritten in-place without the resource version being incremented by using the PATCH operation.
7+
See [Patch with History Rewrite](/hapi-fhir/docs/server_plain/rest_operations.html#patch-with-history-rewrite) for more information."

hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/rest_operations.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,45 @@ The following snippet shows how to define a patch method on a server:
208208
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/PatchExamples.java|patch}}
209209
```
210210

211+
## Patch with History Rewrite
212+
213+
If you wish to patch a historical version of a resource without creating a new version, this can now be done in the
214+
`Patch` operation. While this operation is not supported by the FHIR specification, it's an enhancement added to
215+
specifically to HAPI-FHIR.
216+
217+
In order to use this new functionality, you must set the `myUpdateWithHistoryRewriteEnabled` setting in the `StorageSettings`
218+
to true.
219+
220+
The request must include the header `X-Rewrite-History`, and should be set to true. The body of the request must include
221+
the desired FHIR Patch or JSON Patch. Note that transaction bundles are not yet supported.
222+
223+
The following API request shows an example of executing a FHIR Patch that updates a Patient's birthday without incrementing
224+
the resource version:
225+
226+
227+
```http
228+
PATCH Patient/123/_history/2
229+
Content-Type: application/fhir+json
230+
X-Rewrite-History: true
231+
232+
{
233+
"resourceType": "Parameters",
234+
"parameter": [ {
235+
"name": "operation",
236+
"part": [ {
237+
"name": "type",
238+
"valueCode": "replace"
239+
}, {
240+
"name": "path",
241+
"valueString": "Patient.birthDate"
242+
}, {
243+
"name": "value",
244+
"valueDate": "1930-01-01"
245+
} ]
246+
} ]
247+
}
248+
```
249+
211250

212251
<a name="type_create" />
213252

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,8 +2385,8 @@ public DaoMethodOutcome update(
23852385
if (myStorageSettings.isUpdateWithHistoryRewriteEnabled()
23862386
&& theRequest != null
23872387
&& theRequest.isRewriteHistory()) {
2388-
updateCallback = () ->
2389-
doUpdateWithHistoryRewrite(theResource, theRequest, theTransactionDetails, requestPartitionId);
2388+
updateCallback = () -> doUpdateWithHistoryRewrite(
2389+
theResource, theRequest, theTransactionDetails, requestPartitionId, RestOperationTypeEnum.UPDATE);
23902390
} else {
23912391
updateCallback = () -> doUpdate(
23922392
theResource,
@@ -2620,13 +2620,16 @@ protected DaoMethodOutcome doUpdateForUpdateOrPatch(UpdateParameters<T> theUpdat
26202620
* @param theResource to be saved
26212621
* @param theRequest details of the request
26222622
* @param theTransactionDetails details of the transaction
2623+
* @param theRequestPartitionId the partition on which to perform the request
2624+
* @param theRestOperationType the rest operation type (update or patch)
26232625
* @return the outcome of the operation
26242626
*/
2625-
private DaoMethodOutcome doUpdateWithHistoryRewrite(
2627+
DaoMethodOutcome doUpdateWithHistoryRewrite(
26262628
T theResource,
26272629
RequestDetails theRequest,
26282630
TransactionDetails theTransactionDetails,
2629-
RequestPartitionId theRequestPartitionId) {
2631+
RequestPartitionId theRequestPartitionId,
2632+
RestOperationTypeEnum theRestOperationType) {
26302633
StopWatch w = new StopWatch();
26312634

26322635
// No need for indexing as this will update a non-current version of the resource which will not be searchable
@@ -2669,11 +2672,10 @@ private DaoMethodOutcome doUpdateWithHistoryRewrite(
26692672
&& Long.parseLong(resourceId.getVersionIdPart()) == currentEntity.getVersion();
26702673
IBasePersistedResource<?> savedEntity = updateHistoryEntity(
26712674
theRequest, theResource, currentEntity, entity, resourceId, theTransactionDetails, isUpdatingCurrent);
2672-
DaoMethodOutcome outcome = toMethodOutcome(
2673-
theRequest, savedEntity, theResource, null, RestOperationTypeEnum.UPDATE)
2675+
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource, null, theRestOperationType)
26742676
.setCreated(wasDeleted);
26752677

2676-
populateOperationOutcomeForUpdate(w, outcome, null, RestOperationTypeEnum.UPDATE, theTransactionDetails);
2678+
populateOperationOutcomeForUpdate(w, outcome, null, theRestOperationType, theTransactionDetails);
26772679

26782680
return outcome;
26792681
}

0 commit comments

Comments
 (0)