
001package ca.uhn.fhir.jpa.dao.expunge; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.interceptor.api.HookParams; 024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 025import ca.uhn.fhir.interceptor.api.Pointcut; 026import ca.uhn.fhir.jpa.entity.Batch2JobInstanceEntity; 027import ca.uhn.fhir.jpa.entity.Batch2WorkChunkEntity; 028import ca.uhn.fhir.jpa.entity.BulkImportJobEntity; 029import ca.uhn.fhir.jpa.entity.BulkImportJobFileEntity; 030import ca.uhn.fhir.jpa.entity.PartitionEntity; 031import ca.uhn.fhir.jpa.entity.Search; 032import ca.uhn.fhir.jpa.entity.SearchInclude; 033import ca.uhn.fhir.jpa.entity.SearchResult; 034import ca.uhn.fhir.jpa.entity.SubscriptionTable; 035import ca.uhn.fhir.jpa.entity.TermCodeSystem; 036import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; 037import ca.uhn.fhir.jpa.entity.TermConcept; 038import ca.uhn.fhir.jpa.entity.TermConceptDesignation; 039import ca.uhn.fhir.jpa.entity.TermConceptMap; 040import ca.uhn.fhir.jpa.entity.TermConceptMapGroup; 041import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElement; 042import ca.uhn.fhir.jpa.entity.TermConceptMapGroupElementTarget; 043import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink; 044import ca.uhn.fhir.jpa.entity.TermConceptProperty; 045import ca.uhn.fhir.jpa.entity.TermValueSet; 046import ca.uhn.fhir.jpa.entity.TermValueSetConcept; 047import ca.uhn.fhir.jpa.entity.TermValueSetConceptDesignation; 048import ca.uhn.fhir.jpa.model.entity.ForcedId; 049import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity; 050import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity; 051import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity; 052import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity; 053import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; 054import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTag; 055import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; 056import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; 057import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; 058import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; 059import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; 060import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; 061import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; 062import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 063import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; 064import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri; 065import ca.uhn.fhir.jpa.model.entity.ResourceLink; 066import ca.uhn.fhir.jpa.model.entity.ResourceTable; 067import ca.uhn.fhir.jpa.model.entity.ResourceTag; 068import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity; 069import ca.uhn.fhir.jpa.model.entity.TagDefinition; 070import ca.uhn.fhir.jpa.util.MemoryCacheService; 071import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 072import ca.uhn.fhir.rest.api.server.RequestDetails; 073import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 074import ca.uhn.fhir.util.StopWatch; 075import org.slf4j.Logger; 076import org.slf4j.LoggerFactory; 077import org.springframework.beans.factory.annotation.Autowired; 078import org.springframework.stereotype.Service; 079import org.springframework.transaction.PlatformTransactionManager; 080import org.springframework.transaction.TransactionDefinition; 081import org.springframework.transaction.support.TransactionTemplate; 082 083import javax.annotation.Nullable; 084import javax.annotation.PostConstruct; 085import javax.persistence.EntityManager; 086import javax.persistence.PersistenceContext; 087import javax.persistence.PersistenceContextType; 088import javax.persistence.TypedQuery; 089import javax.persistence.criteria.CriteriaBuilder; 090import javax.persistence.criteria.CriteriaQuery; 091import java.util.List; 092import java.util.concurrent.atomic.AtomicInteger; 093 094@Service 095public class ExpungeEverythingService implements IExpungeEverythingService { 096 private static final Logger ourLog = LoggerFactory.getLogger(ExpungeEverythingService.class); 097 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 098 protected EntityManager myEntityManager; 099 @Autowired 100 private PlatformTransactionManager myPlatformTransactionManager; 101 @Autowired 102 protected IInterceptorBroadcaster myInterceptorBroadcaster; 103 104 private TransactionTemplate myTxTemplate; 105 106 @Autowired 107 private MemoryCacheService myMemoryCacheService; 108 109 private int deletedResourceEntityCount; 110 111 @PostConstruct 112 public void initTxTemplate() { 113 myTxTemplate = new TransactionTemplate(myPlatformTransactionManager); 114 } 115 116 @Override 117 public void expungeEverything(@Nullable RequestDetails theRequest) { 118 119 final AtomicInteger counter = new AtomicInteger(); 120 121 // Notify Interceptors about pre-action call 122 HookParams hooks = new HookParams() 123 .add(AtomicInteger.class, counter) 124 .add(RequestDetails.class, theRequest) 125 .addIfMatchesType(ServletRequestDetails.class, theRequest); 126 CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_EXPUNGE_EVERYTHING, hooks); 127 128 ourLog.info("BEGINNING GLOBAL $expunge"); 129 myTxTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 130 myTxTemplate.execute(t -> { 131 counter.addAndGet(doExpungeEverythingQuery("UPDATE " + TermCodeSystem.class.getSimpleName() + " d SET d.myCurrentVersion = null")); 132 return null; 133 }); 134 counter.addAndGet(expungeEverythingByTypeWithoutPurging(Batch2WorkChunkEntity.class)); 135 counter.addAndGet(expungeEverythingByTypeWithoutPurging(Batch2JobInstanceEntity.class)); 136 counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageVersionResourceEntity.class)); 137 counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageVersionEntity.class)); 138 counter.addAndGet(expungeEverythingByTypeWithoutPurging(NpmPackageEntity.class)); 139 counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchParamPresentEntity.class)); 140 counter.addAndGet(expungeEverythingByTypeWithoutPurging(BulkImportJobFileEntity.class)); 141 counter.addAndGet(expungeEverythingByTypeWithoutPurging(BulkImportJobEntity.class)); 142 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ForcedId.class)); 143 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamDate.class)); 144 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamNumber.class)); 145 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamQuantity.class)); 146 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamQuantityNormalized.class)); 147 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamString.class)); 148 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamToken.class)); 149 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamUri.class)); 150 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedSearchParamCoords.class)); 151 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedComboStringUnique.class)); 152 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceIndexedComboTokenNonUnique.class)); 153 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceLink.class)); 154 counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchResult.class)); 155 counter.addAndGet(expungeEverythingByTypeWithoutPurging(SearchInclude.class)); 156 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSetConceptDesignation.class)); 157 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSetConcept.class)); 158 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermValueSet.class)); 159 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptParentChildLink.class)); 160 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroupElementTarget.class)); 161 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroupElement.class)); 162 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMapGroup.class)); 163 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptMap.class)); 164 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptProperty.class)); 165 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConceptDesignation.class)); 166 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermConcept.class)); 167 myTxTemplate.execute(t -> { 168 for (TermCodeSystem next : myEntityManager.createQuery("SELECT c FROM " + TermCodeSystem.class.getName() + " c", TermCodeSystem.class).getResultList()) { 169 next.setCurrentVersion(null); 170 myEntityManager.merge(next); 171 } 172 return null; 173 }); 174 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermCodeSystemVersion.class)); 175 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TermCodeSystem.class)); 176 counter.addAndGet(expungeEverythingByTypeWithoutPurging(SubscriptionTable.class)); 177 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryTag.class)); 178 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceTag.class)); 179 counter.addAndGet(expungeEverythingByTypeWithoutPurging(TagDefinition.class)); 180 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryProvenanceEntity.class)); 181 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceHistoryTable.class)); 182 int counterBefore = counter.get(); 183 counter.addAndGet(expungeEverythingByTypeWithoutPurging(ResourceTable.class)); 184 counter.addAndGet(expungeEverythingByTypeWithoutPurging(PartitionEntity.class)); 185 186 deletedResourceEntityCount = counter.get() - counterBefore; 187 188 myTxTemplate.execute(t -> { 189 counter.addAndGet(doExpungeEverythingQuery("DELETE from " + Search.class.getSimpleName() + " d")); 190 return null; 191 }); 192 193 purgeAllCaches(); 194 195 ourLog.info("COMPLETED GLOBAL $expunge - Deleted {} rows", counter.get()); 196 } 197 198 @Override 199 public int getExpungeDeletedEntityCount() { 200 return deletedResourceEntityCount; 201 } 202 203 private void purgeAllCaches() { 204 myTxTemplate.execute(t -> { 205 myMemoryCacheService.invalidateAllCaches(); 206 return null; 207 }); 208 } 209 210 private int expungeEverythingByTypeWithoutPurging(Class<?> theEntityType) { 211 int outcome = 0; 212 while (true) { 213 StopWatch sw = new StopWatch(); 214 215 @SuppressWarnings("ConstantConditions") 216 int count = myTxTemplate.execute(t -> { 217 CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); 218 CriteriaQuery<?> cq = cb.createQuery(theEntityType); 219 cq.from(theEntityType); 220 TypedQuery<?> query = myEntityManager.createQuery(cq); 221 query.setMaxResults(1000); 222 List<?> results = query.getResultList(); 223 for (Object result : results) { 224 myEntityManager.remove(result); 225 } 226 return results.size(); 227 }); 228 229 outcome += count; 230 if (count == 0) { 231 break; 232 } 233 234 ourLog.info("Have deleted {} entities of type {} in {}", outcome, theEntityType.getSimpleName(), sw.toString()); 235 } 236 return outcome; 237 } 238 239 public int expungeEverythingByType(Class<?> theEntityType) { 240 int result = expungeEverythingByTypeWithoutPurging(theEntityType); 241 purgeAllCaches(); 242 return result; 243 } 244 245 private int doExpungeEverythingQuery(String theQuery) { 246 StopWatch sw = new StopWatch(); 247 int outcome = myEntityManager.createQuery(theQuery).executeUpdate(); 248 ourLog.debug("SqlQuery affected {} rows in {}: {}", outcome, sw.toString(), theQuery); 249 return outcome; 250 } 251}