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