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