001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2023 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.interceptor.api.HookParams; 023import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 024import ca.uhn.fhir.interceptor.api.Pointcut; 025import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; 026import ca.uhn.fhir.interceptor.model.RequestPartitionId; 027import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; 028import ca.uhn.fhir.jpa.entity.Batch2JobInstanceEntity; 029import ca.uhn.fhir.jpa.entity.Batch2WorkChunkEntity; 030import ca.uhn.fhir.jpa.entity.BulkImportJobEntity; 031import ca.uhn.fhir.jpa.entity.BulkImportJobFileEntity; 032import ca.uhn.fhir.jpa.entity.MdmLink; 033import ca.uhn.fhir.jpa.entity.PartitionEntity; 034import ca.uhn.fhir.jpa.entity.Search; 035import ca.uhn.fhir.jpa.entity.SearchInclude; 036import ca.uhn.fhir.jpa.entity.SearchResult; 037import ca.uhn.fhir.jpa.entity.SubscriptionTable; 038import ca.uhn.fhir.jpa.entity.TermCodeSystem; 039import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; 040import ca.uhn.fhir.jpa.entity.TermConcept; 041import ca.uhn.fhir.jpa.entity.TermConceptDesignation; 042import ca.uhn.fhir.jpa.entity.TermConceptMap; 043import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; 044import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; 045import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; 046import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; 047import ca.uhn.fhir.jpa.entity.TermConceptProperty; 048import ca.uhn.fhir.jpa.entity.TermValueSet; 049import ca.uhn.fhir.jpa.entity.TermValueSetConcept; 050import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; 051import ca.uhn.fhir.jpa.model.entity.ForcedId; 052import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity; 053import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity; 054import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity; 055import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; 056import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; 057import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag; 058import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; 059import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; 060import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; 061import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; 062import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; 063import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; 064import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; 065import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 066import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 067import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; 068import ca.uhn.fhir.jpa.model.entity.ResourceLink; 069import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity; 070import ca.uhn.fhir.jpa.model.entity.ResourceTable; 071import ca.uhn.fhir.jpa.model.entity.ResourceTag; 072import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; 073import ca.uhn.fhir.jpa.model.entity.TagDefinition; 074import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 075import ca.uhn.fhir.jpa.util.MemoryCacheService; 076import ca.uhn.fhir.rest.api.server.RequestDetails; 077import ca.uhn.fhir.rest.server.provider.ProviderConstants; 078import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 079import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 080import ca.uhn.fhir.util.StopWatch; 081import org.slf4j.Logger; 082import org.slf4j.LoggerFactory; 083import org.springframework.beans.factory.annotation.Autowired; 084import org.springframework.stereotype.Service; 085import org.springframework.transaction.annotation.Propagation; 086 087import java.util.List; 088import java.util.concurrent.atomic.AtomicInteger; 089import javax.annotation.Nullable; 090import javax.persistence.EntityManager; 091import javax.persistence.PersistenceContext; 092import javax.persistence.PersistenceContextType; 093import javax.persistence.TypedQuery; 094import javax.persistence.criteria.CriteriaBuilder; 095import javax.persistence.criteria.CriteriaQuery; 096 097@Service 098public class ExpungeEverythingService implements IExpungeEverythingService { 099 private static final Logger ourLog = LoggerFactory.getLogger(ExpungeEverythingService.class); 100 101 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 102 protected EntityManager myEntityManager; 103 104 @Autowired 105 protected IInterceptorBroadcaster myInterceptorBroadcaster; 106 107 @Autowired 108 private HapiTransactionService myTxService; 109 110 @Autowired 111 private MemoryCacheService myMemoryCacheService; 112 113 @Autowired 114 private IRequestPartitionHelperSvc myRequestPartitionHelperSvc; 115 116 private int deletedResourceEntityCount; 117 118 @Override 119 public void expungeEverything(@Nullable RequestDetails theRequest) { 120 121 final AtomicInteger counter = new AtomicInteger(); 122 123 // Notify Interceptors about pre-action call 124 HookParams hooks = new HookParams() 125 .add(AtomicInteger.class, counter) 126 .add(RequestDetails.class, theRequest) 127 .addIfMatchesType(ServletRequestDetails.class, theRequest); 128 CompositeInterceptorBroadcaster.doCallHooks( 129 myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks); 130 131 ourLog.info("BEGINNING GLOBAL $expunge"); 132 Propagation propagation = Propagation.REQUIRES_NEW; 133 ReadPartitionIdRequestDetails details = 134 ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPUNGE); 135 RequestPartitionId requestPartitionId = 136 myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequest, details); 137 138 myTxService 139 .withRequest(theRequest) 140 .withPropagation(propagation) 141 .withRequestPartitionId(requestPartitionId) 142 .execute(() -> { 143 counter.addAndGet(doExpungeEverythingQuery( 144 "UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null")); 145 }); 146 counter.addAndGet( 147 expungeEverythingByTypeWithoutPurging(theRequest, Batch2WorkChunkEntity.class, requestPartitionId)); 148 counter.addAndGet( 149 expungeEverythingByTypeWithoutPurging(theRequest, Batch2JobInstanceEntity.class, requestPartitionId)); 150 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 151 theRequest, NpmPackageVersionResourceEntity.class, requestPartitionId)); 152 counter.addAndGet( 153 expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageVersionEntity.class, requestPartitionId)); 154 counter.addAndGet( 155 expungeEverythingByTypeWithoutPurging(theRequest, NpmPackageEntity.class, requestPartitionId)); 156 counter.addAndGet( 157 expungeEverythingByTypeWithoutPurging(theRequest, SearchParamPresentEntity.class, requestPartitionId)); 158 counter.addAndGet( 159 expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobFileEntity.class, requestPartitionId)); 160 counter.addAndGet( 161 expungeEverythingByTypeWithoutPurging(theRequest, BulkImportJobEntity.class, requestPartitionId)); 162 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ForcedId.class, requestPartitionId)); 163 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 164 theRequest, ResourceIndexedSearchParamDate.class, requestPartitionId)); 165 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 166 theRequest, ResourceIndexedSearchParamNumber.class, requestPartitionId)); 167 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 168 theRequest, ResourceIndexedSearchParamQuantity.class, requestPartitionId)); 169 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 170 theRequest, ResourceIndexedSearchParamQuantityNormalized.class, requestPartitionId)); 171 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 172 theRequest, ResourceIndexedSearchParamString.class, requestPartitionId)); 173 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 174 theRequest, ResourceIndexedSearchParamToken.class, requestPartitionId)); 175 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 176 theRequest, ResourceIndexedSearchParamUri.class, requestPartitionId)); 177 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 178 theRequest, ResourceIndexedSearchParamCoords.class, requestPartitionId)); 179 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 180 theRequest, ResourceIndexedComboStringUnique.class, requestPartitionId)); 181 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 182 theRequest, ResourceIndexedComboTokenNonUnique.class, requestPartitionId)); 183 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceLink.class, requestPartitionId)); 184 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchResult.class, requestPartitionId)); 185 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, SearchInclude.class, requestPartitionId)); 186 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 187 theRequest, TermValueSetConceptDesignation.class, requestPartitionId)); 188 counter.addAndGet( 189 expungeEverythingByTypeWithoutPurging(theRequest, TermValueSetConcept.class, requestPartitionId)); 190 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermValueSet.class, requestPartitionId)); 191 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 192 theRequest, TermConceptParentChildLink.class, requestPartitionId)); 193 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 194 theRequest, TermConceptMapGroupElementTarget.class, requestPartitionId)); 195 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 196 theRequest, TermConceptMapGroupElement.class, requestPartitionId)); 197 counter.addAndGet( 198 expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMapGroup.class, requestPartitionId)); 199 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConceptMap.class, requestPartitionId)); 200 counter.addAndGet( 201 expungeEverythingByTypeWithoutPurging(theRequest, TermConceptProperty.class, requestPartitionId)); 202 counter.addAndGet( 203 expungeEverythingByTypeWithoutPurging(theRequest, TermConceptDesignation.class, requestPartitionId)); 204 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermConcept.class, requestPartitionId)); 205 myTxService 206 .withRequest(theRequest) 207 .withPropagation(propagation) 208 .withRequestPartitionId(requestPartitionId) 209 .execute(() -> { 210 for (TermCodeSystem next : myEntityManager 211 .createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class) 212 .getResultList()) { 213 next.setCurrentVersion(null); 214 myEntityManager.merge(next); 215 } 216 }); 217 counter.addAndGet( 218 expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystemVersion.class, requestPartitionId)); 219 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TermCodeSystem.class, requestPartitionId)); 220 counter.addAndGet( 221 expungeEverythingByTypeWithoutPurging(theRequest, SubscriptionTable.class, requestPartitionId)); 222 counter.addAndGet( 223 expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTag.class, requestPartitionId)); 224 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTag.class, requestPartitionId)); 225 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, TagDefinition.class, requestPartitionId)); 226 counter.addAndGet(expungeEverythingByTypeWithoutPurging( 227 theRequest, ResourceHistoryProvenanceEntity.class, requestPartitionId)); 228 counter.addAndGet( 229 expungeEverythingByTypeWithoutPurging(theRequest, ResourceHistoryTable.class, requestPartitionId)); 230 counter.addAndGet( 231 expungeEverythingByTypeWithoutPurging(theRequest, ResourceSearchUrlEntity.class, requestPartitionId)); 232 int counterBefore = counter.get(); 233 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, ResourceTable.class, requestPartitionId)); 234 counter.addAndGet(expungeEverythingByTypeWithoutPurging(theRequest, PartitionEntity.class, requestPartitionId)); 235 236 deletedResourceEntityCount = counter.get() - counterBefore; 237 238 myTxService 239 .withRequest(theRequest) 240 .withPropagation(propagation) 241 .withRequestPartitionId(requestPartitionId) 242 .execute(() -> { 243 counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d")); 244 }); 245 246 purgeAllCaches(); 247 248 ourLog.info("COMPLETED GLOBAL $expunge - Deleted {} rows", counter.get()); 249 } 250 251 @Override 252 public int getExpungeDeletedEntityCount() { 253 return deletedResourceEntityCount; 254 } 255 256 private void purgeAllCaches() { 257 myMemoryCacheService.invalidateAllCaches(); 258 } 259 260 private int expungeEverythingByTypeWithoutPurging( 261 RequestDetails theRequest, Class<?> theEntityType, RequestPartitionId theRequestPartitionId) { 262 int outcome = 0; 263 while (true) { 264 StopWatch sw = new StopWatch(); 265 266 int count = myTxService 267 .withRequest(theRequest) 268 .withPropagation(Propagation.REQUIRES_NEW) 269 .withRequestPartitionId(theRequestPartitionId) 270 .execute(() -> { 271 CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); 272 CriteriaQuery<?> cq = cb.createQuery(theEntityType); 273 cq.from(theEntityType); 274 TypedQuery<?> query = myEntityManager.createQuery(cq); 275 query.setMaxResults(1000); 276 List<?> results = query.getResultList(); 277 for (Object result : results) { 278 myEntityManager.remove(result); 279 } 280 return results.size(); 281 }); 282 283 outcome += count; 284 if (count == 0) { 285 break; 286 } 287 288 ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw); 289 } 290 return outcome; 291 } 292 293 @Override 294 public int expungeEverythingByType(Class<?> theEntityType) { 295 int result = expungeEverythingByTypeWithoutPurging(null, theEntityType, RequestPartitionId.allPartitions()); 296 purgeAllCaches(); 297 return result; 298 } 299 300 @Override 301 public int expungeEverythingMdmLinks() { 302 return expungeEverythingByType(MdmLink.class); 303 } 304 305 private int doExpungeEverythingQuery(String theQuery) { 306 StopWatch sw = new StopWatch(); 307 int outcome = myEntityManager.createQuery(theQuery).executeUpdate(); 308 ourLog.debug("SqlQuery affected {} rows in {}: {}", outcome, sw, theQuery); 309 return outcome; 310 } 311}