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