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.i18n.Msg;
023import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
024import ca.uhn.fhir.model.api.IQueryParameterType;
025import ca.uhn.fhir.rest.param.QuantityParam;
026import ca.uhn.fhir.rest.param.ReferenceParam;
027import org.hl7.fhir.dstu3.model.Location;
028import org.hl7.fhir.instance.model.api.IBaseResource;
029
030import java.util.Collection;
031import java.util.List;
032
033/**
034 * In DSTU3, the near-distance search parameter is separate from near.  In this utility method,
035 * we search for near-distance search parameters and if we find any, remove them from the list
036 * of search parameters and store it in a dedicated field in {@link SearchParameterMap}.  This is so that
037 * when the "near" search parameter is processed, we have access to this near-distance value.
038 * This requires at most one near-distance parameter.  If more are found, we throw an {@link IllegalArgumentException}.
039 */
040public class Dstu3DistanceHelper {
041        public static void setNearDistance(Class<? extends IBaseResource> theResourceType, SearchParameterMap theParams) {
042                if (theResourceType == Location.class && theParams.containsKey(Location.SP_NEAR_DISTANCE)) {
043                        List<List<IQueryParameterType>> paramAndList = theParams.get(Location.SP_NEAR_DISTANCE);
044                        QuantityParam quantityParam = getNearDistanceParam(paramAndList);
045                        theParams.setNearDistanceParam(quantityParam);
046
047                        // Need to remove near-distance or it we'll get a hashcode predicate for it
048                        theParams.remove(Location.SP_NEAR_DISTANCE);
049                } else if (theParams.containsKey("location")) {
050                        List<List<IQueryParameterType>> paramAndList = theParams.get("location");
051                        ReferenceParam referenceParam = getChainedLocationNearDistanceParam(paramAndList);
052                        if (referenceParam != null) {
053                                QuantityParam quantityParam = new QuantityParam(referenceParam.getValue());
054                                theParams.setNearDistanceParam(quantityParam);
055                        }
056                }
057        }
058
059        private static ReferenceParam getChainedLocationNearDistanceParam(List<List<IQueryParameterType>> theParamAndList) {
060                ReferenceParam retval = null;
061                List<IQueryParameterType> andParamToRemove = null;
062                for (List<IQueryParameterType> paramOrList : theParamAndList) {
063                        IQueryParameterType orParamToRemove = null;
064                        for (IQueryParameterType param : paramOrList) {
065                                if (param instanceof ReferenceParam) {
066                                        ReferenceParam referenceParam = (ReferenceParam) param;
067                                        if (Location.SP_NEAR_DISTANCE.equals(referenceParam.getChain())) {
068                                                if (retval != null) {
069                                                        throw new IllegalArgumentException(Msg.code(494) + "Only one " + Location.SP_NEAR_DISTANCE
070                                                                        + " parameter may be present");
071                                                } else {
072                                                        retval = referenceParam;
073                                                        orParamToRemove = param;
074                                                }
075                                        }
076                                }
077                        }
078                        if (orParamToRemove != null) {
079                                paramOrList.remove(orParamToRemove);
080                                if (paramOrList.isEmpty()) {
081                                        andParamToRemove = paramOrList;
082                                }
083                        }
084                }
085                if (andParamToRemove != null) {
086                        theParamAndList.remove(andParamToRemove);
087                }
088                return retval;
089        }
090
091        private static QuantityParam getNearDistanceParam(List<List<IQueryParameterType>> theParamAndList) {
092                long sum = theParamAndList.stream().mapToLong(Collection::size).sum();
093
094                // No near-distance Param
095                if (sum == 0) {
096                        return null;
097                        // A single near-distance Param
098                } else if (sum == 1) {
099                        return (QuantityParam) theParamAndList.get(0).get(0);
100                        // Too many near-distance params
101                } else {
102                        throw new IllegalArgumentException(
103                                        Msg.code(495) + "Only one " + Location.SP_NEAR_DISTANCE + " parameter may be present");
104                }
105        }
106}