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