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