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; 014 015import static org.apache.juneau.internal.ArrayUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.lang.reflect.*; 019import java.lang.reflect.Type; 020import java.util.*; 021 022import javax.servlet.http.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.collections.*; 026import org.apache.juneau.http.annotation.*; 027import org.apache.juneau.httppart.*; 028import org.apache.juneau.internal.*; 029import org.apache.juneau.json.*; 030import org.apache.juneau.oapi.*; 031import org.apache.juneau.parser.*; 032import org.apache.juneau.http.exception.*; 033 034/** 035 * Represents the parsed form-data parameters in an HTTP request. 036 * 037 * <p> 038 * Similar in functionality to the {@link HttpServletRequest#getParameter(String)} except only looks in the body of the request, not parameters from 039 * the URL query string. 040 * <br>This can be useful in cases where you're using GET parameters on FORM POSTs, and you don't want the body of the request to be read. 041 * 042 * <p> 043 * Use of this object is incompatible with using any other methods that access the body of the request (since this object will 044 * consume the body). 045 * <br>Some examples: 046 * <ul> 047 * <li class='jm'>{@link RestRequest#getBody()} 048 * <li class='jm'>{@link RestRequest#getReader()} 049 * <li class='jm'>{@link RestRequest#getInputStream()} 050 * <li class='ja'>{@link FormData} 051 * </ul> 052 * 053 * <ul class='seealso'> 054 * <li class='link'>{@doc RestmRequestFormData} 055 * </ul> 056 */ 057@SuppressWarnings("unchecked") 058public class RequestFormData extends LinkedHashMap<String,String[]> { 059 private static final long serialVersionUID = 1L; 060 061 private final RestRequest req; 062 private final HttpPartParserSession parser; 063 064 RequestFormData(RestRequest req, HttpPartParserSession parser) { 065 this.req = req; 066 this.parser = parser; 067 } 068 069 /** 070 * Adds default entries to these form-data parameters. 071 * 072 * <p> 073 * This includes the default form-data parameters defined on the resource and method levels. 074 * 075 * @param defaultEntries 076 * The default entries. 077 * <br>Can be <jk>null</jk>. 078 * @return This object (for method chaining). 079 */ 080 public RequestFormData addDefault(Map<String,Object> defaultEntries) { 081 if (defaultEntries != null) { 082 for (Map.Entry<String,Object> e : defaultEntries.entrySet()) { 083 String key = e.getKey(); 084 Object value = e.getValue(); 085 String[] v = get(key); 086 if (v == null || v.length == 0 || StringUtils.isEmpty(v[0])) 087 put(key, stringifyAll(value)); 088 } 089 } 090 return this; 091 } 092 093 /** 094 * Adds a default entries to these form-data parameters. 095 * 096 * <p> 097 * Similar to {@link #put(String, Object)} but doesn't override existing values. 098 * 099 * @param name 100 * The form-data parameter name. 101 * @param value 102 * The form-data parameter value. 103 * <br>Converted to a String using <c>toString()</c>. 104 * <br>Ignored if value is <jk>null</jk> or blank. 105 * @return This object (for method chaining). 106 */ 107 public RequestFormData addDefault(String name, Object value) { 108 return addDefault(Collections.singletonMap(name, value)); 109 } 110 111 /** 112 * Sets a request form-data parameter value. 113 * 114 * @param name The parameter name. 115 * @param value The parameter value. 116 */ 117 public void put(String name, Object value) { 118 super.put(name, stringifyAll(value)); 119 } 120 121 /** 122 * Returns a form-data parameter value. 123 * 124 * <ul class='notes'> 125 * <li> 126 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 127 * <li> 128 * This method returns the raw unparsed value, and differs from calling 129 * <code>get(name, String.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 130 * </ul> 131 * 132 * @param name The form-data parameter name. 133 * @return The parameter value, or <jk>null</jk> if parameter does not exist. 134 */ 135 public String getString(String name) { 136 String[] v = get(name); 137 if (v == null || v.length == 0) 138 return null; 139 140 // Fix for behavior difference between Tomcat and WAS. 141 // getParameter("foo") on "&foo" in Tomcat returns "". 142 // getParameter("foo") on "&foo" in WAS returns null. 143 if (v.length == 1 && v[0] == null) 144 return ""; 145 146 return v[0]; 147 } 148 149 /** 150 * Returns a form-data parameter value. 151 * 152 * <ul class='notes'> 153 * <li> 154 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 155 * <li> 156 * This method returns the raw unparsed value, and differs from calling 157 * <code>get(name, String.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 158 * </ul> 159 * 160 * @param name The form-data parameter name. 161 * @param def The default value. 162 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 163 */ 164 public String getString(String name, String def) { 165 String s = getString(name); 166 return StringUtils.isEmpty(s) ? def : s; 167 } 168 169 /** 170 * Returns a form-data parameter value as an integer. 171 * 172 * <ul class='notes'> 173 * <li> 174 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 175 * <li> 176 * This method returns the raw unparsed value, and differs from calling 177 * <code>get(name, Integer.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 178 * </ul> 179 * 180 * @param name The form-data parameter name. 181 * @return The parameter value, or <c>0</c> if parameter does not exist or is <jk>null</jk> or empty. 182 */ 183 public int getInt(String name) { 184 return getInt(name, 0); 185 } 186 187 /** 188 * Returns a form-data parameter value as an integer. 189 * 190 * <ul class='notes'> 191 * <li> 192 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 193 * <li> 194 * This method returns the raw unparsed value, and differs from calling 195 * <code>get(name, Integer.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 196 * </ul> 197 * 198 * @param name The form-data parameter name. 199 * @param def The default value. 200 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 201 */ 202 public int getInt(String name, int def) { 203 String s = getString(name); 204 return StringUtils.isEmpty(s) ? def : Integer.parseInt(s); 205 } 206 207 /** 208 * Returns a form-data parameter value as a boolean. 209 * 210 * <ul class='notes'> 211 * <li> 212 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 213 * <li> 214 * This method returns the raw unparsed value, and differs from calling 215 * <code>get(name, Boolean.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 216 * </ul> 217 * 218 * @param name The form-data parameter name. 219 * @return The parameter value, or <jk>false</jk> if parameter does not exist or is <jk>null</jk> or empty. 220 */ 221 public boolean getBoolean(String name) { 222 return getBoolean(name, false); 223 } 224 225 /** 226 * Returns a form-data parameter value as a boolean. 227 * 228 * <ul class='notes'> 229 * <li> 230 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 231 * <li> 232 * This method returns the raw unparsed value, and differs from calling 233 * <code>get(name, Boolean.<jk>class</jk>)</code> which uses the {@link HttpPartParser} for parsing the value. 234 * </ul> 235 * 236 * @param name The form-data parameter name. 237 * @param def The default value. 238 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 239 */ 240 public boolean getBoolean(String name, boolean def) { 241 String s = getString(name); 242 return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s); 243 } 244 245 /** 246 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 247 * 248 * <h5 class='section'>Examples:</h5> 249 * <p class='bcode w800'> 250 * <jc>// Parse into an integer.</jc> 251 * <jk>int</jk> myparam = formData.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); 252 * 253 * <jc>// Parse into an int array.</jc> 254 * <jk>int</jk>[] myparam = formData.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 255 256 * <jc>// Parse into a bean.</jc> 257 * MyBean myparam = formData.get(<js>"myparam"</js>, MyBean.<jk>class</jk>); 258 * 259 * <jc>// Parse into a linked-list of objects.</jc> 260 * List myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>); 261 * 262 * <jc>// Parse into a map of object keys/values.</jc> 263 * Map myparam = formData.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>); 264 * </p> 265 * 266 * <ul class='seealso'> 267 * <li class='jf'>{@link RestContext#REST_partParser} 268 * </ul> 269 * 270 * @param name The parameter name. 271 * @param type The class type to convert the parameter value to. 272 * @param <T> The class type to convert the parameter value to. 273 * @return The parameter value converted to the specified class type. 274 * @throws BadRequest Thrown if input could not be parsed. 275 * @throws InternalServerError Thrown if any other exception occurs. 276 */ 277 public <T> T get(String name, Class<T> type) throws BadRequest, InternalServerError { 278 return getInner(null, null, name, null, getClassMeta(type)); 279 } 280 281 /** 282 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 283 * 284 * <h5 class='section'>Examples:</h5> 285 * <p class='bcode w800'> 286 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 287 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 288 * .items( 289 * HttpPartSchema.<jsm>create</jsm>() 290 * .collectionFormat(<js>"pipes"</js>) 291 * .items( 292 * HttpPartSchema.<jsm>create</jsm>() 293 * .collectionFormat(<js>"csv"</js>) 294 * .type(<js>"integer"</js>) 295 * .format(<js>"int64"</js>) 296 * .minimum(<js>"0"</js>) 297 * .maximum(<js>"100"</js>) 298 * .minLength(1) 299 * .maxLength=(10) 300 * ) 301 * ) 302 * .build(); 303 * 304 * <jc>// Parse into a 2d long array.</jc> 305 * <jk>long</jk>[][] myparams = formData.get(schema, <js>"myparam"</js>, <jk>long</jk>[][].<jk>class</jk>); 306 * </p> 307 * 308 * <ul class='seealso'> 309 * <li class='jf'>{@link RestContext#REST_partParser} 310 * </ul> 311 * 312 * @param schema 313 * The schema object that defines the format of the input. 314 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 315 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 316 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 317 * @param name The parameter name. 318 * @param type The class type to convert the parameter value to. 319 * @param <T> The class type to convert the parameter value to. 320 * @return The parameter value converted to the specified class type. 321 * @throws BadRequest Thrown if input could not be parsed. 322 * @throws InternalServerError Thrown if any other exception occurs. 323 */ 324 public <T> T get(HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError { 325 return getInner(null, schema, name, null, getClassMeta(type)); 326 } 327 328 /** 329 * Returns the specified form-data parameter value converted to a POJO using the specified {@link HttpPartParser}. 330 * 331 * <h5 class='section'>Examples:</h5> 332 * <p class='bcode w800'> 333 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 334 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 335 * .items( 336 * HttpPartSchema.<jsm>create</jsm>() 337 * .collectionFormat(<js>"pipes"</js>) 338 * .items( 339 * HttpPartSchema.<jsm>create</jsm>() 340 * .collectionFormat(<js>"csv"</js>) 341 * .type(<js>"integer"</js>) 342 * .format(<js>"int64"</js>) 343 * .minimum(<js>"0"</js>) 344 * .maximum(<js>"100"</js>) 345 * .minLength(1) 346 * .maxLength=(10) 347 * ) 348 * ) 349 * .build(); 350 * 351 * HttpPartParserSession parser = OpenApiParser.<jsf>DEFAULT</jsf>.createSession(); 352 * 353 * <jc>// Parse into a 2d long array.</jc> 354 * <jk>long</jk>[][] myparams = formData.get(parser, schema, <js>"myparam"</js>, <jk>long</jk>[][].<jk>class</jk>); 355 * </p> 356 * 357 * <ul class='seealso'> 358 * <li class='jf'>{@link RestContext#REST_partParser} 359 * </ul> 360 * 361 * @param parser 362 * The parser to use for parsing the string value. 363 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 364 * @param schema 365 * The schema object that defines the format of the input. 366 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 367 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 368 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 369 * @param name The parameter name. 370 * @param type The class type to convert the parameter value to. 371 * @param <T> The class type to convert the parameter value to. 372 * @return The parameter value converted to the specified class type. 373 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 374 * @throws InternalServerError Thrown if any other exception occurs. 375 */ 376 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError { 377 return getInner(parser, schema, name, null, getClassMeta(type)); 378 } 379 380 /** 381 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 382 * 383 * <h5 class='section'>Examples:</h5> 384 * <p class='bcode w800'> 385 * <jc>// Parse into an integer.</jc> 386 * <jk>int</jk> myparam = formData.get(<js>"myparam"</js>, -1, <jk>int</jk>.<jk>class</jk>); 387 * 388 * <jc>// Parse into an int array.</jc> 389 * <jk>int</jk>[] myparam = formData.get(<js>"myparam"</js>, <jk>new int</jk>[0], <jk>int</jk>[].<jk>class</jk>); 390 391 * <jc>// Parse into a bean.</jc> 392 * MyBean myparam = formData.get(<js>"myparam"</js>, <jk>new</jk> MyBean(), MyBean.<jk>class</jk>); 393 * 394 * <jc>// Parse into a linked-list of objects.</jc> 395 * List myparam = formData.get(<js>"myparam"</js>, Collections.<jsm>emptyList</jsm>(), LinkedList.<jk>class</jk>); 396 * 397 * <jc>// Parse into a map of object keys/values.</jc> 398 * Map myparam = formData.get(<js>"myparam"</js>, Collections.<jsm>emptyMap</jsm>(), TreeMap.<jk>class</jk>); 399 * </p> 400 * 401 * <ul class='seealso'> 402 * <li class='jf'>{@link RestContext#REST_partParser} 403 * </ul> 404 * 405 * @param name The parameter name. 406 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 407 * @param type The class type to convert the parameter value to. 408 * @param <T> The class type to convert the parameter value to. 409 * @return The parameter value converted to the specified class type. 410 * @throws BadRequest Thrown if input could not be parsed. 411 * @throws InternalServerError Thrown if any other exception occurs. 412 */ 413 public <T> T get(String name, T def, Class<T> type) throws BadRequest, InternalServerError { 414 return getInner(null, null, name, def, getClassMeta(type)); 415 } 416 417 /** 418 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 419 * 420 * <h5 class='section'>Examples:</h5> 421 * <p class='bcode w800'> 422 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 423 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 424 * .items( 425 * HttpPartSchema.<jsm>create</jsm>() 426 * .collectionFormat(<js>"pipes"</js>) 427 * .items( 428 * HttpPartSchema.<jsm>create</jsm>() 429 * .collectionFormat(<js>"csv"</js>) 430 * .type(<js>"integer"</js>) 431 * .format(<js>"int64"</js>) 432 * .minimum(<js>"0"</js>) 433 * .maximum(<js>"100"</js>) 434 * .minLength(1) 435 * .maxLength=(10) 436 * ) 437 * ) 438 * .build(); 439 * 440 * <jc>// Parse into a 2d long array.</jc> 441 * <jk>long</jk>[][] myparams = formData.get(schema, <js>"myparam"</js>, <jk>new long</jk>[][0], <jk>long</jk>[][].<jk>class</jk>); 442 * </p> 443 * 444 * <ul class='seealso'> 445 * <li class='jf'>{@link RestContext#REST_partParser} 446 * </ul> 447 * 448 * @param schema 449 * The schema object that defines the format of the input. 450 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 451 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 452 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 453 * @param name The parameter name. 454 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 455 * @param type The class type to convert the parameter value to. 456 * @param <T> The class type to convert the parameter value to. 457 * @return The parameter value converted to the specified class type. 458 * @throws BadRequest Thrown if input could not be parsed. 459 * @throws InternalServerError Thrown if any other exception occurs. 460 */ 461 public <T> T get(HttpPartSchema schema, String name, T def, Class<T> type) throws BadRequest, InternalServerError { 462 return getInner(null, schema, name, def, getClassMeta(type)); 463 } 464 465 /** 466 * Returns the specified form-data parameter value converted to a POJO using the specified {@link HttpPartParser}. 467 * 468 * <h5 class='section'>Examples:</h5> 469 * <p class='bcode w800'> 470 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 471 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 472 * .items( 473 * HttpPartSchema.<jsm>create</jsm>() 474 * .collectionFormat(<js>"pipes"</js>) 475 * .items( 476 * HttpPartSchema.<jsm>create</jsm>() 477 * .collectionFormat(<js>"csv"</js>) 478 * .type(<js>"integer"</js>) 479 * .format(<js>"int64"</js>) 480 * .minimum(<js>"0"</js>) 481 * .maximum(<js>"100"</js>) 482 * .minLength(1) 483 * .maxLength=(10) 484 * ) 485 * ) 486 * .build(); 487 * 488 * HttpPartParserSession parser = OpenApiParser.<jsf>DEFAULT</jsf>.createSession(); 489 * 490 * <jc>// Parse into a 2d long array.</jc> 491 * <jk>long</jk>[][] myparams = formData.get(parser, schema, <js>"myparam"</js>, <jk>new long</jk>[][0], <jk>long</jk>[][].<jk>class</jk>); 492 * </p> 493 * 494 * <ul class='seealso'> 495 * <li class='jf'>{@link RestContext#REST_partParser} 496 * </ul> 497 * 498 * @param parser 499 * The parser to use for parsing the string value. 500 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 501 * @param schema 502 * The schema object that defines the format of the input. 503 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 504 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 505 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 506 * @param name The parameter name. 507 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 508 * @param type The class type to convert the parameter value to. 509 * @param <T> The class type to convert the parameter value to. 510 * @return The parameter value converted to the specified class type. 511 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 512 * @throws InternalServerError Thrown if any other exception occurs. 513 */ 514 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, T def, Class<T> type) throws BadRequest, InternalServerError { 515 return getInner(parser, schema, name, def, getClassMeta(type)); 516 } 517 518 /** 519 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 520 * 521 * <p> 522 * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created. 523 * 524 * <h5 class='section'>Examples:</h5> 525 * <p class='bcode w800'> 526 * <jc>// Parse into a linked-list of strings.</jc> 527 * List<String> myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 528 * 529 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 530 * List<List<String>> myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 531 * 532 * <jc>// Parse into a map of string keys/values.</jc> 533 * Map<String,String> myparam = formData.getr(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 534 * 535 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 536 * Map<String,List<MyBean>> myparam = formData.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 537 * </p> 538 * 539 * <ul class='notes'> 540 * <li> 541 * <c>Collections</c> must be followed by zero or one parameter representing the value type. 542 * <li> 543 * <c>Maps</c> must be followed by zero or two parameters representing the key and value types. 544 * </ul> 545 * 546 * <ul class='seealso'> 547 * <li class='jf'>{@link RestContext#REST_partParser} 548 * </ul> 549 * 550 * @param name The parameter name. 551 * @param type 552 * The type of object to create. 553 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 554 * @param args 555 * The type arguments of the class if it's a collection or map. 556 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 557 * <br>Ignored if the main type is not a map or collection. 558 * @return The parameter value converted to the specified class type. 559 * @throws BadRequest Thrown if input could not be parsed. 560 * @throws InternalServerError Thrown if any other exception occurs. 561 */ 562 public <T> T get(String name, Type type, Type...args) throws BadRequest, InternalServerError { 563 return getInner(null, null, name, null, this.<T>getClassMeta(type, args)); 564 } 565 566 /** 567 * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser. 568 * 569 * @param parser 570 * The parser to use for parsing the string value. 571 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 572 * @param schema 573 * The schema object that defines the format of the input. 574 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 575 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 576 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 577 * @param name The parameter name. 578 * @param type 579 * The type of object to create. 580 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 581 * @param args 582 * The type arguments of the class if it's a collection or map. 583 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 584 * <br>Ignored if the main type is not a map or collection. 585 * @return The parameter value converted to the specified class type. 586 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 587 * @throws InternalServerError Thrown if any other exception occurs. 588 */ 589 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError { 590 return getInner(parser, schema, name, null, this.<T>getClassMeta(type, args)); 591 } 592 593 /** 594 * Same as {@link #get(String, Class)} except returns a default value if not found. 595 * 596 * @param name The parameter name. 597 * @param type 598 * The type of object to create. 599 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 600 * @param args 601 * The type arguments of the class if it's a collection or map. 602 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 603 * <br>Ignored if the main type is not a map or collection. 604 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 605 * @param <T> The class type to convert the parameter value to. 606 * @return The parameter value converted to the specified class type. 607 * @throws BadRequest Thrown if input could not be parsed. 608 * @throws InternalServerError Thrown if any other exception occurs. 609 */ 610 public <T> T get(String name, T def, Type type, Type...args) throws BadRequest, InternalServerError { 611 return getInner(null, null, name, def, this.<T>getClassMeta(type, args)); 612 } 613 614 /** 615 * Same as {@link #get(String, Object, Type, Type...)} but allows you to override the part parser. 616 * 617 * @param parser 618 * The parser to use for parsing the string value. 619 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 620 * @param schema 621 * The schema object that defines the format of the input. 622 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 623 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 624 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 625 * @param name The parameter name. 626 * @param type 627 * The type of object to create. 628 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 629 * @param args 630 * The type arguments of the class if it's a collection or map. 631 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 632 * <br>Ignored if the main type is not a map or collection. 633 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 634 * @param <T> The class type to convert the parameter value to. 635 * @return The parameter value converted to the specified class type. 636 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 637 * @throws InternalServerError Thrown if any other exception occurs. 638 */ 639 public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, String name, T def, Type type, Type...args) throws BadRequest, InternalServerError { 640 return getInner(parser, schema, name, def, this.<T>getClassMeta(type, args)); 641 } 642 643 /** 644 * Returns the specified form-data parameter values converted POJO using the {@link HttpPartParser} registered with the resource. 645 * 646 * <p> 647 * Meant to be used on multi-part parameters (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 648 * 649 * <p> 650 * This method must only be called when parsing into classes of type Collection or array. 651 * 652 * <h5 class='section'>Examples:</h5> 653 * <p class='bcode w800'> 654 * <jc>// Parse into multiple integers.</jc> 655 * <jk>int</jk>[] myparam = formData.getAll(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 656 * 657 * <jc>// Parse into multiple int arrays.</jc> 658 * <jk>int</jk>[][] myparam = formData.getAll(<js>"myparam"</js>, <jk>int</jk>[][].<jk>class</jk>); 659 660 * <jc>// Parse into multiple beans.</jc> 661 * MyBean[] myparam = formData.getAll(<js>"myparam"</js>, MyBean[].<jk>class</jk>); 662 * 663 * <jc>// Parse into multiple linked-lists of objects.</jc> 664 * List[] myparam = formData.getAll(<js>"myparam"</js>, LinkedList[].<jk>class</jk>); 665 * 666 * <jc>// Parse into multiple maps of object keys/values.</jc> 667 * Map[] myparam = formData.getAll(<js>"myparam"</js>, TreeMap[].<jk>class</jk>); 668 * </p> 669 * 670 * <ul class='seealso'> 671 * <li class='jf'>{@link RestContext#REST_partParser} 672 * </ul> 673 * 674 * @param name The parameter name. 675 * @param type The class type to convert the parameter value to. 676 * @return The parameter value converted to the specified class type. 677 * @throws BadRequest Thrown if input could not be parsed. 678 * @throws InternalServerError Thrown if any other exception occurs. 679 */ 680 public <T> T getAll(String name, Class<T> type) throws BadRequest, InternalServerError { 681 return getAllInner(null, null, name, getClassMeta(type)); 682 } 683 684 /** 685 * Returns the specified form-data parameter values converted to POJOs using the {@link HttpPartParser} registered with the resource. 686 * 687 * <p> 688 * Meant to be used on multi-part parameters (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 689 * 690 * <h5 class='section'>Examples:</h5> 691 * <p class='bcode w800'> 692 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 693 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 694 * .items( 695 * HttpPartSchema.<jsm>create</jsm>() 696 * .collectionFormat(<js>"pipes"</js>) 697 * .items( 698 * HttpPartSchema.<jsm>create</jsm>() 699 * .collectionFormat(<js>"csv"</js>) 700 * .type(<js>"integer"</js>) 701 * .format(<js>"int64"</js>) 702 * .minimum(<js>"0"</js>) 703 * .maximum(<js>"100"</js>) 704 * .minLength(1) 705 * .maxLength=(10) 706 * ) 707 * ) 708 * .build(); 709 * 710 * <jc>// Parse into multiple 2d long arrays.</jc> 711 * <jk>long</jk>[][][] myparams = formData.getAll(schema, <js>"myparam"</js>, <jk>long</jk>[][][].<jk>class</jk>); 712 * </p> 713 * 714 * <ul class='seealso'> 715 * <li class='jf'>{@link RestContext#REST_partParser} 716 * </ul> 717 * 718 * @param schema 719 * The schema object that defines the format of the input. 720 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 721 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 722 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 723 * @param name The parameter name. 724 * @param type The class type to convert the parameter value to. 725 * @return The parameter value converted to the specified class type. 726 * @throws BadRequest Thrown if input could not be parsed. 727 * @throws InternalServerError Thrown if any other exception occurs. 728 */ 729 public <T> T getAll(HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError { 730 return getAllInner(null, schema, name, getClassMeta(type)); 731 } 732 733 /** 734 * Returns the specified form-data parameter values converted to POJOs using the specified {@link HttpPartParser}. 735 * 736 * <p> 737 * Meant to be used on multi-part parameters (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 738 * 739 * <h5 class='section'>Examples:</h5> 740 * <p class='bcode w800'> 741 * <jc>// Pipe-delimited list of comma-delimited numbers</jc> 742 * HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>() 743 * .items( 744 * HttpPartSchema.<jsm>create</jsm>() 745 * .collectionFormat(<js>"pipes"</js>) 746 * .items( 747 * HttpPartSchema.<jsm>create</jsm>() 748 * .collectionFormat(<js>"csv"</js>) 749 * .type(<js>"integer"</js>) 750 * .format(<js>"int64"</js>) 751 * .minimum(<js>"0"</js>) 752 * .maximum(<js>"100"</js>) 753 * .minLength(1) 754 * .maxLength=(10) 755 * ) 756 * ) 757 * .build(); 758 * 759 * <jc>// Parse into multiple 2d long arrays.</jc> 760 * <jk>long</jk>[][][] myparams = formData.getAll(schema, <js>"myparam"</js>, <jk>long</jk>[][][].<jk>class</jk>); 761 * </p> 762 * 763 * <ul class='seealso'> 764 * <li class='jf'>{@link RestContext#REST_partParser} 765 * </ul> 766 * 767 * @param parser 768 * The parser to use for parsing the string value. 769 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 770 * @param schema 771 * The schema object that defines the format of the input. 772 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 773 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 774 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 775 * @param name The parameter name. 776 * @param type The class type to convert the parameter value to. 777 * @return The parameter value converted to the specified class type. 778 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 779 * @throws InternalServerError Thrown if any other exception occurs. 780 */ 781 public <T> T getAll(HttpPartParserSession parser, HttpPartSchema schema, String name, Class<T> type) throws BadRequest, InternalServerError { 782 return getAllInner(parser, schema, name, getClassMeta(type)); 783 } 784 785 /** 786 * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters 787 * (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 788 * 789 * <p> 790 * This method must only be called when parsing into classes of type Collection or array. 791 * 792 * @param name The parameter name. 793 * @param type 794 * The type of object to create. 795 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 796 * @param args 797 * The type arguments of the class if it's a collection or map. 798 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 799 * <br>Ignored if the main type is not a map or collection. 800 * @return The parameter value converted to the specified class type. 801 * @throws BadRequest Thrown if input could not be parsed. 802 * @throws InternalServerError Thrown if any other exception occurs. 803 */ 804 public <T> T getAll(String name, Type type, Type...args) throws BadRequest, InternalServerError { 805 return getAllInner(null, null, name, this.<T>getClassMeta(type, args)); 806 } 807 808 /** 809 * Same as {@link #getAll(String, Type, Type...)} but allows you to override the part parser. 810 * 811 * @param parser 812 * The parser to use for parsing the string value. 813 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 814 * @param schema 815 * The schema object that defines the format of the input. 816 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 817 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 818 * <br>Only used if parser is schema-aware (e.g. {@link OpenApiParser}). 819 * @param name The parameter name. 820 * @param type 821 * The type of object to create. 822 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 823 * @param args 824 * The type arguments of the class if it's a collection or map. 825 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 826 * <br>Ignored if the main type is not a map or collection. 827 * @return The parameter value converted to the specified class type. 828 * @throws BadRequest Thrown if input could not be parsed or fails schema validation. 829 * @throws InternalServerError Thrown if any other exception occurs. 830 */ 831 public <T> T getAll(HttpPartParserSession parser, HttpPartSchema schema, String name, Type type, Type...args) throws BadRequest, InternalServerError { 832 return getAllInner(parser, schema, name, this.<T>getClassMeta(type, args)); 833 } 834 835 /* Workhorse method */ 836 private <T> T getInner(HttpPartParserSession parser, HttpPartSchema schema, String name, T def, ClassMeta<T> cm) throws BadRequest, InternalServerError { 837 if (parser == null) 838 parser = req.getPartParser(); 839 try { 840 if (cm.isMapOrBean() && isOneOf(name, "*", "")) { 841 OMap m = new OMap(); 842 for (Map.Entry<String,String[]> e : this.entrySet()) { 843 String k = e.getKey(); 844 HttpPartSchema pschema = schema == null ? null : schema.getProperty(k); 845 ClassMeta<?> cm2 = cm.getValueType(); 846 if (cm.getValueType().isCollectionOrArray()) 847 m.put(k, getAllInner(parser, pschema, k, cm2)); 848 else 849 m.put(k, getInner(parser, pschema, k, null, cm2)); 850 } 851 return req.getBeanSession().convertToType(m, cm); 852 } 853 T t = parse(parser, schema, getString(name), cm); 854 return (t == null ? def : t); 855 } catch (SchemaValidationException e) { 856 throw new BadRequest(e, "Validation failed on form-data parameter ''{0}''. ", name); 857 } catch (ParseException e) { 858 throw new BadRequest(e, "Could not parse form-data parameter ''{0}''.", name) ; 859 } catch (Exception e) { 860 throw new InternalServerError(e, "Could not parse form-data parameter ''{0}''.", name) ; 861 } 862 } 863 864 /* Workhorse method */ 865 @SuppressWarnings("rawtypes") 866 <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema schema, String name, ClassMeta<T> cm) throws BadRequest, InternalServerError { 867 String[] p = get(name); 868 if (schema == null) 869 schema = HttpPartSchema.DEFAULT; 870 try { 871 if (cm.isArray()) { 872 List c = new ArrayList(); 873 for (int i = 0; i < p.length; i++) 874 c.add(parse(parser, schema.getItems(), p[i], cm.getElementType())); 875 return (T)toArray(c, cm.getElementType().getInnerClass()); 876 } else if (cm.isCollection()) { 877 Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new OList()); 878 for (int i = 0; i < p.length; i++) 879 c.add(parse(parser, schema.getItems(), p[i], cm.getElementType())); 880 return (T)c; 881 } 882 } catch (SchemaValidationException e) { 883 throw new BadRequest(e, "Validation failed on form-data parameter ''{0}''. ", name); 884 } catch (ParseException e) { 885 throw new BadRequest(e, "Could not parse form-data parameter ''{0}''.", name) ; 886 } catch (Exception e) { 887 throw new InternalServerError(e, "Could not parse form-data parameter ''{0}''.", name) ; 888 } 889 throw new InternalServerError("Invalid call to getAll(String, ClassMeta). Class type must be a Collection or array."); 890 } 891 892 private <T> T parse(HttpPartParserSession parser, HttpPartSchema schema, String val, ClassMeta<T> c) throws SchemaValidationException, ParseException { 893 if (parser == null) 894 parser = this.parser; 895 return parser.parse(HttpPartType.FORMDATA, schema, val, c); 896 } 897 898 /** 899 * Converts the form-data parameters to a readable string. 900 * 901 * @param sorted Sort the form-data parameters by name. 902 * @return A JSON string containing the contents of the form-data parameters. 903 */ 904 public String toString(boolean sorted) { 905 Map<String,Object> m = null; 906 if (sorted) 907 m = new TreeMap<>(); 908 else 909 m = new LinkedHashMap<>(); 910 for (Map.Entry<String,String[]> e : this.entrySet()) { 911 String[] v = e.getValue(); 912 if (v != null) 913 m.put(e.getKey(), v.length == 1 ? v[0] : v); 914 } 915 return SimpleJsonSerializer.DEFAULT.toString(m); 916 } 917 918 /** 919 * Converts this object to a query string. 920 * 921 * <p> 922 * Returned query string does not start with <js>'?'</js>. 923 * 924 * @return A new query string, or an empty string if this object is empty. 925 */ 926 public String asQueryString() { 927 StringBuilder sb = new StringBuilder(); 928 for (Map.Entry<String,String[]> e : this.entrySet()) { 929 for (int i = 0; i < e.getValue().length; i++) { 930 if (sb.length() > 0) 931 sb.append("&"); 932 sb.append(urlEncode(e.getKey())).append('=').append(urlEncode(e.getValue()[i])); 933 } 934 } 935 return sb.toString(); 936 } 937 938 939 @Override /* Object */ 940 public String toString() { 941 return toString(false); 942 } 943 944 //----------------------------------------------------------------------------------------------------------------- 945 // Helper methods 946 //----------------------------------------------------------------------------------------------------------------- 947 948 private <T> ClassMeta<T> getClassMeta(Type type, Type...args) { 949 return req.getBeanSession().getClassMeta(type, args); 950 } 951 952 private <T> ClassMeta<T> getClassMeta(Class<T> type) { 953 return req.getBeanSession().getClassMeta(type); 954 } 955}