
001/*- 002 * #%L 003 * HAPI FHIR Storage api 004 * %% 005 * Copyright (C) 2014 - 2025 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.replacereferences; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.interceptor.model.RequestPartitionId; 024import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 025import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 026import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; 027import ca.uhn.fhir.model.primitive.IdDt; 028import ca.uhn.fhir.rest.api.server.RequestDetails; 029import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.hl7.fhir.instance.model.api.IIdType; 032import org.hl7.fhir.r4.model.Reference; 033 034import java.util.List; 035 036/** 037 * This is a class to restore resources to their previous versions based on the provided versioned resource references. 038 * It is used in the context of undoing changes made by the $hapi.fhir.replace-references operation. 039 */ 040public class PreviousResourceVersionRestorer { 041 042 private final HapiTransactionService myHapiTransactionService; 043 private final DaoRegistry myDaoRegistry; 044 045 public PreviousResourceVersionRestorer( 046 DaoRegistry theDaoRegistry, HapiTransactionService theHapiTransactionService) { 047 myDaoRegistry = theDaoRegistry; 048 myHapiTransactionService = theHapiTransactionService; 049 } 050 051 /** 052 * Given a list of versioned resource references, this method restores each resource to its previous version 053 * if the resource's current version is the same as specified in the given reference 054 * (i.e. the resource was not updated since the reference was created). 055 * 056 * This method is transactional and will attempt to restore all resources in a single transaction. 057 * 058 * Note that this method updates a resource using its previous version's content, 059 * so it will actually cause a new version to be created (i.e. it does not rewrite the history). 060 * 061 * @throws IllegalArgumentException if a given reference is versionless 062 * @throws IllegalArgumentException a given reference has version 1, so it cannot have a previous version to restore to. 063 * @throws ResourceVersionConflictException if the current version of the resource does not match the version specified in the reference. 064 */ 065 public void restoreToPreviousVersionsInTrx( 066 List<Reference> theReferences, RequestDetails theRequestDetails, RequestPartitionId thePartitionId) { 067 myHapiTransactionService 068 .withRequest(theRequestDetails) 069 .withRequestPartitionId(thePartitionId) 070 .execute(() -> restoreToPreviousVersions(theReferences, theRequestDetails)); 071 } 072 073 private void restoreToPreviousVersions(List<Reference> theReferences, RequestDetails theRequestDetails) { 074 for (Reference reference : theReferences) { 075 String referenceStr = reference.getReference(); 076 IIdType referenceId = new IdDt(referenceStr); 077 078 if (!referenceId.hasVersionIdPart()) { 079 throw new IllegalArgumentException( 080 Msg.code(2730) + "Reference does not have a version: " + referenceStr); 081 } 082 Long referenceVersion = referenceId.getVersionIdPartAsLong(); 083 084 // Restore previous version (version - 1) 085 long previousVersion = referenceVersion - 1; 086 if (previousVersion < 1) { 087 throw new IllegalArgumentException(Msg.code(2731) 088 + "Resource cannot be restored to a previous as the provided version is 1: " + referenceStr); 089 } 090 091 // Read the current resource 092 IFhirResourceDao<IBaseResource> dao = myDaoRegistry.getResourceDao(referenceId.getResourceType()); 093 IBaseResource currentResource = dao.read(referenceId.toUnqualifiedVersionless(), theRequestDetails); 094 095 // Check current version 096 Long currentVersion = currentResource.getIdElement().getVersionIdPartAsLong(); 097 if (!currentVersion.equals(referenceVersion)) { 098 String msg = String.format( 099 "The resource cannot be restored because the current version of resource %s (%s) does not match the expected version (%s)", 100 referenceStr, currentVersion, referenceVersion); 101 throw new ResourceVersionConflictException(Msg.code(2732) + msg); 102 } 103 104 IIdType previousId = referenceId.withVersion(Long.toString(previousVersion)); 105 IBaseResource previousResource = dao.read(previousId, theRequestDetails); 106 previousResource.setId(previousResource.getIdElement().toUnqualifiedVersionless()); 107 108 // Update the resource to the previous version's content 109 dao.update(previousResource, theRequestDetails); 110 } 111 } 112}