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.util.*; 020 021import javax.servlet.http.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.httppart.*; 025import org.apache.juneau.internal.*; 026import org.apache.juneau.json.*; 027import org.apache.juneau.parser.*; 028import org.apache.juneau.rest.annotation.*; 029 030/** 031 * Represents the parsed form-data parameters in an HTTP request. 032 * 033 * <p> 034 * Similar in functionality to the {@link HttpServletRequest#getParameter(String)} except only looks in the body of the request, not parameters from 035 * the URL query string. 036 * <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. 037 * 038 * <p> 039 * Use of this object is incompatible with using any other methods that access the body of the request (since this object will 040 * consume the body). 041 * <br>Some examples: 042 * <ul> 043 * <li class='jm'>{@link RestRequest#getBody()} 044 * <li class='jm'>{@link RestRequest#getReader()} 045 * <li class='jm'>{@link RestRequest#getInputStream()} 046 * <li class='ja'>{@link Header} 047 * </ul> 048 * 049 * <h5 class='section'>See Also:</h5> 050 * <ul> 051 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.RequestFormData">Overview > juneau-rest-server > RequestFormData</a> 052 * </ul> 053 */ 054@SuppressWarnings("unchecked") 055public class RequestFormData extends LinkedHashMap<String,String[]> { 056 private static final long serialVersionUID = 1L; 057 058 private HttpPartParser parser; 059 private BeanSession beanSession; 060 061 RequestFormData setParser(HttpPartParser parser) { 062 this.parser = parser; 063 return this; 064 } 065 066 RequestFormData setBeanSession(BeanSession beanSession) { 067 this.beanSession = beanSession; 068 return this; 069 } 070 071 /** 072 * Adds default entries to these form-data parameters. 073 * 074 * <p> 075 * This includes the default form-data parameters defined on the resource and method levels. 076 * 077 * @param defaultEntries 078 * The default entries. 079 * <br>Can be <jk>null</jk>. 080 * @return This object (for method chaining). 081 */ 082 public RequestFormData addDefault(Map<String,Object> defaultEntries) { 083 if (defaultEntries != null) { 084 for (Map.Entry<String,Object> e : defaultEntries.entrySet()) { 085 String key = e.getKey(); 086 Object value = e.getValue(); 087 String[] v = get(key); 088 if (v == null || v.length == 0 || StringUtils.isEmpty(v[0])) 089 put(key, asStrings(value)); 090 } 091 } 092 return this; 093 } 094 095 /** 096 * Adds a default entries to these form-data parameters. 097 * 098 * <p> 099 * Similar to {@link #put(String, Object)} but doesn't override existing values. 100 * 101 * @param name 102 * The form-data parameter name. 103 * @param value 104 * The form-data parameter value. 105 * <br>Converted to a String using <code>toString()</code>. 106 * <br>Ignored if value is <jk>null</jk> or blank. 107 * @return This object (for method chaining). 108 */ 109 public RequestFormData addDefault(String name, Object value) { 110 return addDefault(Collections.singletonMap(name, value)); 111 } 112 113 /** 114 * Sets a request form-data parameter value. 115 * 116 * @param name The parameter name. 117 * @param value The parameter value. 118 */ 119 public void put(String name, Object value) { 120 super.put(name, asStrings(value)); 121 } 122 123 /** 124 * Returns a form-data parameter value. 125 * 126 * <p> 127 * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). 128 * 129 * <h5 class='section'>Notes:</h5> 130 * <ul class='spaced-list'> 131 * <li> 132 * This method returns the raw unparsed value, and differs from calling 133 * <code>get(name, String.<jk>class</js>)</code> which will convert the value from UON 134 * notation: 135 * <ul> 136 * <li><js>"null"</js> => <jk>null</jk> 137 * <li><js>"'null'"</js> => <js>"null"</js> 138 * <li><js>"'foo bar'"</js> => <js>"foo bar"</js> 139 * <li><js>"foo~~bar"</js> => <js>"foo~bar"</js> 140 * </ul> 141 * </ul> 142 * 143 * @param name The form-data parameter name. 144 * @return The parameter value, or <jk>null</jk> if parameter does not exist. 145 */ 146 public String getString(String name) { 147 String[] v = get(name); 148 if (v == null || v.length == 0) 149 return null; 150 151 // Fix for behavior difference between Tomcat and WAS. 152 // getParameter("foo") on "&foo" in Tomcat returns "". 153 // getParameter("foo") on "&foo" in WAS returns null. 154 if (v.length == 1 && v[0] == null) 155 return ""; 156 157 return v[0]; 158 } 159 160 /** 161 * Same as {@link #getString(String)} except returns a default value if <jk>null</jk> or empty. 162 * 163 * @param name The form-data parameter name. 164 * @param def The default value. 165 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 166 */ 167 public String getString(String name, String def) { 168 String s = getString(name); 169 return StringUtils.isEmpty(s) ? def : s; 170 } 171 172 /** 173 * Same as {@link #getString(String)} but converts the value to an integer. 174 * 175 * @param name The form-data parameter name. 176 * @return The parameter value, or <code>0</code> if parameter does not exist or is <jk>null</jk> or empty. 177 */ 178 public int getInt(String name) { 179 return getInt(name, 0); 180 } 181 182 /** 183 * Same as {@link #getString(String,String)} but converts the value to an integer. 184 * 185 * @param name The form-data parameter name. 186 * @param def The default value. 187 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 188 */ 189 public int getInt(String name, int def) { 190 String s = getString(name); 191 return StringUtils.isEmpty(s) ? def : Integer.parseInt(s); 192 } 193 194 /** 195 * Same as {@link #getString(String)} but converts the value to a boolean. 196 * 197 * @param name The form-data parameter name. 198 * @return The parameter value, or <jk>false</jk> if parameter does not exist or is <jk>null</jk> or empty. 199 */ 200 public boolean getBoolean(String name) { 201 return getBoolean(name, false); 202 } 203 204 /** 205 * Same as {@link #getString(String,String)} but converts the value to a boolean. 206 * 207 * @param name The form-data parameter name. 208 * @param def The default value. 209 * @return The parameter value, or the default value if parameter does not exist or is <jk>null</jk> or empty. 210 */ 211 public boolean getBoolean(String name, boolean def) { 212 String s = getString(name); 213 return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s); 214 } 215 216 /** 217 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 218 * 219 * <h5 class='section'>Examples:</h5> 220 * <p class='bcode'> 221 * <jc>// Parse into an integer.</jc> 222 * <jk>int</jk> myparam = formData.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); 223 * 224 * <jc>// Parse into an int array.</jc> 225 * <jk>int</jk>[] myparam = formData.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 226 227 * <jc>// Parse into a bean.</jc> 228 * MyBean myparam = formData.get(<js>"myparam"</js>, MyBean.<jk>class</jk>); 229 * 230 * <jc>// Parse into a linked-list of objects.</jc> 231 * List myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>); 232 * 233 * <jc>// Parse into a map of object keys/values.</jc> 234 * Map myparam = formData.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>); 235 * </p> 236 * 237 * <h5 class='section'>See Also:</h5> 238 * <ul> 239 * <li class='jf'>{@link RestContext#REST_partParser} 240 * </ul> 241 * 242 * @param name The parameter name. 243 * @param type The class type to convert the parameter value to. 244 * @param <T> The class type to convert the parameter value to. 245 * @return The parameter value converted to the specified class type. 246 * @throws ParseException 247 */ 248 public <T> T get(String name, Class<T> type) throws ParseException { 249 return get(null, name, type); 250 } 251 252 /** 253 * Same as {@link #get(String, Object, Class)} but allows you to override the part parser. 254 * 255 * @param parser 256 * The parser to use for parsing the string value. 257 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 258 * @param name The parameter name. 259 * @param type The class type to convert the parameter value to. 260 * @param <T> The class type to convert the parameter value to. 261 * @return The parameter value converted to the specified class type. 262 * @throws ParseException 263 */ 264 public <T> T get(HttpPartParser parser, String name, Class<T> type) throws ParseException { 265 return parse(parser, name, getClassMeta(type)); 266 } 267 268 /** 269 * Same as {@link #get(String, Class)} except returns a default value if not specified. 270 * 271 * @param name The parameter name. 272 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 273 * @param type The class type to convert the parameter value to. 274 * @param <T> The class type to convert the parameter value to. 275 * @return The parameter value converted to the specified class type. 276 * @throws ParseException 277 */ 278 public <T> T get(String name, T def, Class<T> type) throws ParseException { 279 return get(null, name, def, type); 280 } 281 282 /** 283 * Same as {@link #get(String, Object, Class)} but allows you to override the part parser. 284 * 285 * @param parser 286 * The parser to use for parsing the string value. 287 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 288 * @param name The parameter name. 289 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 290 * @param type The class type to convert the parameter value to. 291 * @param <T> The class type to convert the parameter value to. 292 * @return The parameter value converted to the specified class type. 293 * @throws ParseException 294 */ 295 public <T> T get(HttpPartParser parser, String name, T def, Class<T> type) throws ParseException { 296 return parse(parser, name, def, getClassMeta(type)); 297 } 298 299 /** 300 * Same as {@link #get(String, Class)} except for use on multi-part parameters 301 * (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 302 * 303 * <p> 304 * This method must only be called when parsing into classes of type Collection or array. 305 * 306 * @param name The parameter name. 307 * @param type The class type to convert the parameter value to. 308 * @return The parameter value converted to the specified class type. 309 * @throws ParseException 310 */ 311 public <T> T getAll(String name, Class<T> type) throws ParseException { 312 return getAll(null, name, type); 313 } 314 315 /** 316 * Same as {@link #getAll(String, Class)} but allows you to override the part parser. 317 * 318 * <p> 319 * This method must only be called when parsing into classes of type Collection or array. 320 * 321 * @param parser 322 * The parser to use for parsing the string value. 323 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 324 * @param name The parameter name. 325 * @param type The class type to convert the parameter value to. 326 * @return The parameter value converted to the specified class type. 327 * @throws ParseException 328 */ 329 public <T> T getAll(HttpPartParser parser, String name, Class<T> type) throws ParseException { 330 return parseAll(parser, name, getClassMeta(type)); 331 } 332 333 /** 334 * Returns the specified form-data parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 335 * 336 * <p> 337 * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created. 338 * 339 * <h5 class='section'>Examples:</h5> 340 * <p class='bcode'> 341 * <jc>// Parse into a linked-list of strings.</jc> 342 * List<String> myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 343 * 344 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 345 * List<List<String>> myparam = formData.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 346 * 347 * <jc>// Parse into a map of string keys/values.</jc> 348 * Map<String,String> myparam = formData.getr(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 349 * 350 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 351 * 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>); 352 * </p> 353 * 354 * <h5 class='section'>Notes:</h5> 355 * <ul class='spaced-list'> 356 * <li> 357 * <code>Collections</code> must be followed by zero or one parameter representing the value type. 358 * <li> 359 * <code>Maps</code> must be followed by zero or two parameters representing the key and value types. 360 * </ul> 361 * 362 * <h5 class='section'>See Also:</h5> 363 * <ul> 364 * <li class='jf'>{@link RestContext#REST_partParser} 365 * </ul> 366 * 367 * @param name The parameter name. 368 * @param type 369 * The type of object to create. 370 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 371 * @param args 372 * The type arguments of the class if it's a collection or map. 373 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 374 * <br>Ignored if the main type is not a map or collection. 375 * @return The parameter value converted to the specified class type. 376 * @throws ParseException 377 */ 378 public <T> T get(String name, Type type, Type...args) throws ParseException { 379 return get(null, name, type, args); 380 } 381 382 /** 383 * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser. 384 * 385 * @param parser 386 * The parser to use for parsing the string value. 387 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 388 * @param name The parameter name. 389 * @param type 390 * The type of object to create. 391 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 392 * @param args 393 * The type arguments of the class if it's a collection or map. 394 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 395 * <br>Ignored if the main type is not a map or collection. 396 * @return The parameter value converted to the specified class type. 397 * @throws ParseException 398 */ 399 public <T> T get(HttpPartParser parser, String name, Type type, Type...args) throws ParseException { 400 return (T)parse(parser, name, getClassMeta(type, args)); 401 } 402 403 /** 404 * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters 405 * (e.g. <js>"key=1&key=2&key=3"</js> instead of <js>"key=@(1,2,3)"</js>) 406 * 407 * <p> 408 * This method must only be called when parsing into classes of type Collection or array. 409 * 410 * @param name The parameter name. 411 * @param type 412 * The type of object to create. 413 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 414 * @param args 415 * The type arguments of the class if it's a collection or map. 416 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 417 * <br>Ignored if the main type is not a map or collection. 418 * @return The parameter value converted to the specified class type. 419 * @throws ParseException 420 */ 421 public <T> T getAll(String name, Type type, Type...args) throws ParseException { 422 return getAll(null, name, type, args); 423 } 424 425 /** 426 * Same as {@link #getAll(String, Type, Type...)} but allows you to override the part parser. 427 * 428 * @param parser 429 * The parser to use for parsing the string value. 430 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 431 * @param name The parameter name. 432 * @param type 433 * The type of object to create. 434 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 435 * @param args 436 * The type arguments of the class if it's a collection or map. 437 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 438 * <br>Ignored if the main type is not a map or collection. 439 * @return The parameter value converted to the specified class type. 440 * @throws ParseException 441 */ 442 public <T> T getAll(HttpPartParser parser, String name, Type type, Type...args) throws ParseException { 443 return (T)parseAll(parser, name, getClassMeta(type, args)); 444 } 445 446 447 /* Workhorse method */ 448 <T> T parse(HttpPartParser parser, String name, T def, ClassMeta<T> cm) throws ParseException { 449 String val = getString(name); 450 if (val == null) 451 return def; 452 return parseValue(parser, val, cm); 453 } 454 455 /* Workhorse method */ 456 <T> T parse(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException { 457 String val = getString(name); 458 if (cm.isPrimitive() && (val == null || val.isEmpty())) 459 return cm.getPrimitiveDefault(); 460 return parseValue(parser, val, cm); 461 } 462 463 /* Workhorse method */ 464 @SuppressWarnings("rawtypes") 465 <T> T parseAll(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException { 466 String[] p = get(name); 467 if (p == null) 468 return null; 469 if (parser == null) 470 parser = this.parser; 471 if (cm.isArray()) { 472 List c = new ArrayList(); 473 for (int i = 0; i < p.length; i++) 474 c.add(parseValue(parser, p[i], cm.getElementType())); 475 return (T)toArray(c, cm.getElementType().getInnerClass()); 476 } else if (cm.isCollection()) { 477 try { 478 Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList()); 479 for (int i = 0; i < p.length; i++) 480 c.add(parseValue(parser, p[i], cm.getElementType())); 481 return (T)c; 482 } catch (ParseException e) { 483 throw e; 484 } catch (Exception e) { 485 // Typically an instantiation exception. 486 throw new ParseException(e); 487 } 488 } 489 throw new ParseException("Invalid call to getParameters(String, ClassMeta). Class type must be a Collection or array."); 490 } 491 492 private <T> T parseValue(HttpPartParser parser, String val, ClassMeta<T> c) throws ParseException { 493 try { 494 if (parser == null) 495 parser = this.parser; 496 return parser.parse(HttpPartType.FORM_DATA, val, c); 497 } catch (Exception e) { 498 throw new ParseException(e); 499 } 500 } 501 502 /** 503 * Converts the form-data parameters to a readable string. 504 * 505 * @param sorted Sort the form-data parameters by name. 506 * @return A JSON string containing the contents of the form-data parameters. 507 */ 508 public String toString(boolean sorted) { 509 Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>()); 510 for (Map.Entry<String,String[]> e : this.entrySet()) { 511 String[] v = e.getValue(); 512 m.put(e.getKey(), v.length == 1 ? v[0] : v); 513 } 514 return JsonSerializer.DEFAULT_LAX.toString(m); 515 } 516 517 private ClassMeta<?> getClassMeta(Type type, Type...args) { 518 return beanSession.getClassMeta(type, args); 519 } 520 521 private <T> ClassMeta<T> getClassMeta(Class<T> type) { 522 return beanSession.getClassMeta(type); 523 } 524 525 @Override /* Object */ 526 public String toString() { 527 return toString(false); 528 } 529}