001/*- 002 * #%L 003 * HAPI FHIR Storage api 004 * %% 005 * Copyright (C) 2014 - 2024 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.jpa.dao.expunge; 021 022import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 023import ca.uhn.fhir.jpa.api.model.ExpungeOptions; 024import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; 025import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; 026import ca.uhn.fhir.rest.api.server.RequestDetails; 027import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 028import com.google.common.annotations.VisibleForTesting; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031import org.springframework.beans.factory.annotation.Autowired; 032import org.springframework.context.annotation.Scope; 033import org.springframework.stereotype.Component; 034 035import java.util.List; 036import java.util.concurrent.Callable; 037import java.util.concurrent.atomic.AtomicInteger; 038 039@Component 040@Scope("prototype") 041public class ExpungeOperation implements Callable<ExpungeOutcome> { 042 private static final Logger ourLog = LoggerFactory.getLogger(ExpungeService.class); 043 public static final String PROCESS_NAME = "Expunging"; 044 public static final String THREAD_PREFIX = "expunge"; 045 046 @Autowired 047 private IResourceExpungeService myResourceExpungeService; 048 049 @Autowired 050 private JpaStorageSettings myStorageSettings; 051 052 private final String myResourceName; 053 private final IResourcePersistentId myResourceId; 054 private final ExpungeOptions myExpungeOptions; 055 private final RequestDetails myRequestDetails; 056 private final AtomicInteger myRemainingCount; 057 058 @Autowired 059 private HapiTransactionService myTxService; 060 061 public ExpungeOperation( 062 String theResourceName, 063 IResourcePersistentId theResourceId, 064 ExpungeOptions theExpungeOptions, 065 RequestDetails theRequestDetails) { 066 myResourceName = theResourceName; 067 myResourceId = theResourceId; 068 myExpungeOptions = theExpungeOptions; 069 myRequestDetails = theRequestDetails; 070 myRemainingCount = new AtomicInteger(myExpungeOptions.getLimit()); 071 } 072 073 @Override 074 public ExpungeOutcome call() { 075 if (myExpungeOptions.isExpungeDeletedResources() 076 && (myResourceId == null || myResourceId.getVersion() == null)) { 077 expungeDeletedResources(); 078 if (expungeLimitReached()) { 079 return expungeOutcome(); 080 } 081 } 082 083 if (myExpungeOptions.isExpungeOldVersions()) { 084 expungeOldVersions(); 085 if (expungeLimitReached()) { 086 return expungeOutcome(); 087 } 088 } 089 090 return expungeOutcome(); 091 } 092 093 private void expungeDeletedResources() { 094 List<IResourcePersistentId> resourceIds = findHistoricalVersionsOfDeletedResources(); 095 096 deleteHistoricalVersions(resourceIds); 097 if (expungeLimitReached()) { 098 return; 099 } 100 101 deleteCurrentVersionsOfDeletedResources(resourceIds); 102 } 103 104 private List<IResourcePersistentId> findHistoricalVersionsOfDeletedResources() { 105 List<IResourcePersistentId> retVal = getPartitionAwareSupplier() 106 .supplyInPartitionedContext(() -> myResourceExpungeService.findHistoricalVersionsOfDeletedResources( 107 myResourceName, myResourceId, myRemainingCount.get())); 108 109 ourLog.debug("Found {} historical versions", retVal.size()); 110 return retVal; 111 } 112 113 private boolean expungeLimitReached() { 114 boolean expungeLimitReached = myRemainingCount.get() <= 0; 115 if (expungeLimitReached) { 116 ourLog.debug("Expunge limit has been hit - Stopping operation"); 117 } 118 return expungeLimitReached; 119 } 120 121 private void expungeOldVersions() { 122 List<IResourcePersistentId> historicalIds = getPartitionAwareSupplier() 123 .supplyInPartitionedContext(() -> myResourceExpungeService.findHistoricalVersionsOfNonDeletedResources( 124 myResourceName, myResourceId, myRemainingCount.get())); 125 126 getPartitionRunner() 127 .runInPartitionedThreads( 128 historicalIds, 129 partition -> myResourceExpungeService.expungeHistoricalVersions( 130 myRequestDetails, partition, myRemainingCount)); 131 } 132 133 private PartitionAwareSupplier getPartitionAwareSupplier() { 134 return new PartitionAwareSupplier(myTxService, myRequestDetails); 135 } 136 137 private PartitionRunner getPartitionRunner() { 138 return new PartitionRunner( 139 PROCESS_NAME, 140 THREAD_PREFIX, 141 myStorageSettings.getExpungeBatchSize(), 142 myStorageSettings.getExpungeThreadCount(), 143 myTxService, 144 myRequestDetails); 145 } 146 147 private void deleteCurrentVersionsOfDeletedResources(List<IResourcePersistentId> theResourceIds) { 148 getPartitionRunner() 149 .runInPartitionedThreads( 150 theResourceIds, 151 partition -> myResourceExpungeService.expungeCurrentVersionOfResources( 152 myRequestDetails, partition, myRemainingCount)); 153 } 154 155 private void deleteHistoricalVersions(List<IResourcePersistentId> theResourceIds) { 156 getPartitionRunner() 157 .runInPartitionedThreads( 158 theResourceIds, 159 partition -> myResourceExpungeService.expungeHistoricalVersionsOfIds( 160 myRequestDetails, partition, myRemainingCount)); 161 } 162 163 private ExpungeOutcome expungeOutcome() { 164 return new ExpungeOutcome().setDeletedCount(myExpungeOptions.getLimit() - myRemainingCount.get()); 165 } 166 167 @VisibleForTesting 168 public void setHapiTransactionServiceForTesting(HapiTransactionService theHapiTransactionService) { 169 myTxService = theHapiTransactionService; 170 } 171 172 @VisibleForTesting 173 public void setStorageSettingsForTesting(JpaStorageSettings theStorageSettings) { 174 myStorageSettings = theStorageSettings; 175 } 176 177 @VisibleForTesting 178 public void setExpungeDaoServiceForTesting(IResourceExpungeService theIResourceExpungeService) { 179 myResourceExpungeService = theIResourceExpungeService; 180 } 181}