View Javadoc
1   package ca.uhn.fhir.jpa.dao;
2   
3   import ca.uhn.fhir.context.FhirContext;
4   import ca.uhn.fhir.model.api.IQueryParameterAnd;
5   import ca.uhn.fhir.model.api.IQueryParameterOr;
6   import ca.uhn.fhir.model.api.IQueryParameterType;
7   import ca.uhn.fhir.model.api.Include;
8   import ca.uhn.fhir.rest.api.Constants;
9   import ca.uhn.fhir.rest.api.SortOrderEnum;
10  import ca.uhn.fhir.rest.api.SortSpec;
11  import ca.uhn.fhir.rest.param.DateParam;
12  import ca.uhn.fhir.rest.param.DateRangeParam;
13  import ca.uhn.fhir.util.ObjectUtil;
14  import ca.uhn.fhir.util.UrlUtil;
15  import org.apache.commons.lang3.StringUtils;
16  import org.apache.commons.lang3.Validate;
17  import org.apache.commons.lang3.builder.ToStringBuilder;
18  import org.apache.commons.lang3.builder.ToStringStyle;
19  
20  import java.util.*;
21  
22  import static org.apache.commons.lang3.StringUtils.isNotBlank;
23  
24  /*
25   * #%L
26   * HAPI FHIR JPA Server
27   * %%
28   * Copyright (C) 2014 - 2018 University Health Network
29   * %%
30   * Licensed under the Apache License, Version 2.0 (the "License");
31   * you may not use this file except in compliance with the License.
32   * You may obtain a copy of the License at
33   * 
34   * http://www.apache.org/licenses/LICENSE-2.0
35   * 
36   * Unless required by applicable law or agreed to in writing, software
37   * distributed under the License is distributed on an "AS IS" BASIS,
38   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
39   * See the License for the specific language governing permissions and
40   * limitations under the License.
41   * #L%
42   */
43  
44  public class SearchParameterMap extends LinkedHashMap<String, List<List<? extends IQueryParameterType>>> {
45  
46  	private static final long serialVersionUID = 1L;
47  
48  	private Integer myCount;
49  	private EverythingModeEnum myEverythingMode = null;
50  	private Set<Include> myIncludes;
51  	private DateRangeParam myLastUpdated;
52  	private boolean myLoadSynchronous;
53  	private Integer myLoadSynchronousUpTo;
54  	private Set<Include> myRevIncludes;
55  	private SortSpec mySort;
56  
57  	/**
58  	 * Constructor
59  	 */
60  	public SearchParameterMap() {
61  		super();
62  	}
63  
64  	/**
65  	 * Constructor
66  	 */
67  	public SearchParameterMap(String theName, IQueryParameterType theParam) {
68  		add(theName, theParam);
69  	}
70  
71  	public SearchParameterMap add(String theName, DateParam theDateParam) {
72  		add(theName, (IQueryParameterOr<?>) theDateParam);
73  		return this;
74  	}
75  
76  	public void add(String theName, IQueryParameterAnd<?> theAnd) {
77  		if (theAnd == null) {
78  			return;
79  		}
80  		if (!containsKey(theName)) {
81  			put(theName, new ArrayList<List<? extends IQueryParameterType>>());
82  		}
83  
84  		for (IQueryParameterOr<?> next : theAnd.getValuesAsQueryTokens()) {
85  			if (next == null) {
86  				continue;
87  			}
88  			get(theName).add(next.getValuesAsQueryTokens());
89  		}
90  	}
91  
92  	public void add(String theName, IQueryParameterOr<?> theOr) {
93  		if (theOr == null) {
94  			return;
95  		}
96  		if (!containsKey(theName)) {
97  			put(theName, new ArrayList<List<? extends IQueryParameterType>>());
98  		}
99  
100 		get(theName).add(theOr.getValuesAsQueryTokens());
101 	}
102 
103 	public SearchParameterMap add(String theName, IQueryParameterType theParam) {
104 		assert !Constants.PARAM_LASTUPDATED.equals(theName); // this has it's own field in the map
105 
106 		if (theParam == null) {
107 			return this;
108 		}
109 		if (!containsKey(theName)) {
110 			put(theName, new ArrayList<List<? extends IQueryParameterType>>());
111 		}
112 		ArrayList<IQueryParameterType> list = new ArrayList<IQueryParameterType>();
113 		list.add(theParam);
114 		get(theName).add(list);
115 
116 		return this;
117 	}
118 
119 	public void addInclude(Include theInclude) {
120 		getIncludes().add(theInclude);
121 	}
122 
123 	private void addLastUpdateParam(StringBuilder b, DateParam date) {
124 		if (date != null && isNotBlank(date.getValueAsString())) {
125 			addUrlParamSeparator(b);
126 			b.append(Constants.PARAM_LASTUPDATED);
127 			b.append('=');
128 			b.append(date.getValueAsString());
129 		}
130 	}
131 
132 	public void addRevInclude(Include theInclude) {
133 		getRevIncludes().add(theInclude);
134 	}
135 
136 	private void addUrlIncludeParams(StringBuilder b, String paramName, Set<Include> theList) {
137 		ArrayList<Include> list = new ArrayList<Include>(theList);
138 
139 		Collections.sort(list, new IncludeComparator());
140 		for (Include nextInclude : list) {
141 			addUrlParamSeparator(b);
142 			b.append(paramName);
143 			b.append('=');
144 			b.append(UrlUtil.escapeUrlParam(nextInclude.getParamType()));
145 			b.append(':');
146 			b.append(UrlUtil.escapeUrlParam(nextInclude.getParamName()));
147 			if (isNotBlank(nextInclude.getParamTargetType())) {
148 				b.append(':');
149 				b.append(nextInclude.getParamTargetType());
150 			}
151 		}
152 	}
153 
154 	private void addUrlParamSeparator(StringBuilder theB) {
155 		if (theB.length() == 0) {
156 			theB.append('?');
157 		} else {
158 			theB.append('&');
159 		}
160 	}
161 
162 	public Integer getCount() {
163 		return myCount;
164 	}
165 
166 	public void setCount(Integer theCount) {
167 		myCount = theCount;
168 	}
169 
170 	public EverythingModeEnum getEverythingMode() {
171 		return myEverythingMode;
172 	}
173 
174 	public void setEverythingMode(EverythingModeEnum theConsolidateMatches) {
175 		myEverythingMode = theConsolidateMatches;
176 	}
177 
178 	public Set<Include> getIncludes() {
179 		if (myIncludes == null) {
180 			myIncludes = new HashSet<Include>();
181 		}
182 		return myIncludes;
183 	}
184 
185 	public void setIncludes(Set<Include> theIncludes) {
186 		myIncludes = theIncludes;
187 	}
188 
189 	/**
190 	 * Returns null if there is no last updated value
191 	 */
192 	public DateRangeParam getLastUpdated() {
193 		if (myLastUpdated != null) {
194 			if (myLastUpdated.isEmpty()) {
195 				myLastUpdated = null;
196 			}
197 		}
198 		return myLastUpdated;
199 	}
200 
201 	public void setLastUpdated(DateRangeParam theLastUpdated) {
202 		myLastUpdated = theLastUpdated;
203 	}
204 
205 	/**
206 	 * Returns null if there is no last updated value, and removes the lastupdated
207 	 * value from this map
208 	 */
209 	public DateRangeParam getLastUpdatedAndRemove() {
210 		DateRangeParam retVal = getLastUpdated();
211 		myLastUpdated = null;
212 		return retVal;
213 	}
214 
215 	/**
216 	 * If set, tells the server to load these results synchronously, and not to load
217 	 * more than X results
218 	 */
219 	public Integer getLoadSynchronousUpTo() {
220 		return myLoadSynchronousUpTo;
221 	}
222 
223 	/**
224 	 * If set, tells the server to load these results synchronously, and not to load
225 	 * more than X results. Note that setting this to a value will also set
226 	 * {@link #setLoadSynchronous(boolean)} to true
227 	 */
228 	public SearchParameterMap setLoadSynchronousUpTo(Integer theLoadSynchronousUpTo) {
229 		myLoadSynchronousUpTo = theLoadSynchronousUpTo;
230 		if (myLoadSynchronousUpTo != null) {
231 			setLoadSynchronous(true);
232 		}
233 		return this;
234 	}
235 
236 	public Set<Include> getRevIncludes() {
237 		if (myRevIncludes == null) {
238 			myRevIncludes = new HashSet<>();
239 		}
240 		return myRevIncludes;
241 	}
242 
243 	public void setRevIncludes(Set<Include> theRevIncludes) {
244 		myRevIncludes = theRevIncludes;
245 	}
246 
247 	public SortSpec getSort() {
248 		return mySort;
249 	}
250 
251 	public void setSort(SortSpec theSort) {
252 		mySort = theSort;
253 	}
254 
255 	/**
256 	 * This will only return true if all parameters have no modifier of any kind
257 	 */
258 	public boolean isAllParametersHaveNoModifier() {
259 		for (List<List<? extends IQueryParameterType>> nextParamName : values()) {
260 			for (List<? extends IQueryParameterType> nextAnd : nextParamName) {
261 				for (IQueryParameterType nextOr : nextAnd) {
262 					if (isNotBlank(nextOr.getQueryParameterQualifier())) {
263 						return false;
264 					}
265 				}
266 			}
267 		}
268 		return true;
269 	}
270 
271 	/**
272 	 * If set, tells the server to load these results synchronously, and not to load
273 	 * more than X results
274 	 */
275 	public boolean isLoadSynchronous() {
276 		return myLoadSynchronous;
277 	}
278 
279 	/**
280 	 * If set, tells the server to load these results synchronously, and not to load
281 	 * more than X results
282 	 */
283 	public SearchParameterMap setLoadSynchronous(boolean theLoadSynchronous) {
284 		myLoadSynchronous = theLoadSynchronous;
285 		return this;
286 	}
287 
288 	public String toNormalizedQueryString(FhirContext theCtx) {
289 		StringBuilder b = new StringBuilder();
290 
291 		ArrayList<String> keys = new ArrayList<String>(keySet());
292 		Collections.sort(keys);
293 		for (String nextKey : keys) {
294 
295 			List<List<? extends IQueryParameterType>> nextValuesAndsIn = get(nextKey);
296 			List<List<IQueryParameterType>> nextValuesAndsOut = new ArrayList<List<IQueryParameterType>>();
297 
298 			for (List<? extends IQueryParameterType> nextValuesAndIn : nextValuesAndsIn) {
299 
300 				List<IQueryParameterType> nextValuesOrsOut = new ArrayList<IQueryParameterType>();
301 				for (IQueryParameterType nextValueOrIn : nextValuesAndIn) {
302 					if (nextValueOrIn.getMissing() != null || isNotBlank(nextValueOrIn.getValueAsQueryToken(theCtx))) {
303 						nextValuesOrsOut.add(nextValueOrIn);
304 					}
305 				}
306 
307 				Collections.sort(nextValuesOrsOut, new QueryParameterTypeComparator(theCtx));
308 
309 				if (nextValuesOrsOut.size() > 0) {
310 					nextValuesAndsOut.add(nextValuesOrsOut);
311 				}
312 
313 			} // for AND
314 
315 			Collections.sort(nextValuesAndsOut, new QueryParameterOrComparator(theCtx));
316 
317 			for (List<IQueryParameterType> nextValuesAnd : nextValuesAndsOut) {
318 				addUrlParamSeparator(b);
319 				IQueryParameterType firstValue = nextValuesAnd.get(0);
320 				b.append(UrlUtil.escapeUrlParam(nextKey));
321 
322 				if (firstValue.getMissing() != null) {
323 					b.append(Constants.PARAMQUALIFIER_MISSING);
324 					b.append('=');
325 					if (firstValue.getMissing()) {
326 						b.append(Constants.PARAMQUALIFIER_MISSING_TRUE);
327 					} else {
328 						b.append(Constants.PARAMQUALIFIER_MISSING_FALSE);
329 					}
330 					continue;
331 				}
332 
333 				if (isNotBlank(firstValue.getQueryParameterQualifier())) {
334 					b.append(firstValue.getQueryParameterQualifier());
335 				}
336 
337 				b.append('=');
338 
339 				for (int i = 0; i < nextValuesAnd.size(); i++) {
340 					IQueryParameterType nextValueOr = nextValuesAnd.get(i);
341 					if (i > 0) {
342 						b.append(',');
343 					}
344 					String valueAsQueryToken = nextValueOr.getValueAsQueryToken(theCtx);
345 //					b.append(ParameterUtil.escapeAndUrlEncode(valueAsQueryToken));
346 					b.append(UrlUtil.escapeUrlParam(valueAsQueryToken));
347 				}
348 			}
349 
350 		} // for keys
351 
352 		SortSpec sort = getSort();
353 		boolean first = true;
354 		while (sort != null) {
355 
356 			if (isNotBlank(sort.getParamName())) {
357 				if (first) {
358 					addUrlParamSeparator(b);
359 					b.append(Constants.PARAM_SORT);
360 					b.append('=');
361 					first = false;
362 				} else {
363 					b.append(',');
364 				}
365 				if (sort.getOrder() == SortOrderEnum.DESC) {
366 					b.append('-');
367 				}
368 				b.append(sort.getParamName());
369 			}
370 
371 			Validate.isTrue(sort != sort.getChain()); // just in case, shouldn't happen
372 			sort = sort.getChain();
373 		}
374 
375 		addUrlIncludeParams(b, Constants.PARAM_INCLUDE, getIncludes());
376 		addUrlIncludeParams(b, Constants.PARAM_REVINCLUDE, getRevIncludes());
377 
378 		if (getLastUpdated() != null) {
379 			DateParam lb = getLastUpdated().getLowerBound();
380 			addLastUpdateParam(b, lb);
381 			DateParam ub = getLastUpdated().getUpperBound();
382 			addLastUpdateParam(b, ub);
383 		}
384 
385 		if (getCount() != null) {
386 			addUrlParamSeparator(b);
387 			b.append(Constants.PARAM_COUNT);
388 			b.append('=');
389 			b.append(getCount());
390 		}
391 
392 		if (b.length() == 0) {
393 			b.append('?');
394 		}
395 
396 		return b.toString();
397 	}
398 
399 	@Override
400 	public String toString() {
401 		ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
402 		if (isEmpty() == false) {
403 			b.append("params", super.toString());
404 		}
405 		if (getIncludes().isEmpty() == false) {
406 			b.append("includes", getIncludes());
407 		}
408 		return b.toString();
409 	}
410 
411 	static int compare(FhirContext theCtx, IQueryParameterType theO1, IQueryParameterType theO2) {
412 		int retVal;
413 		if (theO1.getMissing() == null && theO2.getMissing() == null) {
414 			retVal = 0;
415 		} else if (theO1.getMissing() == null) {
416 			retVal = -1;
417 		} else if (theO2.getMissing() == null) {
418 			retVal = 1;
419 		} else if (ObjectUtil.equals(theO1.getMissing(), theO2.getMissing())) {
420 			retVal = 0;
421 		} else {
422 			if (theO1.getMissing().booleanValue()) {
423 				retVal = 1;
424 			} else {
425 				retVal = -1;
426 			}
427 		}
428 
429 		if (retVal == 0) {
430 			String q1 = theO1.getQueryParameterQualifier();
431 			String q2 = theO2.getQueryParameterQualifier();
432 			retVal = StringUtils.compare(q1, q2);
433 		}
434 
435 		if (retVal == 0) {
436 			String v1 = theO1.getValueAsQueryToken(theCtx);
437 			String v2 = theO2.getValueAsQueryToken(theCtx);
438 			retVal = StringUtils.compare(v1, v2);
439 		}
440 		return retVal;
441 	}
442 
443 	public enum EverythingModeEnum {
444 		/*
445 		 * Don't reorder! We rely on the ordinals
446 		 */
447 		ENCOUNTER_INSTANCE(false, true, true),
448 		ENCOUNTER_TYPE(false, true, false),
449 		PATIENT_INSTANCE(true, false, true),
450 		PATIENT_TYPE(true, false, false);
451 
452 		private final boolean myEncounter;
453 
454 		private final boolean myInstance;
455 
456 		private final boolean myPatient;
457 
458 		EverythingModeEnum(boolean thePatient, boolean theEncounter, boolean theInstance) {
459 			assert thePatient ^ theEncounter;
460 			myPatient = thePatient;
461 			myEncounter = theEncounter;
462 			myInstance = theInstance;
463 		}
464 
465 		public boolean isEncounter() {
466 			return myEncounter;
467 		}
468 
469 		public boolean isInstance() {
470 			return myInstance;
471 		}
472 
473 		public boolean isPatient() {
474 			return myPatient;
475 		}
476 	}
477 
478 	public class IncludeComparator implements Comparator<Include> {
479 
480 		@Override
481 		public int compare(Include theO1, Include theO2) {
482 			int retVal = StringUtils.compare(theO1.getParamType(), theO2.getParamType());
483 			if (retVal == 0) {
484 				retVal = StringUtils.compare(theO1.getParamName(), theO2.getParamName());
485 			}
486 			if (retVal == 0) {
487 				retVal = StringUtils.compare(theO1.getParamTargetType(), theO2.getParamTargetType());
488 			}
489 			return retVal;
490 		}
491 
492 	}
493 
494 	public class QueryParameterOrComparator implements Comparator<List<IQueryParameterType>> {
495 		private final FhirContext myCtx;
496 
497 		public QueryParameterOrComparator(FhirContext theCtx) {
498 			myCtx = theCtx;
499 		}
500 
501 		@Override
502 		public int compare(List<IQueryParameterType> theO1, List<IQueryParameterType> theO2) {
503 			// These lists will never be empty
504 			return SearchParameterMap.compare(myCtx, theO1.get(0), theO2.get(0));
505 		}
506 
507 	}
508 
509 	public class QueryParameterTypeComparator implements Comparator<IQueryParameterType> {
510 
511 		private final FhirContext myCtx;
512 
513 		public QueryParameterTypeComparator(FhirContext theCtx) {
514 			myCtx = theCtx;
515 		}
516 
517 		@Override
518 		public int compare(IQueryParameterType theO1, IQueryParameterType theO2) {
519 			return SearchParameterMap.compare(myCtx, theO1, theO2);
520 		}
521 
522 	}
523 
524 }