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.search.warm;
021
022import ca.uhn.fhir.context.ConfigurationException;
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.context.RuntimeResourceDefinition;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
027import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
028import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
029import ca.uhn.fhir.jpa.api.model.WarmCacheEntry;
030import ca.uhn.fhir.jpa.model.sched.HapiJob;
031import ca.uhn.fhir.jpa.model.sched.IHasScheduledJobs;
032import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
033import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition;
034import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
035import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
036import ca.uhn.fhir.util.UrlUtil;
037import jakarta.annotation.PostConstruct;
038import org.apache.commons.lang3.time.DateUtils;
039import org.quartz.JobExecutionContext;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.springframework.beans.factory.annotation.Autowired;
043import org.springframework.stereotype.Component;
044
045import java.util.ArrayList;
046import java.util.Collections;
047import java.util.LinkedHashMap;
048import java.util.List;
049import java.util.Map;
050import java.util.Set;
051
052@Component
053public class CacheWarmingSvcImpl implements ICacheWarmingSvc, IHasScheduledJobs {
054
055        private static final Logger ourLog = LoggerFactory.getLogger(CacheWarmingSvcImpl.class);
056
057        @Autowired
058        private JpaStorageSettings myStorageSettings;
059
060        private Map<WarmCacheEntry, Long> myCacheEntryToNextRefresh = new LinkedHashMap<>();
061
062        @Autowired
063        private FhirContext myCtx;
064
065        @Autowired
066        private DaoRegistry myDaoRegistry;
067
068        @Autowired
069        private MatchUrlService myMatchUrlService;
070
071        @Override
072        public synchronized void performWarmingPass() {
073                ourLog.trace("Starting cache warming pass for {} tasks", myCacheEntryToNextRefresh.size());
074
075                for (WarmCacheEntry nextCacheEntry : new ArrayList<>(myCacheEntryToNextRefresh.keySet())) {
076
077                        long nextRefresh = myCacheEntryToNextRefresh.get(nextCacheEntry);
078                        if (nextRefresh < System.currentTimeMillis()) {
079
080                                // Perform the search
081                                refreshNow(nextCacheEntry);
082
083                                // Set the next time to warm this search
084                                nextRefresh = nextCacheEntry.getPeriodMillis() + System.currentTimeMillis();
085                                myCacheEntryToNextRefresh.put(nextCacheEntry, nextRefresh);
086                        }
087                }
088        }
089
090        private void refreshNow(WarmCacheEntry theCacheEntry) {
091                String nextUrl = theCacheEntry.getUrl();
092
093                RuntimeResourceDefinition resourceDef = UrlUtil.parseUrlResourceType(myCtx, nextUrl);
094                IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
095                String queryPart = parseWarmUrlParamPart(nextUrl);
096                SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
097
098                callingDao.search(responseCriteriaUrl);
099        }
100
101        private String parseWarmUrlParamPart(String theNextUrl) {
102                int paramIndex = theNextUrl.indexOf('?');
103                if (paramIndex == -1) {
104                        throw new ConfigurationException(Msg.code(1172) + "Invalid warm cache URL (must have ? character)");
105                }
106                return theNextUrl.substring(paramIndex);
107        }
108
109        @PostConstruct
110        public void start() {
111                initCacheMap();
112        }
113
114        @Override
115        public void scheduleJobs(ISchedulerService theSchedulerService) {
116                ScheduledJobDefinition jobDetail = new ScheduledJobDefinition();
117                jobDetail.setId(getClass().getName());
118                jobDetail.setJobClass(Job.class);
119                theSchedulerService.scheduleClusteredJob(10 * DateUtils.MILLIS_PER_SECOND, jobDetail);
120        }
121
122        public static class Job implements HapiJob {
123                @Autowired
124                private ICacheWarmingSvc myTarget;
125
126                @Override
127                public void execute(JobExecutionContext theContext) {
128                        myTarget.performWarmingPass();
129                }
130        }
131
132        public synchronized Set<WarmCacheEntry> initCacheMap() {
133
134                myCacheEntryToNextRefresh.clear();
135                List<WarmCacheEntry> warmCacheEntries = myStorageSettings.getWarmCacheEntries();
136                for (WarmCacheEntry next : warmCacheEntries) {
137
138                        // Validate
139                        parseWarmUrlParamPart(next.getUrl());
140                        UrlUtil.parseUrlResourceType(myCtx, next.getUrl());
141
142                        myCacheEntryToNextRefresh.put(next, 0L);
143                }
144
145                return Collections.unmodifiableSet(myCacheEntryToNextRefresh.keySet());
146        }
147}