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