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