001/*-
002 * #%L
003 * HAPI FHIR JPA - Search Parameters
004 * %%
005 * Copyright (C) 2014 - 2024 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.jpa.searchparam.util;
021
022import ca.uhn.fhir.context.ConfigurationException;
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.context.RuntimeSearchParam;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.model.api.IQueryParameterAnd;
027import ca.uhn.fhir.model.api.IQueryParameterType;
028import ca.uhn.fhir.rest.api.QualifiedParamList;
029import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
030import ca.uhn.fhir.rest.param.CompositeAndListParam;
031import ca.uhn.fhir.rest.param.DateAndListParam;
032import ca.uhn.fhir.rest.param.DateParam;
033import ca.uhn.fhir.rest.param.HasAndListParam;
034import ca.uhn.fhir.rest.param.HasParam;
035import ca.uhn.fhir.rest.param.NumberAndListParam;
036import ca.uhn.fhir.rest.param.NumberParam;
037import ca.uhn.fhir.rest.param.QuantityAndListParam;
038import ca.uhn.fhir.rest.param.QuantityParam;
039import ca.uhn.fhir.rest.param.ReferenceAndListParam;
040import ca.uhn.fhir.rest.param.ReferenceParam;
041import ca.uhn.fhir.rest.param.SpecialAndListParam;
042import ca.uhn.fhir.rest.param.SpecialParam;
043import ca.uhn.fhir.rest.param.StringAndListParam;
044import ca.uhn.fhir.rest.param.StringParam;
045import ca.uhn.fhir.rest.param.TokenAndListParam;
046import ca.uhn.fhir.rest.param.TokenParam;
047import ca.uhn.fhir.rest.param.UriAndListParam;
048import ca.uhn.fhir.rest.param.UriParam;
049import ca.uhn.fhir.rest.param.binder.QueryParameterAndBinder;
050import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
051import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
052import jakarta.annotation.Nonnull;
053
054import java.util.ArrayList;
055import java.util.Collections;
056import java.util.Comparator;
057import java.util.List;
058
059public enum JpaParamUtil {
060        ;
061
062        /**
063         * This is a utility method intended provided to help the JPA module.
064         */
065        public static IQueryParameterAnd<?> parseQueryParams(
066                        FhirContext theContext,
067                        RestSearchParameterTypeEnum paramType,
068                        String theUnqualifiedParamName,
069                        List<QualifiedParamList> theParameters) {
070                QueryParameterAndBinder binder;
071                switch (paramType) {
072                        case COMPOSITE:
073                                throw new UnsupportedOperationException(Msg.code(496));
074                        case DATE:
075                                binder = new QueryParameterAndBinder(DateAndListParam.class, Collections.emptyList());
076                                break;
077                        case NUMBER:
078                                binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections.emptyList());
079                                break;
080                        case QUANTITY:
081                                binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections.emptyList());
082                                break;
083                        case REFERENCE:
084                                binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections.emptyList());
085                                break;
086                        case STRING:
087                                binder = new QueryParameterAndBinder(StringAndListParam.class, Collections.emptyList());
088                                break;
089                        case TOKEN:
090                                binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.emptyList());
091                                break;
092                        case URI:
093                                binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.emptyList());
094                                break;
095                        case HAS:
096                                binder = new QueryParameterAndBinder(HasAndListParam.class, Collections.emptyList());
097                                break;
098                        case SPECIAL:
099                                binder = new QueryParameterAndBinder(SpecialAndListParam.class, Collections.emptyList());
100                                break;
101                        default:
102                                throw new IllegalArgumentException(Msg.code(497) + "Parameter '" + theUnqualifiedParamName
103                                                + "' has type " + paramType + " which is currently not supported.");
104                }
105
106                return binder.parse(theContext, theUnqualifiedParamName, theParameters);
107        }
108
109        /**
110         * This is a utility method intended provided to help the JPA module.
111         */
112        public static IQueryParameterAnd<?> parseQueryParams(
113                        ISearchParamRegistry theSearchParamRegistry,
114                        FhirContext theContext,
115                        RuntimeSearchParam theParamDef,
116                        String theUnqualifiedParamName,
117                        List<QualifiedParamList> theParameters) {
118
119                RestSearchParameterTypeEnum paramType = theParamDef.getParamType();
120
121                if (paramType == RestSearchParameterTypeEnum.COMPOSITE) {
122
123                        List<RuntimeSearchParam> compositeList = resolveComponentParameters(theSearchParamRegistry, theParamDef);
124
125                        if (compositeList.size() != 2) {
126                                throw new ConfigurationException(Msg.code(498) + "Search parameter of type " + theUnqualifiedParamName
127                                                + " must have 2 composite types declared in parameter annotation, found "
128                                                + compositeList.size());
129                        }
130
131                        RuntimeSearchParam left = compositeList.get(0);
132                        RuntimeSearchParam right = compositeList.get(1);
133
134                        @SuppressWarnings({"unchecked", "rawtypes"})
135                        CompositeAndListParam<IQueryParameterType, IQueryParameterType> cp = new CompositeAndListParam(
136                                        getCompositeBindingClass(left.getParamType(), left.getName()),
137                                        getCompositeBindingClass(right.getParamType(), right.getName()));
138
139                        cp.setValuesAsQueryTokens(theContext, theUnqualifiedParamName, theParameters);
140
141                        return cp;
142                } else {
143                        return parseQueryParams(theContext, paramType, theUnqualifiedParamName, theParameters);
144                }
145        }
146
147        public static List<RuntimeSearchParam> resolveComponentParameters(
148                        ISearchParamRegistry theSearchParamRegistry, RuntimeSearchParam theParamDef) {
149                List<RuntimeSearchParam> compositeList =
150                                resolveCompositeComponentsDeclaredOrder(theSearchParamRegistry, theParamDef);
151
152                // todo mb why is this sorted?  Is the param order flipped too during query-time?
153                compositeList.sort((Comparator.comparing(RuntimeSearchParam::getName)));
154
155                return compositeList;
156        }
157
158        @Nonnull
159        public static List<RuntimeSearchParam> resolveCompositeComponentsDeclaredOrder(
160                        ISearchParamRegistry theSearchParamRegistry, RuntimeSearchParam theParamDef) {
161                List<RuntimeSearchParam> compositeList = new ArrayList<>();
162                List<RuntimeSearchParam.Component> components = theParamDef.getComponents();
163                for (RuntimeSearchParam.Component next : components) {
164                        String url = next.getReference();
165                        RuntimeSearchParam componentParam = theSearchParamRegistry.getActiveSearchParamByUrl(url);
166                        if (componentParam == null) {
167                                throw new InternalErrorException(Msg.code(499) + "Can not find SearchParameter: " + url);
168                        }
169                        compositeList.add(componentParam);
170                }
171                return compositeList;
172        }
173
174        private static Class<?> getCompositeBindingClass(
175                        RestSearchParameterTypeEnum paramType, String theUnqualifiedParamName) {
176
177                switch (paramType) {
178                        case DATE:
179                                return DateParam.class;
180                        case NUMBER:
181                                return NumberParam.class;
182                        case QUANTITY:
183                                return QuantityParam.class;
184                        case REFERENCE:
185                                return ReferenceParam.class;
186                        case STRING:
187                                return StringParam.class;
188                        case TOKEN:
189                                return TokenParam.class;
190                        case URI:
191                                return UriParam.class;
192                        case HAS:
193                                return HasParam.class;
194                        case SPECIAL:
195                                return SpecialParam.class;
196
197                        case COMPOSITE:
198                        default:
199                                throw new IllegalArgumentException(Msg.code(500) + "Parameter '" + theUnqualifiedParamName
200                                                + "' has type " + paramType + " which is currently not supported.");
201                }
202        }
203}