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.utils.*; 029 030/** 031 * Represents the query parameters in an HTTP request. 032 * 033 * <p> 034 * Similar in functionality to the {@link HttpServletRequest#getParameter(String)} except only looks in the URL string, not parameters from 035 * URL-Encoded FORM posts. 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 * <h5 class='section'>See Also:</h5> 039 * <ul> 040 * <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-rest-server.RequestQuery">Overview > juneau-rest-server > RequestQuery</a> 041 * </ul> 042 */ 043@SuppressWarnings("unchecked") 044public final class RequestQuery extends LinkedHashMap<String,String[]> { 045 private static final long serialVersionUID = 1L; 046 047 private HttpPartParser parser; 048 private BeanSession beanSession; 049 050 RequestQuery parser(HttpPartParser parser) { 051 this.parser = parser; 052 return this; 053 } 054 055 RequestQuery beanSession(BeanSession beanSession) { 056 this.beanSession = beanSession; 057 return this; 058 } 059 060 /* 061 * Create a copy of the request query parameters. 062 */ 063 RequestQuery copy() { 064 RequestQuery rq = new RequestQuery(); 065 rq.putAll(this); 066 return rq; 067 } 068 069 /** 070 * Adds default entries to these query parameters. 071 * 072 * <p> 073 * This includes the default queries defined at 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 RequestQuery 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, asStrings(value)); 088 } 089 } 090 return this; 091 } 092 093 /** 094 * Adds a default entries to these query parameters. 095 * 096 * <p> 097 * Similar to {@link #put(String, Object)} but doesn't override existing values. 098 * 099 * @param name 100 * The query parameter name. 101 * @param value 102 * The query parameter value. 103 * <br>Converted to a String using <code>toString()</code>. 104 * <br>Ignored if value is <jk>null</jk> or blank. 105 * @return This object (for method chaining). 106 */ 107 public RequestQuery addDefault(String name, Object value) { 108 return addDefault(Collections.singletonMap(name, value)); 109 } 110 111 /** 112 * Sets a request query parameter value. 113 * 114 * <p> 115 * This overwrites any existing value. 116 * 117 * @param name The parameter name. 118 * @param value 119 * The parameter value. 120 * <br>Can be <jk>null</jk>. 121 */ 122 public void put(String name, Object value) { 123 if (value == null) 124 put(name, null); 125 else 126 put(name, asStrings(value)); 127 } 128 129 /** 130 * Returns a query parameter value as a string. 131 * 132 * <p> 133 * If multiple query parameters have the same name, this returns only the first instance. 134 * 135 * @param name The URL parameter name. 136 * @return 137 * The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&foo"</js>). 138 */ 139 public String getString(String name) { 140 String[] v = get(name); 141 if (v == null || v.length == 0) 142 return null; 143 144 // Fix for behavior difference between Tomcat and WAS. 145 // getParameter("foo") on "&foo" in Tomcat returns "". 146 // getParameter("foo") on "&foo" in WAS returns null. 147 if (v.length == 1 && v[0] == null) 148 return ""; 149 150 return v[0]; 151 } 152 153 /** 154 * Same as {@link #getString(String)} but returns the specified default value if the query parameter was not 155 * specified. 156 * 157 * @param name The URL parameter name. 158 * @param def The default value. 159 * @return 160 * The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>). 161 */ 162 public String getString(String name, String def) { 163 String s = getString(name); 164 return StringUtils.isEmpty(s) ? def : s; 165 } 166 167 /** 168 * Same as {@link #getString(String)} but converts the value to an integer. 169 * 170 * @param name The URL parameter name. 171 * @return 172 * The parameter value, or <code>0</code> if parameter not specified or has no value (e.g. <js>"&foo"</js>). 173 */ 174 public int getInt(String name) { 175 return getInt(name, 0); 176 } 177 178 /** 179 * Same as {@link #getString(String,String)} but converts the value to an integer. 180 * 181 * @param name The URL parameter name. 182 * @param def The default value. 183 * @return 184 * The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>). 185 */ 186 public int getInt(String name, int def) { 187 String s = getString(name); 188 return StringUtils.isEmpty(s) ? def : Integer.parseInt(s); 189 } 190 191 /** 192 * Same as {@link #getString(String)} but converts the value to a boolean. 193 * 194 * @param name The URL parameter name. 195 * @return 196 * The parameter value, or <jk>false</jk> if parameter not specified or has no value (e.g. <js>"&foo"</js>). 197 */ 198 public boolean getBoolean(String name) { 199 return getBoolean(name, false); 200 } 201 202 /** 203 * Same as {@link #getString(String,String)} but converts the value to a boolean. 204 * 205 * @param name The URL parameter name. 206 * @param def The default value. 207 * @return 208 * The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>). 209 */ 210 public boolean getBoolean(String name, boolean def) { 211 String s = getString(name); 212 return StringUtils.isEmpty(s) ? def : Boolean.parseBoolean(s); 213 } 214 215 /** 216 * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 217 * 218 * <h5 class='section'>Examples:</h5> 219 * <p class='bcode'> 220 * <jc>// Parse into an integer.</jc> 221 * <jk>int</jk> myparam = query.get(<js>"myparam"</js>, <jk>int</jk>.<jk>class</jk>); 222 * 223 * <jc>// Parse into an int array.</jc> 224 * <jk>int</jk>[] myparam = query.get(<js>"myparam"</js>, <jk>int</jk>[].<jk>class</jk>); 225 226 * <jc>// Parse into a bean.</jc> 227 * MyBean myparam = query.get(<js>"myparam"</js>, MyBean.<jk>class</jk>); 228 * 229 * <jc>// Parse into a linked-list of objects.</jc> 230 * List myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>); 231 * 232 * <jc>// Parse into a map of object keys/values.</jc> 233 * Map myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>); 234 * </p> 235 * 236 * <h5 class='section'>See Also:</h5> 237 * <ul> 238 * <li class='jf'>{@link RestContext#REST_partParser} 239 * </ul> 240 * 241 * @param name The parameter name. 242 * @param type The class type to convert the parameter value to. 243 * @param <T> The class type to convert the parameter value to. 244 * @return The parameter value converted to the specified class type. 245 * @throws ParseException 246 */ 247 public <T> T get(String name, Class<T> type) throws ParseException { 248 return get(null, name, type); 249 } 250 251 /** 252 * Same as {@link #get(String, Class)} but allows you to override the part parser. 253 * 254 * @param parser 255 * The parser to use for parsing the string value. 256 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 257 * @param name The parameter name. 258 * @param type The class type to convert the parameter value to. 259 * @param <T> The class type to convert the parameter value to. 260 * @return The parameter value converted to the specified class type. 261 * @throws ParseException 262 */ 263 public <T> T get(HttpPartParser parser, String name, Class<T> type) throws ParseException { 264 return get(parser, name, getClassMeta(type)); 265 } 266 267 /** 268 * Same as {@link #get(String, Class)} except returns a default value if not found. 269 * 270 * @param name The parameter name. 271 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 272 * @param type The class type to convert the parameter value to. 273 * @param <T> The class type to convert the parameter value to. 274 * @return The parameter value converted to the specified class type. 275 * @throws ParseException 276 */ 277 public <T> T get(String name, T def, Class<T> type) throws ParseException { 278 return get(null, name, def, type); 279 } 280 281 /** 282 * Same as {@link #get(String, Object, Class)} but allows you to override the part parser. 283 * 284 * @param parser 285 * The parser to use for parsing the string value. 286 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 287 * @param name The parameter name. 288 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 289 * @param type The class type to convert the parameter value to. 290 * @param <T> The class type to convert the parameter value to. 291 * @return The parameter value converted to the specified class type. 292 * @throws ParseException 293 */ 294 public <T> T get(HttpPartParser parser, String name, T def, Class<T> type) throws ParseException { 295 return get(parser, name, def, getClassMeta(type)); 296 } 297 298 /** 299 * Returns the specified query parameter value converted to a POJO using the {@link HttpPartParser} registered with the resource. 300 * 301 * <p> 302 * Similar to {@link #get(String,Class)} but allows for complex collections of POJOs to be created. 303 * 304 * <h5 class='section'>Examples:</h5> 305 * <p class='bcode'> 306 * <jc>// Parse into a linked-list of strings.</jc> 307 * List<String> myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 308 * 309 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 310 * List<List<String>> myparam = query.get(<js>"myparam"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 311 * 312 * <jc>// Parse into a map of string keys/values.</jc> 313 * Map<String,String> myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 314 * 315 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 316 * Map<String,List<MyBean>> myparam = query.get(<js>"myparam"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 317 * </p> 318 * 319 * <h5 class='section'>Notes:</h5> 320 * <ul class='spaced-list'> 321 * <li> 322 * <code>Collections</code> must be followed by zero or one parameter representing the value type. 323 * <li> 324 * <code>Maps</code> must be followed by zero or two parameters representing the key and value types. 325 * </ul> 326 * 327 * <h5 class='section'>See Also:</h5> 328 * <ul> 329 * <li class='jf'>{@link RestContext#REST_partParser} 330 * </ul> 331 * 332 * @param name The parameter name. 333 * @param type 334 * The type of object to create. 335 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 336 * @param args 337 * The type arguments of the class if it's a collection or map. 338 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 339 * <br>Ignored if the main type is not a map or collection. 340 * @param <T> The class type to convert the parameter value to. 341 * @return The parameter value converted to the specified class type. 342 * @throws ParseException 343 */ 344 public <T> T get(String name, Type type, Type...args) throws ParseException { 345 return get((HttpPartParser)null, name, type, args); 346 } 347 348 /** 349 * Same as {@link #get(String, Type, Type...)} but allows you to override the part parser. 350 * 351 * @param parser 352 * The parser to use for parsing the string value. 353 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 354 * 355 * @param name The parameter name. 356 * @param type 357 * The type of object to create. 358 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 359 * @param args 360 * The type arguments of the class if it's a collection or map. 361 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 362 * <br>Ignored if the main type is not a map or collection. 363 * @param <T> The class type to convert the parameter value to. 364 * @return The parameter value converted to the specified class type. 365 * @throws ParseException 366 */ 367 public <T> T get(HttpPartParser parser, String name, Type type, Type...args) throws ParseException { 368 return (T)parse(parser, name, getClassMeta(type, args)); 369 } 370 371 /** 372 * Same as {@link #get(String, Class)} except returns a default value if not found. 373 * 374 * @param name The parameter name. 375 * @param type 376 * The type of object to create. 377 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 378 * @param args 379 * The type arguments of the class if it's a collection or map. 380 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 381 * <br>Ignored if the main type is not a map or collection. 382 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 383 * @param <T> The class type to convert the parameter value to. 384 * @return The parameter value converted to the specified class type. 385 * @throws ParseException 386 */ 387 public <T> T get(String name, Object def, Type type, Type...args) throws ParseException { 388 return get(null, name, def, type, args); 389 } 390 391 /** 392 * Same as {@link #get(String, Object, Type, Type...)} but allows you to override the part parser. 393 * 394 * @param parser 395 * The parser to use for parsing the string value. 396 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 397 * @param name The parameter name. 398 * @param type 399 * The type of object to create. 400 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 401 * @param args 402 * The type arguments of the class if it's a collection or map. 403 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 404 * <br>Ignored if the main type is not a map or collection. 405 * @param def The default value if the parameter was not specified or is <jk>null</jk>. 406 * @param <T> The class type to convert the parameter value to. 407 * @return The parameter value converted to the specified class type. 408 * @throws ParseException 409 */ 410 public <T> T get(HttpPartParser parser, String name, Object def, Type type, Type...args) throws ParseException { 411 return (T)parse(parser, name, def, getClassMeta(type, args)); 412 } 413 414 /** 415 * Same as {@link #get(String, Class)} except for use on multi-part parameters 416 * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=@(1,2,3)"</js>). 417 * 418 * <p> 419 * This method must only be called when parsing into classes of type Collection or array. 420 * 421 * @param name The query parameter name. 422 * @param c The class type to convert the parameter value to. 423 * @param <T> The class type to convert the parameter value to. 424 * @return The query parameter value converted to the specified class type. 425 * @throws ParseException 426 */ 427 public <T> T getAll(String name, Class<T> c) throws ParseException { 428 return getAll(name, beanSession.getClassMeta(c)); 429 } 430 431 /** 432 * Same as {@link #get(String, Type, Type...)} except for use on multi-part parameters 433 * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=@(1,2,3)"</js>). 434 * 435 * <p> 436 * This method must only be called when parsing into classes of type Collection or array. 437 * 438 * @param name The query parameter name. 439 * @param type 440 * The type of object to create. 441 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 442 * @param args 443 * The type arguments of the class if it's a collection or map. 444 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 445 * <br>Ignored if the main type is not a map or collection. 446 * @param <T> The class type to convert the parameter value to. 447 * @return The query parameter value converted to the specified class type. 448 * @throws ParseException 449 */ 450 public <T> T getAll(String name, Type type, Type...args) throws ParseException { 451 return getAll(null, name, type, args); 452 } 453 454 /** 455 * Same as {@link #getAll(String, Type, Type...)} but allows you to override the part parser. 456 * 457 * @param parser 458 * The parser to use for parsing the string value. 459 * <br>If <jk>null</jk>, uses the part parser defined on the resource/method. 460 * @param name The query parameter name. 461 * @param type 462 * The type of object to create. 463 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 464 * @param args 465 * The type arguments of the class if it's a collection or map. 466 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 467 * <br>Ignored if the main type is not a map or collection. 468 * @param <T> The class type to convert the parameter value to. 469 * @return The query parameter value converted to the specified class type. 470 * @throws ParseException 471 */ 472 public <T> T getAll(HttpPartParser parser, String name, Type type, Type...args) throws ParseException { 473 return (T)parseAll(parser, name, getClassMeta(type, args)); 474 } 475 476 /** 477 * Returns <jk>true</jk> if the request contains any of the specified query parameters. 478 * 479 * @param params The list of parameters to check for. 480 * @return <jk>true</jk> if the request contains any of the specified query parameters. 481 */ 482 public boolean containsAnyKeys(String...params) { 483 for (String p : params) 484 if (containsKey(p)) 485 return true; 486 return false; 487 } 488 489 /** 490 * Locates the special search query arguments in the query and returns them as a {@link SearchArgs} object. 491 * 492 * <p> 493 * The query arguments are as follows: 494 * <ul class='spaced-list'> 495 * <li> 496 * <js>"&s="</js> - A comma-delimited list of column-name/search-token pairs. 497 * <br>Example: <js>"&s=column1=foo*,column2=*bar"</js> 498 * <li> 499 * <js>"&v="</js> - A comma-delimited list column names to view. 500 * <br>Example: <js>"&v=column1,column2"</js> 501 * <li> 502 * <js>"&o="</js> - A comma-delimited list column names to sort by. 503 * <br>Column names can be suffixed with <js>'-'</js> to indicate descending order. 504 * <br>Example: <js>"&o=column1,column2-"</js> 505 * <li> 506 * <js>"&p="</js> - The zero-index row number of the first row to display. 507 * <br>Example: <js>"&p=100"</js> 508 * <li> 509 * <js>"&l="</js> - The number of rows to return. 510 * <br><code>0</code> implies return all rows. 511 * <br>Example: <js>"&l=100"</js> 512 * <li> 513 * <js>"&i="</js> - The case-insensitive search flag. 514 * <br>Example: <js>"&i=true"</js> 515 * </ul> 516 * 517 * <h5 class='section'>Notes:</h5> 518 * <ul class='spaced-list'> 519 * <li> 520 * Whitespace is trimmed in the parameters. 521 * </ul> 522 * 523 * @return 524 * A new {@link SearchArgs} object initialized with the special search query arguments. 525 * <br>Returns <jk>null</jk> if no search arguments were found. 526 */ 527 public SearchArgs getSearchArgs() { 528 if (hasAny("s","v","o","p","l","i")) { 529 return new SearchArgs.Builder() 530 .search(getString("s")) 531 .view(getString("v")) 532 .sort(getString("o")) 533 .position(getInt("p")) 534 .limit(getInt("l")) 535 .ignoreCase(getBoolean("i")) 536 .build(); 537 } 538 return null; 539 } 540 541 /** 542 * Returns <jk>true</jk> if the query parameters contains any of the specified names. 543 * 544 * @param paramNames The parameter names to check for. 545 * @return <jk>true</jk> if the query parameters contains any of the specified names. 546 */ 547 public boolean hasAny(String...paramNames) { 548 for (String p : paramNames) 549 if (containsKey(p)) 550 return true; 551 return false; 552 } 553 554 /* Workhorse method */ 555 private <T> T parse(HttpPartParser parser, String name, Object def, ClassMeta<T> cm) throws ParseException { 556 String val = getString(name); 557 if (val == null) 558 return (T)def; 559 return parseValue(parser, val, cm); 560 } 561 562 /* Workhorse method */ 563 private <T> T parse(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException { 564 String val = getString(name); 565 if (cm.isPrimitive() && (val == null || val.isEmpty())) 566 return cm.getPrimitiveDefault(); 567 return parseValue(parser, val, cm); 568 } 569 570 /* Workhorse method */ 571 @SuppressWarnings("rawtypes") 572 private <T> T parseAll(HttpPartParser parser, String name, ClassMeta<T> cm) throws ParseException { 573 String[] p = get(name); 574 if (p == null) 575 return null; 576 if (cm.isArray()) { 577 List c = new ArrayList(); 578 for (int i = 0; i < p.length; i++) 579 c.add(parseValue(parser, p[i], cm.getElementType())); 580 return (T)toArray(c, cm.getElementType().getInnerClass()); 581 } else if (cm.isCollection()) { 582 try { 583 Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList()); 584 for (int i = 0; i < p.length; i++) 585 c.add(parseValue(parser, p[i], cm.getElementType())); 586 return (T)c; 587 } catch (ParseException e) { 588 throw e; 589 } catch (Exception e) { 590 // Typically an instantiation exception. 591 throw new ParseException(e); 592 } 593 } 594 throw new ParseException("Invalid call to getQueryParameters(String, ClassMeta). Class type must be a Collection or array."); 595 } 596 597 private <T> T parseValue(HttpPartParser parser, String val, ClassMeta<T> c) throws ParseException { 598 if (parser == null) 599 parser = this.parser; 600 return parser.parse(HttpPartType.QUERY, val, c); 601 } 602 603 /** 604 * Converts the query parameters to a readable string. 605 * 606 * @param sorted Sort the query parameters by name. 607 * @return A JSON string containing the contents of the query parameters. 608 */ 609 public String toString(boolean sorted) { 610 Map<String,Object> m = (sorted ? new TreeMap<String,Object>() : new LinkedHashMap<String,Object>()); 611 for (Map.Entry<String,String[]> e : this.entrySet()) { 612 String[] v = e.getValue(); 613 m.put(e.getKey(), v.length == 1 ? v[0] : v); 614 } 615 return JsonSerializer.DEFAULT_LAX.toString(m); 616 } 617 618 /** 619 * Converts this object to a query string. 620 * 621 * <p> 622 * Returned query string does not start with <js>'?'</js>. 623 * 624 * @return A new query string, or an empty string if this object is empty. 625 */ 626 public String toQueryString() { 627 StringBuilder sb = new StringBuilder(); 628 for (Map.Entry<String,String[]> e : this.entrySet()) { 629 for (int i = 0; i < e.getValue().length; i++) { 630 if (sb.length() > 0) 631 sb.append("&"); 632 sb.append(urlEncode(e.getKey())).append('=').append(urlEncode(e.getValue()[i])); 633 } 634 } 635 return sb.toString(); 636 } 637 638 private ClassMeta<?> getClassMeta(Type type, Type...args) { 639 return beanSession.getClassMeta(type, args); 640 } 641 642 private <T> ClassMeta<T> getClassMeta(Class<T> type) { 643 return beanSession.getClassMeta(type); 644 } 645 646 @Override /* Object */ 647 public String toString() { 648 return toString(false); 649 } 650}