package ca.uhn.fhir.jpa.search; /* * #%L * HAPI FHIR JPA Server * %% * Copyright (C) 2014 - 2017 University Health Network * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.util.Collection; import java.util.Date; import org.apache.commons.lang3.time.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.google.common.annotations.VisibleForTesting; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao; import ca.uhn.fhir.jpa.dao.data.ISearchResultDao; import ca.uhn.fhir.jpa.entity.Search; /** * Deletes old searches */ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc { public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class); /* * We give a bit of extra leeway just to avoid race conditions where a query result * is being reused (because a new client request came in with the same params) right before * the result is to be deleted */ private long myCutoffSlack = DEFAULT_CUTOFF_SLACK; @Autowired private DaoConfig myDaoConfig; @Autowired private ISearchDao mySearchDao; @Autowired private ISearchIncludeDao mySearchIncludeDao; @Autowired private ISearchResultDao mySearchResultDao; @Autowired private PlatformTransactionManager myTransactionManager; protected void deleteSearch(final Search next) { TransactionTemplate tt = new TransactionTemplate(myTransactionManager); tt.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus theArg0) { Search searchToDelete = mySearchDao.findOne(next.getId()); ourLog.info("Expiring stale search {} / {}", searchToDelete.getId(), searchToDelete.getUuid()); mySearchIncludeDao.deleteForSearch(searchToDelete.getId()); mySearchResultDao.deleteForSearch(searchToDelete.getId()); mySearchDao.delete(searchToDelete); } }); } @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) public void pollForStaleSearchesAndDeleteThem() { long cutoffMillis = myDaoConfig.getExpireSearchResultsAfterMillis(); if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) { cutoffMillis = Math.max(cutoffMillis, myDaoConfig.getReuseCachedSearchResultsForMillis()); } Date cutoff = new Date((System.currentTimeMillis() - cutoffMillis) - myCutoffSlack); ourLog.debug("Searching for searches which are before {}", cutoff); Collection<Search> toDelete = mySearchDao.findWhereLastReturnedBefore(cutoff); if (!toDelete.isEmpty()) { for (final Search next : toDelete) { ourLog.info("Deleting search {} - Created[{}] -- Last returned[{}]", next.getUuid(), next.getCreated(), next.getSearchLastReturned()); deleteSearch(next); } ourLog.info("Deleted {} searches, {} remaining", toDelete.size(), mySearchDao.count()); } } @Scheduled(fixedDelay = DEFAULT_CUTOFF_SLACK) @Transactional(propagation = Propagation.NOT_SUPPORTED) @Override public synchronized void schedulePollForStaleSearches() { if (!myDaoConfig.isSchedulingDisabled()) { if (myDaoConfig.isExpireSearchResults()) { pollForStaleSearchesAndDeleteThem(); } } } @VisibleForTesting public void setCutoffSlackForUnitTest(long theCutoffSlack) { myCutoffSlack = theCutoffSlack; } }