001/*- 002 * #%L 003 * HAPI FHIR JPA Server 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.delete.batch2; 021 022import ca.uhn.fhir.jpa.api.svc.IDeleteExpungeSvc; 023import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; 024import ca.uhn.fhir.jpa.model.dao.JpaPid; 025import ca.uhn.fhir.jpa.model.entity.ResourceTable; 026import jakarta.persistence.EntityManager; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.springframework.beans.factory.annotation.Autowired; 030 031import java.util.List; 032import java.util.stream.Collectors; 033 034public class DeleteExpungeSvcImpl implements IDeleteExpungeSvc<JpaPid> { 035 private static final Logger ourLog = LoggerFactory.getLogger(DeleteExpungeSvcImpl.class); 036 037 private final EntityManager myEntityManager; 038 private final DeleteExpungeSqlBuilder myDeleteExpungeSqlBuilder; 039 private final IFulltextSearchSvc myFullTextSearchSvc; 040 041 public DeleteExpungeSvcImpl( 042 EntityManager theEntityManager, 043 DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, 044 @Autowired(required = false) IFulltextSearchSvc theFullTextSearchSvc) { 045 myEntityManager = theEntityManager; 046 myDeleteExpungeSqlBuilder = theDeleteExpungeSqlBuilder; 047 myFullTextSearchSvc = theFullTextSearchSvc; 048 } 049 050 @Override 051 public int deleteExpunge(List<JpaPid> theJpaPids, boolean theCascade, Integer theCascadeMaxRounds) { 052 DeleteExpungeSqlBuilder.DeleteExpungeSqlResult sqlResult = 053 myDeleteExpungeSqlBuilder.convertPidsToDeleteExpungeSql(theJpaPids, theCascade, theCascadeMaxRounds); 054 List<String> sqlList = sqlResult.getSqlStatements(); 055 056 ourLog.debug("Executing {} delete expunge sql commands", sqlList.size()); 057 long totalDeleted = 0; 058 for (String sql : sqlList) { 059 ourLog.trace("Executing sql " + sql); 060 totalDeleted += myEntityManager.createNativeQuery(sql).executeUpdate(); 061 } 062 063 ourLog.info("{} records deleted", totalDeleted); 064 clearHibernateSearchIndex(theJpaPids); 065 066 // TODO KHS instead of logging progress, produce result chunks that get aggregated into a delete expunge report 067 return sqlResult.getRecordCount(); 068 } 069 070 @Override 071 public boolean isCascadeSupported() { 072 return true; 073 } 074 075 /** 076 * If we are running with HS enabled, the expunge operation will cause dangling documents because Hibernate Search is not aware of custom SQL queries that delete resources. 077 * This method clears the Hibernate Search index for the given resources. 078 */ 079 private void clearHibernateSearchIndex(List<JpaPid> thePersistentIds) { 080 if (myFullTextSearchSvc != null && !myFullTextSearchSvc.isDisabled()) { 081 List<Object> objectIds = 082 thePersistentIds.stream().map(JpaPid::getId).collect(Collectors.toList()); 083 myFullTextSearchSvc.deleteIndexedDocumentsByTypeAndId(ResourceTable.class, objectIds); 084 ourLog.info("Cleared Hibernate Search indexes."); 085 } 086 } 087}