
001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 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.rest.api; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.model.api.IQueryParameterOr; 024import ca.uhn.fhir.model.api.IQueryParameterType; 025import jakarta.annotation.Nonnull; 026 027import java.util.ArrayList; 028import java.util.StringTokenizer; 029 030import static org.apache.commons.lang3.StringUtils.isBlank; 031 032public class QualifiedParamList extends ArrayList<String> { 033 034 private static final long serialVersionUID = 1L; 035 036 private String myQualifier; 037 038 public QualifiedParamList() { 039 super(); 040 } 041 042 public QualifiedParamList(int theCapacity) { 043 super(theCapacity); 044 } 045 046 public QualifiedParamList(IQueryParameterOr<?> theNextOr, FhirContext theContext) { 047 for (IQueryParameterType next : theNextOr.getValuesAsQueryTokens()) { 048 if (myQualifier == null) { 049 myQualifier = next.getQueryParameterQualifier(); 050 } 051 add(next.getValueAsQueryToken(theContext)); 052 } 053 } 054 055 public String getQualifier() { 056 return myQualifier; 057 } 058 059 public void setQualifier(String theQualifier) { 060 myQualifier = theQualifier; 061 } 062 063 public static QualifiedParamList singleton(String theParamValue) { 064 return singleton(null, theParamValue); 065 } 066 067 public static QualifiedParamList singleton(String theQualifier, String theParamValue) { 068 QualifiedParamList retVal = new QualifiedParamList(1); 069 retVal.setQualifier(theQualifier); 070 retVal.add(theParamValue); 071 return retVal; 072 } 073 074 @Nonnull 075 public static QualifiedParamList splitQueryStringByCommasIgnoreEscape(String theQualifier, String theParams) { 076 QualifiedParamList retVal = new QualifiedParamList(); 077 retVal.setQualifier(theQualifier); 078 079 StringTokenizer tok = new StringTokenizer(theParams, ",", true); 080 String prev = null; 081 while (tok.hasMoreElements()) { 082 String str = tok.nextToken(); 083 if (isBlank(str)) { 084 prev = null; 085 continue; 086 } 087 088 if (str.equals(",")) { 089 if (countTrailingSlashes(prev) % 2 == 1) { 090 int idx = retVal.size() - 1; 091 String existing = retVal.get(idx); 092 prev = existing.substring(0, existing.length() - 1) + ','; 093 retVal.set(idx, prev); 094 } else { 095 prev = null; 096 } 097 continue; 098 } 099 100 if (prev != null && prev.length() > 0 && prev.charAt(prev.length() - 1) == ',') { 101 int idx = retVal.size() - 1; 102 String existing = retVal.get(idx); 103 prev = existing + str; 104 retVal.set(idx, prev); 105 } else { 106 retVal.add(str); 107 prev = str; 108 } 109 } 110 111 // If no value was found, at least add that empty string as a value. It should get ignored later, but at 112 // least this lets us give a sensible error message if the parameter name was bad. See 113 // ResourceProviderR4Test#testParameterWithNoValueThrowsError_InvalidChainOnCustomSearch for an example 114 if (retVal.size() == 0) { 115 retVal.add(""); 116 } 117 118 return retVal; 119 } 120 121 private static int countTrailingSlashes(String theString) { 122 if (theString == null) { 123 return 0; 124 } 125 int retVal = 0; 126 for (int i = theString.length() - 1; i >= 0; i--) { 127 char nextChar = theString.charAt(i); 128 if (nextChar != '\\') { 129 break; 130 } 131 retVal++; 132 } 133 return retVal; 134 } 135}