001/*-
002 * #%L
003 * Smile CDR - CDR
004 * %%
005 * Copyright (C) 2016 - 2025 Smile CDR, Inc.
006 * %%
007 * All rights reserved.
008 * #L%
009 */
010package ca.cdr.api.fhirgw.model;
011
012import ca.uhn.fhir.model.api.Include;
013import ca.uhn.fhir.rest.api.SearchStyleEnum;
014import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
015import ca.uhn.fhir.rest.api.SortSpec;
016import ca.uhn.fhir.rest.param.DateRangeParam;
017import ca.uhn.fhir.rest.param.UriAndListParam;
018import ca.uhn.fhir.rest.param.UriOrListParam;
019import com.google.common.collect.ArrayListMultimap;
020import com.google.common.collect.ListMultimap;
021import com.google.common.collect.Multimap;
022import com.google.common.collect.Multimaps;
023import jakarta.annotation.Nonnull;
024import jakarta.annotation.Nullable;
025import org.apache.commons.lang3.Validate;
026
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import static org.apache.commons.lang3.ObjectUtils.getIfNull;
034
035/**
036 * This class represents a FHIR Gateway Search request
037 */
038public class SearchRequest extends BaseRequest<SearchRequest> {
039
040        private ListMultimap<String, String> myParameters = EMPTY_STRING_MULTIMAP;
041        private Set<Include> myIncludes = Collections.emptySet();
042        private Set<Include> myRevIncludes = Collections.emptySet();
043        private SortSpec mySort;
044        private Integer myCount;
045        private SearchTotalModeEnum myTotalMode;
046        private Integer myOffset;
047        private DateRangeParam myLastUpdated;
048        private UriAndListParam myProfile;
049
050        @Nullable
051        private SearchStyleEnum mySearchStyle;
052
053        /**
054         * Constructor
055         */
056        public SearchRequest() {
057                // nothing
058        }
059
060        /**
061         * Copy constructor
062         */
063        public SearchRequest(SearchRequest theSearchRequest) {
064                super(theSearchRequest);
065
066                if (!theSearchRequest.getParameters().isEmpty()) {
067                        myParameters = ArrayListMultimap.create();
068                        myParameters.putAll(theSearchRequest.getParameters());
069                }
070
071                if (!theSearchRequest.getIncludes().isEmpty()) {
072                        myIncludes = new HashSet<>();
073                        for (Include next : theSearchRequest.getIncludes()) {
074                                myIncludes.add(next.toLocked());
075                        }
076                }
077
078                if (!theSearchRequest.getRevIncludes().isEmpty()) {
079                        myRevIncludes = new HashSet<>();
080                        for (Include next : theSearchRequest.getRevIncludes()) {
081                                myRevIncludes.add(next.toLocked());
082                        }
083                }
084
085                if (theSearchRequest.getSort() != null) {
086                        mySort = clone(theSearchRequest.getSort());
087                }
088
089                if (theSearchRequest.getLastUpdated() != null) {
090                        myLastUpdated = new DateRangeParam(
091                                        theSearchRequest.getLastUpdated().getLowerBound() != null
092                                                        ? theSearchRequest.getLastUpdated().getLowerBound().getValueAsString()
093                                                        : null,
094                                        theSearchRequest.getLastUpdated().getUpperBound() != null
095                                                        ? theSearchRequest.getLastUpdated().getUpperBound().getValueAsString()
096                                                        : null);
097                }
098
099                if (theSearchRequest.getProfile() != null) {
100                        myProfile = new UriAndListParam();
101                        for (UriOrListParam uriOrListParam : theSearchRequest.getProfile().getValuesAsQueryTokens()) {
102                                myProfile.addAnd(uriOrListParam);
103                        }
104                }
105
106                myCount = theSearchRequest.getCount();
107                myOffset = theSearchRequest.getOffset();
108                myTotalMode = theSearchRequest.getTotalMode();
109                mySearchStyle = theSearchRequest.getSearchStyle();
110        }
111
112        private static SortSpec clone(SortSpec theSort) {
113                if (theSort == null) {
114                        return null;
115                }
116                return new SortSpec(theSort.getParamName(), theSort.getOrder(), clone(theSort.getChain()));
117        }
118
119        /**
120         * Specifies the search form (GET vs POST) to use for this search. If set to
121         * <code>null</code>, the default will be used.
122         */
123        @Nullable
124        public SearchStyleEnum getSearchStyle() {
125                return mySearchStyle;
126        }
127
128        /**
129         * Specifies the search form (GET vs POST) to use for this search. If set to
130         * <code>null</code>, the default will be used.
131         */
132        public void setSearchStyle(@Nullable SearchStyleEnum theSearchStyle) {
133                mySearchStyle = theSearchStyle;
134        }
135
136        public Multimap<String, String> getParameters() {
137                return Multimaps.unmodifiableMultimap(myParameters);
138        }
139
140        public SearchRequest setParameters(Map<String, List<String>> theParameters) {
141                Validate.notNull(theParameters, "theParameters must not be null");
142                myParameters = ArrayListMultimap.create();
143                for (Map.Entry<String, List<String>> next : theParameters.entrySet()) {
144                        myParameters.putAll(next.getKey(), next.getValue());
145                }
146                return this;
147        }
148
149        /**
150         * Add a new search parameter
151         */
152        public void addParameter(String theName, String theValue) {
153                Validate.notBlank(theName, "theName must not be empty");
154                Validate.notBlank(theValue, "theValue must not be empty");
155                if (myParameters.isEmpty()) {
156                        myParameters = ArrayListMultimap.create();
157                }
158                myParameters.put(theName, theValue);
159        }
160
161        /**
162         * Adds OR parameters to the list of search parameters as a key value
163         * pair where the value is a comma-delimited list i.e. _id=123,345
164         *
165         * @param theName   The parameter name
166         * @param theValues the values for the parameter
167         */
168        public void addOrListParameter(String theName, List<String> theValues) {
169                Validate.notBlank(theName, "theName must not be empty");
170                Validate.notEmpty(theValues, "theValues must not be empty");
171                String values = String.join(",", theValues);
172                if (myParameters.isEmpty()) {
173                        myParameters = ArrayListMultimap.create();
174                }
175                myParameters.put(theName, values);
176        }
177
178        /**
179         * Remove all parameters with the given name
180         *
181         * @return Returns any parameter values that were removed
182         */
183        public List<String> removeParameters(String theName) {
184                if (!myParameters.isEmpty()) {
185                        return Collections.unmodifiableList(myParameters.removeAll(theName));
186                } else {
187                        return Collections.emptyList();
188                }
189        }
190
191        /**
192         * Remove the parameter with the given name and value, if found. Any other parameters with the same name are kept.
193         *
194         * @param theName  The parameter name
195         * @param theValue The parameter value
196         * @return Returns <code>true</code> if a parameter value was removed
197         */
198        public boolean removeParameter(String theName, String theValue) {
199                return myParameters.remove(theName, theValue);
200        }
201
202        /**
203         * Returns the first parameter value for a given parameter name (returns <code>null</code> if no parameters match the given name)
204         *
205         * @param theName The parameter name
206         * @return The value, or <code>null</code>
207         */
208        @Nullable
209        public String getParameter(String theName) {
210                List<String> values = getParameters(theName);
211                if (!values.isEmpty()) {
212                        return values.get(0);
213                }
214                return null;
215        }
216
217        /**
218         * Returns a List of parameter values for the given name. Will not return <code>null</code>.
219         *
220         * @param theName The parameter name
221         * @return A List of values, or an empty List
222         */
223        @Nonnull
224        public List<String> getParameters(String theName) {
225                List<String> values = myParameters.get(theName);
226                values = getIfNull(values, Collections.emptyList());
227                return Collections.unmodifiableList(values);
228        }
229
230        public Set<Include> getIncludes() {
231                return myIncludes;
232        }
233
234        public SearchRequest setIncludes(Set<Include> theIncludes) {
235                Validate.notNull(theIncludes, "theIncludes must not be null");
236                myIncludes = theIncludes;
237                return this;
238        }
239
240        public Set<Include> getRevIncludes() {
241                return myRevIncludes;
242        }
243
244        public SearchRequest setRevIncludes(Set<Include> theRevIncludes) {
245                Validate.notNull(theRevIncludes, "theRevIncludes must not be null");
246                myRevIncludes = theRevIncludes;
247                return this;
248        }
249
250        public SortSpec getSort() {
251                return mySort;
252        }
253
254        public SearchRequest setSort(SortSpec theSort) {
255                mySort = theSort;
256                return this;
257        }
258
259        public Integer getCount() {
260                return myCount;
261        }
262
263        public SearchRequest setCount(Integer theCount) {
264                myCount = theCount;
265                return this;
266        }
267
268        public Integer getOffset() {
269                return myOffset;
270        }
271
272        public SearchRequest setOffset(Integer theOffset) {
273                myOffset = theOffset;
274                return this;
275        }
276
277        public DateRangeParam getLastUpdated() {
278                return myLastUpdated;
279        }
280
281        public SearchRequest setLastUpdated(DateRangeParam theLastUpdated) {
282                myLastUpdated = theLastUpdated;
283                return this;
284        }
285
286        public UriAndListParam getProfile() {
287                return myProfile;
288        }
289
290        public SearchRequest setProfile(UriAndListParam theProfile) {
291                myProfile = theProfile;
292                return this;
293        }
294
295        public SearchTotalModeEnum getTotalMode() {
296                return myTotalMode;
297        }
298
299        public SearchRequest setTotalMode(SearchTotalModeEnum theTotalMode) {
300                myTotalMode = theTotalMode;
301                return this;
302        }
303}