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}