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