View Javadoc
1   package ca.uhn.fhir.jpa.dao;
2   
3   /*
4    * #%L
5    * HAPI FHIR JPA Server
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   * http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import ca.uhn.fhir.context.FhirContext;
24  import ca.uhn.fhir.context.RuntimeResourceDefinition;
25  import ca.uhn.fhir.context.RuntimeSearchParam;
26  import ca.uhn.fhir.jpa.search.JpaRuntimeSearchParam;
27  import ca.uhn.fhir.util.StopWatch;
28  import ca.uhn.fhir.rest.api.server.IBundleProvider;
29  import ca.uhn.fhir.util.SearchParameterUtil;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.lang3.time.DateUtils;
32  import org.hl7.fhir.instance.model.api.IBaseResource;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  import org.springframework.beans.BeansException;
36  import org.springframework.beans.factory.annotation.Autowired;
37  import org.springframework.context.ApplicationContext;
38  import org.springframework.context.ApplicationContextAware;
39  import org.springframework.scheduling.annotation.Scheduled;
40  
41  import javax.annotation.PostConstruct;
42  import java.util.*;
43  
44  import static org.apache.commons.lang3.StringUtils.isBlank;
45  
46  public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implements ISearchParamRegistry, ApplicationContextAware {
47  
48  	private static final int MAX_MANAGED_PARAM_COUNT = 10000;
49  	private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
50  	private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams;
51  	private volatile Map<String, List<JpaRuntimeSearchParam>> myActiveUniqueSearchParams = Collections.emptyMap();
52  	private volatile Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap();
53  	@Autowired
54  	private FhirContext myCtx;
55  	private Collection<IFhirResourceDao<?>> myResourceDaos;
56  	private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
57  	@Autowired
58  	private DaoConfig myDaoConfig;
59  	private volatile long myLastRefresh;
60  	private ApplicationContext myApplicationContext;
61  
62  	public BaseSearchParamRegistry() {
63  		super();
64  	}
65  
66  	@Override
67  	public void requestRefresh() {
68  		synchronized (this) {
69  			myLastRefresh = 0;
70  		}
71  	}
72  
73  	@Override
74  	public void forceRefresh() {
75  		requestRefresh();
76  		refreshCacheIfNecessary();
77  	}
78  
79  	@Override
80  	public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
81  		RuntimeSearchParam retVal = null;
82  		Map<String, RuntimeSearchParam> params = getActiveSearchParams().get(theResourceName);
83  		if (params != null) {
84  			retVal = params.get(theParamName);
85  		}
86  		return retVal;
87  	}
88  
89  
90  	@Override
91  	public Map<String, Map<String, RuntimeSearchParam>> getActiveSearchParams() {
92  		return myActiveSearchParams;
93  	}
94  
95  	@Override
96  	public Map<String, RuntimeSearchParam> getActiveSearchParams(String theResourceName) {
97  		return myActiveSearchParams.get(theResourceName);
98  	}
99  
100 	@Override
101 	public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName) {
102 		List<JpaRuntimeSearchParam> retVal = myActiveUniqueSearchParams.get(theResourceName);
103 		if (retVal == null) {
104 			retVal = Collections.emptyList();
105 		}
106 		return retVal;
107 	}
108 
109 	@Override
110 	public List<JpaRuntimeSearchParam> getActiveUniqueSearchParams(String theResourceName, Set<String> theParamNames) {
111 
112 		Map<Set<String>, List<JpaRuntimeSearchParam>> paramNamesToParams = myActiveParamNamesToUniqueSearchParams.get(theResourceName);
113 		if (paramNamesToParams == null) {
114 			return Collections.emptyList();
115 		}
116 
117 		List<JpaRuntimeSearchParam> retVal = paramNamesToParams.get(theParamNames);
118 		if (retVal == null) {
119 			retVal = Collections.emptyList();
120 		}
121 		return Collections.unmodifiableList(retVal);
122 	}
123 
124 	public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
125 		return myBuiltInSearchParams;
126 	}
127 
128 	private Map<String, RuntimeSearchParam> getSearchParamMap(Map<String, Map<String, RuntimeSearchParam>> searchParams, String theResourceName) {
129 		Map<String, RuntimeSearchParam> retVal = searchParams.get(theResourceName);
130 		if (retVal == null) {
131 			retVal = new HashMap<>();
132 			searchParams.put(theResourceName, retVal);
133 		}
134 		return retVal;
135 	}
136 
137 	public abstract IFhirResourceDao<SP> getSearchParameterDao();
138 
139 	private void populateActiveSearchParams(Map<String, Map<String, RuntimeSearchParam>> theActiveSearchParams) {
140 		Map<String, List<JpaRuntimeSearchParam>> activeUniqueSearchParams = new HashMap<>();
141 		Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> activeParamNamesToUniqueSearchParams = new HashMap<>();
142 
143 		Map<String, RuntimeSearchParam> idToRuntimeSearchParam = new HashMap<>();
144 		List<JpaRuntimeSearchParam> jpaSearchParams = new ArrayList<>();
145 
146 		/*
147 		 * Loop through parameters and find JPA params
148 		 */
149 		for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextResourceNameToEntries : theActiveSearchParams.entrySet()) {
150 			List<JpaRuntimeSearchParam> uniqueSearchParams = activeUniqueSearchParams.get(nextResourceNameToEntries.getKey());
151 			if (uniqueSearchParams == null) {
152 				uniqueSearchParams = new ArrayList<>();
153 				activeUniqueSearchParams.put(nextResourceNameToEntries.getKey(), uniqueSearchParams);
154 			}
155 			Collection<RuntimeSearchParam> nextSearchParamsForResourceName = nextResourceNameToEntries.getValue().values();
156 			for (RuntimeSearchParam nextCandidate : nextSearchParamsForResourceName) {
157 
158 				if (nextCandidate.getId() != null) {
159 					idToRuntimeSearchParam.put(nextCandidate.getId().toUnqualifiedVersionless().getValue(), nextCandidate);
160 				}
161 
162 				if (nextCandidate instanceof JpaRuntimeSearchParam) {
163 					JpaRuntimeSearchParam nextCandidateCasted = (JpaRuntimeSearchParam) nextCandidate;
164 					jpaSearchParams.add(nextCandidateCasted);
165 					if (nextCandidateCasted.isUnique()) {
166 						uniqueSearchParams.add(nextCandidateCasted);
167 					}
168 				}
169 			}
170 
171 		}
172 
173 		Set<String> haveSeen = new HashSet<>();
174 		for (JpaRuntimeSearchParam next : jpaSearchParams) {
175 			if (!haveSeen.add(next.getId().toUnqualifiedVersionless().getValue())) {
176 				continue;
177 			}
178 
179 			Set<String> paramNames = new HashSet<>();
180 			for (JpaRuntimeSearchParam.Component nextComponent : next.getComponents()) {
181 				String nextRef = nextComponent.getReference().getReferenceElement().toUnqualifiedVersionless().getValue();
182 				RuntimeSearchParam componentTarget = idToRuntimeSearchParam.get(nextRef);
183 				if (componentTarget != null) {
184 					next.getCompositeOf().add(componentTarget);
185 					paramNames.add(componentTarget.getName());
186 				} else {
187 					ourLog.warn("Search parameter {} refers to unknown component {}", next.getId().toUnqualifiedVersionless().getValue(), nextRef);
188 				}
189 			}
190 
191 			if (next.getCompositeOf() != null) {
192 				Collections.sort(next.getCompositeOf(), new Comparator<RuntimeSearchParam>() {
193 					@Override
194 					public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) {
195 						return StringUtils.compare(theO1.getName(), theO2.getName());
196 					}
197 				});
198 				for (String nextBase : next.getBase()) {
199 					if (!activeParamNamesToUniqueSearchParams.containsKey(nextBase)) {
200 						activeParamNamesToUniqueSearchParams.put(nextBase, new HashMap<Set<String>, List<JpaRuntimeSearchParam>>());
201 					}
202 					if (!activeParamNamesToUniqueSearchParams.get(nextBase).containsKey(paramNames)) {
203 						activeParamNamesToUniqueSearchParams.get(nextBase).put(paramNames, new ArrayList<JpaRuntimeSearchParam>());
204 					}
205 					activeParamNamesToUniqueSearchParams.get(nextBase).get(paramNames).add(next);
206 				}
207 			}
208 		}
209 
210 		myActiveUniqueSearchParams = activeUniqueSearchParams;
211 		myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams;
212 	}
213 
214 	@PostConstruct
215 	public void postConstruct() {
216 		Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
217 
218 		myResourceDaos = new ArrayList<>();
219 		Map<String, IFhirResourceDao> daos = myApplicationContext.getBeansOfType(IFhirResourceDao.class, false, false);
220 		for (IFhirResourceDao next : daos.values()) {
221 			myResourceDaos.add(next);
222 		}
223 
224 		for (IFhirResourceDao<?> nextDao : myResourceDaos) {
225 			RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextDao.getResourceType());
226 			String nextResourceName = nextResDef.getName();
227 			HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
228 			resourceNameToSearchParams.put(nextResourceName, nameToParam);
229 
230 			for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
231 				nameToParam.put(nextSp.getName(), nextSp);
232 			}
233 		}
234 
235 		myBuiltInSearchParams = Collections.unmodifiableMap(resourceNameToSearchParams);
236 
237 		refreshCacheIfNecessary();
238 	}
239 
240 	@Override
241 	public void refreshCacheIfNecessary() {
242 		long refreshInterval = 60 * DateUtils.MILLIS_PER_MINUTE;
243 		if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
244 			synchronized (this) {
245 				if (System.currentTimeMillis() - refreshInterval > myLastRefresh) {
246 					StopWatch sw = new StopWatch();
247 
248 					Map<String, Map<String, RuntimeSearchParam>> searchParams = new HashMap<>();
249 					for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextBuiltInEntry : getBuiltInSearchParams().entrySet()) {
250 						for (RuntimeSearchParam nextParam : nextBuiltInEntry.getValue().values()) {
251 							String nextResourceName = nextBuiltInEntry.getKey();
252 							getSearchParamMap(searchParams, nextResourceName).put(nextParam.getName(), nextParam);
253 						}
254 					}
255 
256 					SearchParameterMap params = new SearchParameterMap();
257 					params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT);
258 
259 					IBundleProvider allSearchParamsBp = getSearchParameterDao().search(params);
260 					int size = allSearchParamsBp.size();
261 
262 					// Just in case..
263 					if (size >= MAX_MANAGED_PARAM_COUNT) {
264 						ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!");
265 						size = MAX_MANAGED_PARAM_COUNT;
266 					}
267 
268 					List<IBaseResource> allSearchParams = allSearchParamsBp.getResources(0, size);
269 					for (IBaseResource nextResource : allSearchParams) {
270 						SP nextSp = (SP) nextResource;
271 						if (nextSp == null) {
272 							continue;
273 						}
274 
275 						RuntimeSearchParam runtimeSp = toRuntimeSp(nextSp);
276 						if (runtimeSp == null) {
277 							continue;
278 						}
279 
280 						for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) {
281 							if (isBlank(nextBaseName)) {
282 								continue;
283 							}
284 
285 							Map<String, RuntimeSearchParam> searchParamMap = getSearchParamMap(searchParams, nextBaseName);
286 							String name = runtimeSp.getName();
287 							if (myDaoConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) {
288 								searchParamMap.put(name, runtimeSp);
289 							}
290 
291 						}
292 					}
293 
294 					Map<String, Map<String, RuntimeSearchParam>> activeSearchParams = new HashMap<>();
295 					for (Map.Entry<String, Map<String, RuntimeSearchParam>> nextEntry : searchParams.entrySet()) {
296 						for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) {
297 							String nextName = nextSp.getName();
298 							if (nextSp.getStatus() != RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE) {
299 								nextSp = null;
300 							}
301 
302 							if (!activeSearchParams.containsKey(nextEntry.getKey())) {
303 								activeSearchParams.put(nextEntry.getKey(), new HashMap<>());
304 							}
305 							if (activeSearchParams.containsKey(nextEntry.getKey())) {
306 								ourLog.debug("Replacing existing/built in search param {}:{} with new one", nextEntry.getKey(), nextName);
307 							}
308 
309 							if (nextSp != null) {
310 								activeSearchParams.get(nextEntry.getKey()).put(nextName, nextSp);
311 							} else {
312 								activeSearchParams.get(nextEntry.getKey()).remove(nextName);
313 							}
314 						}
315 					}
316 
317 					myActiveSearchParams = activeSearchParams;
318 
319 					populateActiveSearchParams(activeSearchParams);
320 
321 					myLastRefresh = System.currentTimeMillis();
322 					ourLog.info("Refreshed search parameter cache in {}ms", sw.getMillis());
323 				}
324 			}
325 		}
326 	}
327 
328 	@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
329 	public void refreshCacheOnSchedule() {
330 		refreshCacheIfNecessary();
331 	}
332 
333 	@Override
334 	public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
335 		myApplicationContext = theApplicationContext;
336 	}
337 
338 	protected abstract RuntimeSearchParam toRuntimeSp(SP theNextSp);
339 
340 
341 }