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