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.util; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.model.sched.HapiJob; 024import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs; 025import ca.uhn.fhir.jpa.model.sched.ISchedulerService; 026import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; 027import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 028import com.google.common.annotations.VisibleForTesting; 029import org.apache.commons.lang3.time.DateUtils; 030import org.quartz.JobExecutionContext; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033import org.springframework.beans.factory.annotation.Autowired; 034 035import java.util.Map; 036import java.util.concurrent.Callable; 037import java.util.concurrent.atomic.AtomicReference; 038 039public class ResourceCountCache implements IHasScheduledJobs { 040 041 private static final Logger ourLog = LoggerFactory.getLogger(ResourceCountCache.class); 042 private static Long ourNowForUnitTest; 043 private final Callable<Map<String, Long>> myFetcher; 044 private volatile long myCacheMillis; 045 private AtomicReference<Map<String, Long>> myCapabilityStatement = new AtomicReference<>(); 046 private long myLastFetched; 047 048 /** 049 * Constructor 050 */ 051 public ResourceCountCache(Callable<Map<String, Long>> theFetcher) { 052 myFetcher = theFetcher; 053 } 054 055 public synchronized void clear() { 056 ourLog.info("Clearing cache"); 057 myCapabilityStatement.set(null); 058 myLastFetched = 0; 059 } 060 061 public synchronized Map<String, Long> get() { 062 return myCapabilityStatement.get(); 063 } 064 065 private Map<String, Long> refresh() { 066 Map<String, Long> retVal; 067 try { 068 retVal = myFetcher.call(); 069 } catch (Exception e) { 070 throw new InternalErrorException(Msg.code(799) + e); 071 } 072 073 myCapabilityStatement.set(retVal); 074 myLastFetched = now(); 075 return retVal; 076 } 077 078 public void setCacheMillis(long theCacheMillis) { 079 myCacheMillis = theCacheMillis; 080 } 081 082 public void update() { 083 if (myCacheMillis > 0) { 084 long now = now(); 085 long expiry = now - myCacheMillis; 086 if (myLastFetched < expiry) { 087 refresh(); 088 } 089 } 090 } 091 092 @Override 093 public void scheduleJobs(ISchedulerService theSchedulerService) { 094 ScheduledJobDefinition jobDetail = new ScheduledJobDefinition(); 095 jobDetail.setId(getClass().getName()); 096 jobDetail.setJobClass(Job.class); 097 theSchedulerService.scheduleLocalJob(10 * DateUtils.MILLIS_PER_MINUTE, jobDetail); 098 } 099 100 public static class Job implements HapiJob { 101 @Autowired 102 private ResourceCountCache myTarget; 103 104 @Override 105 public void execute(JobExecutionContext theContext) { 106 myTarget.update(); 107 } 108 } 109 110 private static long now() { 111 if (ourNowForUnitTest != null) { 112 return ourNowForUnitTest; 113 } 114 return System.currentTimeMillis(); 115 } 116 117 @VisibleForTesting 118 static void setNowForUnitTest(Long theNowForUnitTest) { 119 ourNowForUnitTest = theNowForUnitTest; 120 } 121}