001/*- 002 * #%L 003 * HAPI FHIR JPA Server 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.search.builder.predicate; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jpa.model.util.UcumServiceUtil; 024import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 025import ca.uhn.fhir.model.api.IQueryParameterType; 026import ca.uhn.fhir.rest.param.QuantityParam; 027import ca.uhn.fhir.rest.param.SpecialParam; 028import ca.uhn.fhir.rest.param.TokenParam; 029import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 030import ca.uhn.fhir.util.UrlUtil; 031import org.apache.commons.lang3.StringUtils; 032 033import static org.apache.commons.lang3.StringUtils.defaultString; 034import static org.apache.commons.lang3.StringUtils.isBlank; 035 036public class ParsedLocationParam { 037 private final double myLatitudeValue; 038 private final double myLongitudeValue; 039 private final double myDistanceKm; 040 041 private ParsedLocationParam(String theLatitudeValue, String theLongitudeValue, double theDistanceKm) { 042 myLatitudeValue = parseLatLonParameter(theLatitudeValue); 043 myLongitudeValue = parseLatLonParameter(theLongitudeValue); 044 myDistanceKm = theDistanceKm; 045 } 046 047 private static double parseLatLonParameter(String theValue) { 048 try { 049 return Double.parseDouble(defaultString(theValue)); 050 } catch (NumberFormatException e) { 051 throw new InvalidRequestException( 052 Msg.code(2308) + "Invalid lat/lon parameter value: " + UrlUtil.sanitizeUrlPart(theValue)); 053 } 054 } 055 056 public double getLatitudeValue() { 057 return myLatitudeValue; 058 } 059 060 public double getLongitudeValue() { 061 return myLongitudeValue; 062 } 063 064 public double getDistanceKm() { 065 return myDistanceKm; 066 } 067 068 public static ParsedLocationParam from(SearchParameterMap theParams, IQueryParameterType theParam) { 069 String latitudeValue; 070 String longitudeValue; 071 double distanceKm = 0.0; 072 073 if (theParam instanceof TokenParam) { // DSTU3 074 TokenParam param = (TokenParam) theParam; 075 String value = param.getValue(); 076 String[] parts = value.split(":"); 077 if (parts.length != 2) { 078 throw new IllegalArgumentException(Msg.code(1228) + "Invalid position format '" + value 079 + "'. Required format is 'latitude:longitude'"); 080 } 081 latitudeValue = parts[0]; 082 longitudeValue = parts[1]; 083 if (isBlank(latitudeValue) || isBlank(longitudeValue)) { 084 throw new IllegalArgumentException(Msg.code(1229) + "Invalid position format '" + value 085 + "'. Both latitude and longitude must be provided."); 086 } 087 QuantityParam distanceParam = theParams.getNearDistanceParam(); 088 if (distanceParam != null) { 089 distanceKm = parseLatLonParameter(distanceParam.getValueAsString()); 090 } 091 } else if (theParam instanceof SpecialParam) { // R4 092 SpecialParam param = (SpecialParam) theParam; 093 String value = param.getValue(); 094 String[] parts = StringUtils.split(value, '|'); 095 if (parts.length < 2 || parts.length > 4) { 096 throw new IllegalArgumentException( 097 Msg.code(1230) + "Invalid position format '" + value 098 + "'. Required format is 'latitude|longitude' or 'latitude|longitude|distance' or 'latitude|longitude|distance|units'"); 099 } 100 latitudeValue = parts[0]; 101 longitudeValue = parts[1]; 102 if (isBlank(latitudeValue) || isBlank(longitudeValue)) { 103 throw new IllegalArgumentException(Msg.code(1231) + "Invalid position format '" + value 104 + "'. Both latitude and longitude must be provided."); 105 } 106 if (parts.length >= 3) { 107 String distanceString = parts[2]; 108 if (!isBlank(distanceString)) { 109 distanceKm = parseLatLonParameter(distanceString); 110 } 111 112 if (parts.length >= 4) { 113 String distanceUnits = parts[3]; 114 distanceKm = UcumServiceUtil.convert(distanceKm, distanceUnits, "km"); 115 } 116 } 117 } else { 118 throw new IllegalArgumentException(Msg.code(1232) + "Invalid position type: " + theParam.getClass()); 119 } 120 121 return new ParsedLocationParam(latitudeValue, longitudeValue, distanceKm); 122 } 123}