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.defaultIfNull;
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         * Remove all parameters with the given name
151         *
152         * @return Returns any parameter values that were removed
153         */
154        public List<String> removeParameters(String theName) {
155                if (!myParameters.isEmpty()) {
156                        return Collections.unmodifiableList(myParameters.removeAll(theName));
157                } else {
158                        return Collections.emptyList();
159                }
160        }
161
162        /**
163         * Remove the parameter with the given name and value, if found. Any other parameters with the same name are kept.
164         *
165         * @param theName  The parameter name
166         * @param theValue The parameter value
167         * @return Returns <code>true</code> if a parameter value was removed
168         */
169        public boolean removeParameter(String theName, String theValue) {
170                return myParameters.remove(theName, theValue);
171        }
172
173        /**
174         * Returns the first parameter value for a given parameter name (returns <code>null</code> if no parameters match the given name)
175         *
176         * @param theName The parameter name
177         * @return The value, or <code>null</code>
178         */
179        @Nullable
180        public String getParameter(String theName) {
181                List<String> values = getParameters(theName);
182                if (!values.isEmpty()) {
183                        return values.get(0);
184                }
185                return null;
186        }
187
188        /**
189         * Returns a List of parameter values for the given name. Will not return <code>null</code>.
190         *
191         * @param theName The parameter name
192         * @return A List of values, or an empty List
193         */
194        @Nonnull
195        public List<String> getParameters(String theName) {
196                List<String> values = myParameters.get(theName);
197                values = defaultIfNull(values, Collections.emptyList());
198                return Collections.unmodifiableList(values);
199        }
200
201        public Set<Include> getIncludes() {
202                return myIncludes;
203        }
204
205        public SearchRequest setIncludes(Set<Include> theIncludes) {
206                Validate.notNull(theIncludes, "theIncludes must not be null");
207                myIncludes = theIncludes;
208                return this;
209        }
210
211        public Set<Include> getRevIncludes() {
212                return myRevIncludes;
213        }
214
215        public SearchRequest setRevIncludes(Set<Include> theRevIncludes) {
216                Validate.notNull(theRevIncludes, "theRevIncludes must not be null");
217                myRevIncludes = theRevIncludes;
218                return this;
219        }
220
221        public SortSpec getSort() {
222                return mySort;
223        }
224
225        public SearchRequest setSort(SortSpec theSort) {
226                mySort = theSort;
227                return this;
228        }
229
230        public Integer getCount() {
231                return myCount;
232        }
233
234        public SearchRequest setCount(Integer theCount) {
235                myCount = theCount;
236                return this;
237        }
238
239        public Integer getOffset() {
240                return myOffset;
241        }
242
243        public SearchRequest setOffset(Integer theOffset) {
244                myOffset = theOffset;
245                return this;
246        }
247
248        public DateRangeParam getLastUpdated() {
249                return myLastUpdated;
250        }
251
252        public SearchRequest setLastUpdated(DateRangeParam theLastUpdated) {
253                myLastUpdated = theLastUpdated;
254                return this;
255        }
256
257        public UriAndListParam getProfile() {
258                return myProfile;
259        }
260
261        public SearchRequest setProfile(UriAndListParam theProfile) {
262                myProfile = theProfile;
263                return this;
264        }
265
266        public SearchTotalModeEnum getTotalMode() {
267                return myTotalMode;
268        }
269
270        public SearchRequest setTotalMode(SearchTotalModeEnum theTotalMode) {
271                myTotalMode = theTotalMode;
272                return this;
273        }
274
275        /**
276         * Add a new search parameter
277         */
278        public void addParameter(String theName, String theValue) {
279                Validate.notBlank(theName, "theName must not be empty");
280                Validate.notBlank(theValue, "theValue must not be empty");
281                if (myParameters.isEmpty()) {
282                        myParameters = ArrayListMultimap.create();
283                }
284                myParameters.put(theName, theValue);
285        }
286}