001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.httppart; 018 019import static org.apache.juneau.commons.httppart.HttpPartType.*; 020import static org.apache.juneau.commons.utils.CollectionUtils.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.StringUtils.*; 023 024import java.util.*; 025import java.util.stream.*; 026 027import org.apache.juneau.commons.collections.*; 028import org.apache.juneau.commons.lang.*; 029import org.apache.juneau.commons.svl.*; 030import org.apache.juneau.commons.utils.*; 031import org.apache.juneau.http.*; 032import org.apache.juneau.http.HttpPart; 033import org.apache.juneau.http.part.*; 034import org.apache.juneau.marshall.httppart.*; 035import org.apache.juneau.rest.server.*; 036 037/** 038 * Represents the query parameters in an HTTP request. 039 * 040 * <p> 041 * The {@link RequestQueryParamList} object is the API for accessing the GET query parameters of an HTTP request. 042 * It can be accessed by passing it as a parameter on your REST Java method: 043 * </p> 044 * <p class='bjava'> 045 * <ja>@RestPost</ja>(...) 046 * <jk>public</jk> Object myMethod(RequestQueryParamList <jv>query</jv>) {...} 047 * </p> 048 * 049 * <h5 class='figure'>Example:</h5> 050 * <p class='bjava'> 051 * <ja>@RestPost</ja>(...) 052 * <jk>public</jk> Object myMethod(RequestQueryParamList <jv>query</jv>) { 053 * 054 * <jc>// Get query parameters converted to various types.</jc> 055 * <jk>int</jk> <jv>p1</jv> = <jv>query</jv>.get(<js>"p1"</js>).asInteger().orElse(0); 056 * String <jv>p2</jv> = <jv>query</jv>.get(<js>"p2"</js>).orElse(<jk>null</jk>); 057 * UUID <jv>p3</jv> = <jv>query</jv>.get(<js>"p3"</js>).as(UUID.<jk>class</jk>).orElse(<jk>null</jk>); 058 * } 059 * </p> 060 * 061 * <p> 062 * An important distinction between the behavior of this object and <l>HttpServletRequest.getParameter(String)</l> is 063 * that the former will NOT load the content of the request on FORM POSTS and will only look at parameters 064 * found in the query string. 065 * This can be useful in cases where you're mixing GET parameters and FORM POSTS and you don't want to 066 * inadvertently read the content of the request to get a query parameter. 067 * </p> 068 * 069 * <p> 070 * Some important methods on this class are: 071 * </p> 072 * <ul class='javatree'> 073 * <li class='jc'>{@link RequestQueryParamList} 074 * <ul class='spaced-list'> 075 * <li>Methods for retrieving query parameters: 076 * <ul class='javatreec'> 077 * <li class='jm'>{@link RequestQueryParamList#contains(String) contains(String)} 078 * <li class='jm'>{@link RequestQueryParamList#containsAny(String...) containsAny(String...)} 079 * <li class='jm'>{@link RequestQueryParamList#get(Class) get(Class)} 080 * <li class='jm'>{@link RequestQueryParamList#get(String) get(String)} 081 * <li class='jm'>{@link RequestQueryParamList#getAll(String) getAll(String)} 082 * <li class='jm'>{@link RequestQueryParamList#getFirst(String) getFirst(String)} 083 * <li class='jm'>{@link RequestQueryParamList#getLast(String) getLast(String)} 084 * </ul> 085 * <li>Methods overridding query parameters: 086 * <ul class='javatreec'> 087 * <li class='jm'>{@link RequestQueryParamList#add(HttpPart...) add(HttpPart...)} 088 * <li class='jm'>{@link RequestQueryParamList#add(String,Object) add(String,Object)} 089 * <li class='jm'>{@link RequestQueryParamList#addDefault(List) addDefault(List)} 090 * <li class='jm'>{@link RequestQueryParamList#addDefault(HttpPart...) addDefault(HttpPart...)} 091 * <li class='jm'>{@link RequestQueryParamList#addDefault(String,String) addDefault(String,String)} 092 * <li class='jm'>{@link RequestQueryParamList#remove(String) remove(String)} 093 * <li class='jm'>{@link RequestQueryParamList#set(HttpPart...) set(HttpPart...)} 094 * <li class='jm'>{@link RequestQueryParamList#set(String,Object) set(String,Object)} 095 * </ul> 096 * <li>Other methods: 097 * <ul class='javatreec'> 098 * <li class='jm'>{@link RequestQueryParamList#asQueryString() asQueryString()} 099 * <li class='jm'>{@link RequestQueryParamList#copy() copy()} 100 * <li class='jm'>{@link RequestQueryParamList#isEmpty() isEmpty()} 101 * </ul> 102 * </ul> 103 * </ul> 104 * 105 * <p> 106 * Entries are stored in a case-sensitive map unless overridden via the constructor. 107 * 108 * <h5 class='section'>See Also:</h5><ul> 109 * <li class='jc'>{@link RequestQueryParam} 110 * <li class='ja'>{@link Query} 111 * <li class='ja'>{@link HasQuery} 112 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerHttpParts">HTTP parts in REST servers</a> 113 * </ul> 114 */ 115@SuppressWarnings({ 116 "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants 117}) 118public class RequestQueryParamList extends ArrayList<RequestQueryParam> { 119 120 private static final long serialVersionUID = 1L; 121 122 private final transient RestRequest req; 123 private boolean caseSensitive; 124 private final transient VarResolverSession vs; 125 private transient HttpPartParserSession parser; 126 127 /** 128 * Constructor. 129 * 130 * @param req The request creating this bean. Must not be <jk>null</jk>. 131 * @param query The raw parsed query parameter values. Must not be <jk>null</jk>. 132 * @param caseSensitive Whether case-sensitive name matching is enabled. 133 */ 134 public RequestQueryParamList(RestRequest req, Map<String,String[]> query, boolean caseSensitive) { 135 this.req = req; 136 this.caseSensitive = caseSensitive; 137 this.vs = req.getVarResolverSession(); 138 139 for (var e : query.entrySet()) { 140 var name = e.getKey(); 141 142 var values = e.getValue(); 143 if (values == null) 144 values = new String[0]; 145 146 // Fix for behavior difference between Tomcat and WAS. 147 // getParameter("foo") on "&foo" in Tomcat returns "". 148 // getParameter("foo") on "&foo" in WAS returns null. 149 if (values.length == 1 && values[0] == null) 150 values[0] = ""; 151 152 if (values.length == 0) 153 values = a((String)null); 154 155 for (var value : values) 156 add(new RequestQueryParam(req, name, value)); 157 } 158 } 159 160 /** 161 * Copy constructor. 162 */ 163 private RequestQueryParamList(RequestQueryParamList copyFrom) { 164 req = copyFrom.req; 165 caseSensitive = copyFrom.caseSensitive; 166 parser = copyFrom.parser; 167 addAll(copyFrom); 168 vs = copyFrom.vs; 169 } 170 171 /** 172 * Subset constructor. 173 */ 174 private RequestQueryParamList(RequestQueryParamList copyFrom, String...names) { 175 this.req = copyFrom.req; 176 caseSensitive = copyFrom.caseSensitive; 177 parser = copyFrom.parser; 178 vs = copyFrom.vs; 179 for (var n : names) 180 copyFrom.stream().filter(x -> eq(x.getName(), n)).forEach(this::add); 181 } 182 183 /** 184 * Adds request parameter values. 185 * 186 * <p> 187 * Parameters are added to the end. 188 * <br>Existing parameters with the same name are not changed. 189 * 190 * @param parameters The parameter objects. Must not be <jk>null</jk>. 191 * @return This object. 192 */ 193 public RequestQueryParamList add(HttpPart...parameters) { 194 reqnn("parameters", parameters); 195 for (var p : parameters) 196 if (nn(p)) 197 add(p.getName(), p.getValue()); 198 return this; 199 } 200 201 /** 202 * Adds a parameter value. 203 * 204 * <p> 205 * Parameter is added to the end. 206 * <br>Existing parameter with the same name are not changed. 207 * 208 * @param name The parameter name. Must not be <jk>null</jk>. 209 * @param value The parameter value. 210 * @return This object. 211 */ 212 public RequestQueryParamList add(String name, Object value) { 213 reqnn("name", name); 214 add(new RequestQueryParam(req, name, s(value)).parser(parser)); 215 return this; 216 } 217 218 /** 219 * Adds default entries to these parameters. 220 * 221 * <p> 222 * Similar to {@link #set(String, Object)} but doesn't override existing values. 223 * 224 * @param pairs 225 * The default entries. 226 * <br>Can be <jk>null</jk> (no-op). 227 * @return This object. 228 */ 229 public RequestQueryParamList addDefault(List<? extends HttpPart> pairs) { 230 if (pairs == null) 231 return this; 232 for (var p : pairs) { 233 var name = p.getName(); 234 var l = stream(name); 235 var hasAllBlanks = l.allMatch(x -> Shorts.ie(x.getValue())); 236 if (hasAllBlanks) { 237 removeAll(getAll(name)); 238 add(new RequestQueryParam(req, name, vs.resolve(p.getValue()))); 239 } 240 } 241 return this; 242 } 243 244 /** 245 * Adds default entries to these parameters. 246 * 247 * <p> 248 * Similar to {@link #set(String, Object)} but doesn't override existing values. 249 * 250 * @param pairs 251 * The default entries. 252 * <br>Can be <jk>null</jk> (no-op). 253 * @return This object. 254 */ 255 public RequestQueryParamList addDefault(HttpPart...pairs) { 256 return addDefault(l(pairs)); 257 } 258 259 /** 260 * Adds a default entry to the query parameters. 261 * 262 * @param name The name. 263 * @param value The value. 264 * @return This object. 265 */ 266 public RequestQueryParamList addDefault(String name, String value) { 267 return addDefault(HttpStringPart.of(name, value)); 268 } 269 270 /** 271 * Converts this object to a query string. 272 * 273 * <p> 274 * Returned query string does not start with <js>'?'</js>. 275 * 276 * @return A new query string, or an empty string if this object is empty. 277 */ 278 public String asQueryString() { 279 var sb = new StringBuilder(); 280 for (var e : this) { 281 if (!sb.isEmpty()) 282 sb.append("&"); 283 sb.append(urlEncode(e.getName())).append('=').append(urlEncode(e.getValue())); 284 } 285 return sb.toString(); 286 } 287 288 /** 289 * Sets case sensitivity for names in this list. 290 * 291 * @param value The new value for this setting. 292 * @return This object (for method chaining). 293 */ 294 public RequestQueryParamList caseSensitive(boolean value) { 295 caseSensitive = value; 296 return this; 297 } 298 299 /** 300 * Returns <jk>true</jk> if the parameters with the specified name is present. 301 * 302 * @param name The parameter name. Must not be <jk>null</jk>. 303 * @return <jk>true</jk> if the parameters with the specified names are present. 304 */ 305 public boolean contains(String name) { 306 return stream(name).findAny().isPresent(); 307 } 308 309 /** 310 * Returns <jk>true</jk> if the parameter with any of the specified names are present. 311 * 312 * @param names The parameter names. Must not be <jk>null</jk>. 313 * @return <jk>true</jk> if the parameter with any of the specified names are present. 314 */ 315 public boolean containsAny(String...names) { 316 reqnn("names", names); 317 for (var n : names) 318 if (stream(n).findAny().isPresent()) 319 return true; 320 return false; 321 } 322 323 /** 324 * Makes a copy of these parameters. 325 * 326 * @return A new parameters object. 327 */ 328 public RequestQueryParamList copy() { 329 return new RequestQueryParamList(this); 330 } 331 332 /** 333 * Returns the query parameter as the specified bean type. 334 * 335 * <p> 336 * Type must have a name specified via the {@link Query} annotation 337 * and a public constructor that takes in either <c>value</c> or <c>name,value</c> as strings. 338 * 339 * @param <T> The bean type to create. 340 * @param type The bean type to create. 341 * @return The bean, or {@link Optional#empty()} if the parameter is not present. 342 */ 343 public <T> Optional<T> get(Class<T> type) { 344 var cm = req.getMarshallingSession().getClassMeta(type); 345 var name = HttpParts.getName(QUERY, cm).orElseThrow(() -> rex("@Query(name) not found on class %s", cn(type))); 346 return get(name).as(type); 347 } 348 349 /** 350 * Returns the condensed header with the specified name. 351 * 352 * <p> 353 * If multiple headers are present, they will be combined into a single comma-delimited list. 354 * 355 * @param name The header name. 356 * @return The header, never <jk>null</jk>. 357 */ 358 public RequestQueryParam get(String name) { 359 List<RequestQueryParam> l = getAll(name); 360 if (l.isEmpty()) 361 return new RequestQueryParam(req, name, null).parser(parser); 362 if (l.size() == 1) 363 return l.get(0); 364 var sb = new StringBuilder(128); 365 for (var i = 0; i < l.size(); i++) { 366 if (i > 0) 367 sb.append(", "); 368 sb.append(l.get(i).getValue()); 369 } 370 return new RequestQueryParam(req, name, sb.toString()).parser(parser); 371 } 372 373 /** 374 * Returns all the parameters with the specified name. 375 * 376 * @param name The parameter name. 377 * @return The list of all parameters with the specified name, or an empty list if none are found. 378 * <br>List is unmodifiable. 379 */ 380 public List<RequestQueryParam> getAll(String name) { 381 return stream(name).toList(); 382 } 383 384 /** 385 * Returns the first parameter with the specified name. 386 * 387 * <p> 388 * Note that this method never returns <jk>null</jk> and that {@link RequestQueryParam#isPresent()} can be used 389 * to test for the existence of the parameter. 390 * 391 * @param name The parameter name. 392 * @return The parameter. Never <jk>null</jk>. 393 */ 394 public RequestQueryParam getFirst(String name) { 395 reqnn("name", name); 396 return stream(name).findFirst().orElseGet(() -> new RequestQueryParam(req, name, null).parser(parser)); 397 } 398 399 /** 400 * Returns the last parameter with the specified name. 401 * 402 * <p> 403 * Note that this method never returns <jk>null</jk> and that {@link RequestQueryParam#isPresent()} can be used 404 * to test for the existence of the parameter. 405 * 406 * @param name The parameter name. 407 * @return The parameter. Never <jk>null</jk>. 408 */ 409 public RequestQueryParam getLast(String name) { 410 reqnn("name", name); 411 var v = Holder.<RequestQueryParam>empty(); 412 stream(name).forEach(v::set); 413 return v.orElseGet(() -> new RequestQueryParam(req, name, null).parser(parser)); 414 } 415 416 /** 417 * Returns all the unique header names in this list. 418 * @return The list of all unique header names in this list. 419 * <br>List is unmodifiable. 420 */ 421 public List<String> getNames() { return stream().map(RequestQueryParam::getName).map(x -> caseSensitive ? x : lcr(x)).distinct().toList(); } 422 423 /** 424 * Returns all headers in sorted order. 425 * 426 * @return The stream of all headers in sorted order. 427 */ 428 public Stream<RequestQueryParam> getSorted() { 429 Comparator<RequestQueryParam> x; 430 if (caseSensitive) 431 x = Comparator.comparing(RequestQueryParam::getName); 432 else 433 x = (x1, x2) -> String.CASE_INSENSITIVE_ORDER.compare(x1.getName(), x2.getName()); 434 return stream().sorted(x); 435 } 436 437 /** 438 * Sets the parser to use for part values. 439 * 440 * @param value The new value for this setting. 441 * @return This object. 442 */ 443 public RequestQueryParamList parser(HttpPartParserSession value) { 444 parser = value; 445 forEach(x -> x.parser(parser)); 446 return this; 447 } 448 449 /** 450 * Remove parameters. 451 * 452 * @param name The parameter names. Must not be <jk>null</jk>. 453 * @return This object. 454 */ 455 public RequestQueryParamList remove(String name) { 456 reqnn("name", name); 457 removeIf(x -> eq(x.getName(), name)); 458 return this; 459 } 460 461 /** 462 * Sets request header values. 463 * 464 * <p> 465 * Parameters are added to the end of the headers. 466 * <br>Any previous parameters with the same name are removed. 467 * 468 * @param parameters The parameters to set. Must not be <jk>null</jk> or contain <jk>null</jk>. 469 * @return This object. 470 */ 471 public RequestQueryParamList set(HttpPart...parameters) { 472 reqnn("headers", parameters); 473 for (var p : parameters) 474 remove(p.getName()); 475 for (var p : parameters) 476 add(p); 477 return this; 478 } 479 480 /** 481 * Sets a parameter value. 482 * 483 * <p> 484 * Parameter is added to the end. 485 * <br>Any previous parameters with the same name are removed. 486 * 487 * @param name The parameter name. Must not be <jk>null</jk>. 488 * @param value 489 * The parameter value. 490 * <br>Converted to a string using {@link Object#toString()}. 491 * <br>Can be <jk>null</jk> (the parameter is set with a <jk>null</jk> value). 492 * @return This object. 493 */ 494 public RequestQueryParamList set(String name, Object value) { 495 reqnn("name", name); 496 set(new RequestQueryParam(req, name, s(value)).parser(parser)); 497 return this; 498 } 499 500 /** 501 * Returns all headers with the specified name. 502 * 503 * @param name The header name. 504 * @return The stream of all headers with matching names. Never <jk>null</jk>. 505 */ 506 public Stream<RequestQueryParam> stream(String name) { 507 return stream().filter(x -> eq(x.getName(), name)); 508 } 509 510 /** 511 * Returns a copy of this object but only with the specified param names copied. 512 * 513 * @param names The list to include in the copy. 514 * @return A new list object. 515 */ 516 public RequestQueryParamList subset(String...names) { 517 return new RequestQueryParamList(this, names); 518 } 519 520 protected FluentMap<String,Object> properties() { 521 // @formatter:off 522 var m = filteredBeanPropertyMap(); 523 for (var n : getNames()) 524 m.a(n, get(n).asString().orElse(null)); 525 return m; 526 // @formatter:on 527 } 528 529 @Override 530 public boolean equals(Object o) { 531 return this == o || (o instanceof RequestQueryParamList o2 && super.equals(o2)); 532 } 533 534 @Override 535 public int hashCode() { 536 return super.hashCode(); 537 } 538 539 @Override /* Overridden from Object */ 540 public String toString() { 541 return r(properties()); 542 } 543 544 private boolean eq(String s1, String s2) { 545 return Shorts.eq(! caseSensitive, s1, s2); // NOAI 546 } 547}