001package ca.uhn.fhir.context;
002
003import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
004import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
005import org.apache.commons.lang3.builder.EqualsBuilder;
006import org.apache.commons.lang3.builder.HashCodeBuilder;
007import org.apache.commons.lang3.builder.ToStringBuilder;
008import org.apache.commons.lang3.builder.ToStringStyle;
009import org.hl7.fhir.instance.model.api.IBaseExtension;
010import org.hl7.fhir.instance.model.api.IIdType;
011
012import javax.annotation.Nonnull;
013import javax.annotation.Nullable;
014import java.util.ArrayList;
015import java.util.Collection;
016import java.util.Collections;
017import java.util.HashMap;
018import java.util.HashSet;
019import java.util.List;
020import java.util.Map;
021import java.util.Set;
022import java.util.StringTokenizer;
023
024import static org.apache.commons.lang3.StringUtils.isNotBlank;
025import static org.apache.commons.lang3.StringUtils.trim;
026
027/*
028 * #%L
029 * HAPI FHIR - Core Library
030 * %%
031 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
032 * %%
033 * Licensed under the Apache License, Version 2.0 (the "License");
034 * you may not use this file except in compliance with the License.
035 * You may obtain a copy of the License at
036 *
037 *      http://www.apache.org/licenses/LICENSE-2.0
038 *
039 * Unless required by applicable law or agreed to in writing, software
040 * distributed under the License is distributed on an "AS IS" BASIS,
041 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
042 * See the License for the specific language governing permissions and
043 * limitations under the License.
044 * #L%
045 */
046
047public class RuntimeSearchParam {
048        private final IIdType myId;
049        private final Set<String> myBase;
050        private final String myDescription;
051        private final String myName;
052        private final RestSearchParameterTypeEnum myParamType;
053        private final String myPath;
054        private final Set<String> myTargets;
055        private final Set<String> myProvidesMembershipInCompartments;
056        private final RuntimeSearchParamStatusEnum myStatus;
057        private final String myUri;
058        private final Map<String, List<IBaseExtension<?, ?>>> myExtensions = new HashMap<>();
059        private final ComboSearchParamType myComboSearchParamType;
060        private final List<Component> myComponents;
061        private IPhoneticEncoder myPhoneticEncoder;
062
063        /**
064         * Constructor
065         */
066        public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType,
067                                                                          Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, Collection<String> theBase) {
068                this(theId, theUri, theName, theDescription, thePath, theParamType, theProvidesMembershipInCompartments, theTargets, theStatus, null, Collections.emptyList(), theBase);
069        }
070
071        /**
072         * Copy constructor
073         */
074        public RuntimeSearchParam(RuntimeSearchParam theSp) {
075                this(theSp.getId(), theSp.getUri(), theSp.getName(), theSp.getDescription(), theSp.getPath(), theSp.getParamType(), theSp.getProvidesMembershipInCompartments(), theSp.getTargets(), theSp.getStatus(), theSp.getComboSearchParamType(), theSp.getComponents(), theSp.getBase());
076        }
077
078        /**
079         * Constructor
080         */
081        public RuntimeSearchParam(IIdType theId, String theUri, String theName, String theDescription, String thePath, RestSearchParameterTypeEnum theParamType, Set<String> theProvidesMembershipInCompartments, Set<String> theTargets, RuntimeSearchParamStatusEnum theStatus, ComboSearchParamType theComboSearchParamType, List<Component> theComponents, Collection<String> theBase) {
082                super();
083
084                myId = theId;
085                myUri = theUri;
086                myName = theName;
087                myDescription = theDescription;
088                myPath = thePath;
089                myParamType = theParamType;
090                myStatus = theStatus;
091                if (theProvidesMembershipInCompartments != null && !theProvidesMembershipInCompartments.isEmpty()) {
092                        myProvidesMembershipInCompartments = Collections.unmodifiableSet(theProvidesMembershipInCompartments);
093                } else {
094                        myProvidesMembershipInCompartments = null;
095                }
096                if (theTargets != null && theTargets.isEmpty() == false) {
097                        myTargets = Collections.unmodifiableSet(theTargets);
098                } else {
099                        myTargets = Collections.emptySet();
100                }
101
102                if (theBase == null || theBase.isEmpty()) {
103                        HashSet<String> base = new HashSet<>();
104                        if (isNotBlank(thePath)) {
105                                int indexOf = thePath.indexOf('.');
106                                if (indexOf != -1) {
107                                        base.add(trim(thePath.substring(0, indexOf)));
108                                }
109                        }
110                        myBase = Collections.unmodifiableSet(base);
111                } else {
112                        myBase = Collections.unmodifiableSet(new HashSet<>(theBase));
113                }
114                myComboSearchParamType = theComboSearchParamType;
115                if (theComponents != null) {
116                        myComponents = Collections.unmodifiableList(theComponents);
117                } else {
118                        myComponents = Collections.emptyList();
119                }
120        }
121
122        public List<Component> getComponents() {
123                return myComponents;
124        }
125
126        /**
127         * Returns <code>null</code> if this is not a combo search param type
128         */
129        @Nullable
130        public ComboSearchParamType getComboSearchParamType() {
131                return myComboSearchParamType;
132        }
133
134        /**
135         * Retrieve user data - This can be used to store any application-specific data
136         */
137        @Nonnull
138        public List<IBaseExtension<?, ?>> getExtensions(String theKey) {
139                List<IBaseExtension<?, ?>> retVal = myExtensions.get(theKey);
140                if (retVal != null) {
141                        retVal = Collections.unmodifiableList(retVal);
142                } else {
143                        retVal = Collections.emptyList();
144                }
145                return retVal;
146        }
147
148        /**
149         * Sets user data - This can be used to store any application-specific data
150         */
151        public RuntimeSearchParam addExtension(String theKey, IBaseExtension theValue) {
152                List<IBaseExtension<?, ?>> valuesList = myExtensions.computeIfAbsent(theKey, k -> new ArrayList<>());
153                valuesList.add(theValue);
154                return this;
155        }
156
157        @Override
158        public String toString() {
159                return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
160                        .append("base", myBase)
161                        .append("name", myName)
162                        .append("path", myPath)
163                        .append("id", myId)
164                        .append("uri", myUri)
165                        .toString();
166        }
167
168        public IIdType getId() {
169                return myId;
170        }
171
172        public String getUri() {
173                return myUri;
174        }
175
176        @Override
177        public boolean equals(Object theO) {
178                if (this == theO) return true;
179
180                if (theO == null || getClass() != theO.getClass()) return false;
181
182                RuntimeSearchParam that = (RuntimeSearchParam) theO;
183
184                return new EqualsBuilder()
185                        .append(getId(), that.getId())
186                        .append(getName(), that.getName())
187                        .append(getPath(), that.getPath())
188                        .append(getUri(), that.getUri())
189                        .isEquals();
190        }
191
192        @Override
193        public int hashCode() {
194                return new HashCodeBuilder(17, 37)
195                        .append(getId())
196                        .append(getName())
197                        .append(getPath())
198                        .append(getUri())
199                        .toHashCode();
200        }
201
202        public Set<String> getBase() {
203                return myBase;
204        }
205
206        @Nonnull
207        public Set<String> getTargets() {
208                return myTargets;
209        }
210
211        public boolean hasTargets() {
212                return !myTargets.isEmpty();
213        }
214
215        public RuntimeSearchParamStatusEnum getStatus() {
216                return myStatus;
217        }
218
219        public String getDescription() {
220                return myDescription;
221        }
222
223        public String getName() {
224                return myName;
225        }
226
227        public RestSearchParameterTypeEnum getParamType() {
228                return myParamType;
229        }
230
231        public String getPath() {
232                return myPath;
233        }
234
235        public List<String> getPathsSplit() {
236                return getPathsSplitForResourceType(null);
237        }
238
239        /**
240         * Can return null
241         */
242        public Set<String> getProvidesMembershipInCompartments() {
243                return myProvidesMembershipInCompartments;
244        }
245
246        public RuntimeSearchParam setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {
247                myPhoneticEncoder = thePhoneticEncoder;
248                return this;
249        }
250
251        public String encode(String theString) {
252                if (myPhoneticEncoder == null || theString == null) {
253                        return theString;
254                }
255                return myPhoneticEncoder.encode(theString);
256        }
257
258        public List<String> getPathsSplitForResourceType(@Nullable String theResourceName) {
259                String path = getPath();
260                if (path.indexOf('|') == -1) {
261                        if (theResourceName != null && !pathMatchesResourceType(theResourceName, path)) {
262                                return Collections.emptyList();
263                        }
264                        return Collections.singletonList(path);
265                }
266
267                List<String> retVal = new ArrayList<>();
268                StringTokenizer tok = new StringTokenizer(path, "|");
269                while (tok.hasMoreElements()) {
270                        String nextPath = tok.nextToken().trim();
271                        if (theResourceName != null && !pathMatchesResourceType(theResourceName, nextPath)) {
272                                continue;
273                        }
274                        retVal.add(nextPath.trim());
275                }
276                return retVal;
277        }
278
279        public enum RuntimeSearchParamStatusEnum {
280                ACTIVE,
281                DRAFT,
282                RETIRED,
283                UNKNOWN
284        }
285
286        /**
287         * This method tests whether a given FHIRPath expression <i>could</i>
288         * possibly apply to the given resource type.
289         *
290         * @param theResourceName
291         * @param thePath
292         * @return
293         */
294        static boolean pathMatchesResourceType(String theResourceName, String thePath) {
295                for (int i = 0; i < thePath.length() - 1; i++) {
296                        char nextChar = thePath.charAt(i);
297                        if (Character.isLowerCase(nextChar)) {
298                                return true;
299                        }
300                        if (Character.isLetter(nextChar)) {
301                                if (fhirPathExpressionStartsWith(theResourceName, thePath, i)) {
302                                        return true;
303                                }
304                                if (fhirPathExpressionStartsWith("Resource", thePath, i)) {
305                                        return true;
306                                }
307                                if (fhirPathExpressionStartsWith("DomainResource", thePath, i)) {
308                                        return true;
309                                }
310                                return false;
311                        }
312                }
313
314                return false;
315        }
316
317        private static boolean fhirPathExpressionStartsWith(String theResourceName, String thePath, int theStartingIndex) {
318                if (thePath.startsWith(theResourceName, theStartingIndex) && thePath.length() > theResourceName.length()) {
319                        for (int i = theResourceName.length() + theStartingIndex; i < thePath.length(); i++) {
320                                char nextChar = thePath.charAt(i);
321                                if (nextChar == '.') {
322                                        return true;
323                                } else if (nextChar != ' ') {
324                                        return false;
325                                }
326                        }
327                }
328                return false;
329        }
330
331        public static class Component {
332                private final String myExpression;
333                private final String myReference;
334
335                /**
336                 * Constructor
337                 */
338                public Component(String theExpression, String theReference) {
339                        myExpression = theExpression;
340                        myReference = theReference;
341
342                }
343
344                @Override
345                public String toString() {
346                        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
347                                .append("expression", myExpression)
348                                .append("reference", myReference)
349                                .toString();
350                }
351
352                public String getExpression() {
353                        return myExpression;
354                }
355
356                public String getReference() {
357                        return myReference;
358                }
359        }
360
361}