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.httppart; 014 015import static java.util.Collections.*; 016import static org.apache.juneau.httppart.HttpPartSchema.Format.*; 017import static org.apache.juneau.httppart.HttpPartSchema.Type.*; 018import static org.apache.juneau.internal.StringUtils.*; 019 020import java.lang.annotation.*; 021import java.lang.reflect.*; 022import java.math.*; 023import java.util.*; 024import java.util.concurrent.atomic.*; 025import java.util.regex.*; 026 027import org.apache.juneau.*; 028import org.apache.juneau.http.annotation.*; 029import org.apache.juneau.internal.*; 030import org.apache.juneau.parser.*; 031import org.apache.juneau.utils.*; 032 033/** 034 * Represents an OpenAPI schema definition. 035 * 036 * <p> 037 * The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts. 038 * <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts. 039 * 040 * <p> 041 * Schema objects are created via builders instantiated through the {@link #create()} method. 042 * 043 * <p> 044 * This class is thread safe and reusable. 045 * 046 * <h5 class='section'>See Also:</h5> 047 * <ul> 048 * <li class='link'>{@doc juneau-marshall.OpenApiDetails} 049 * </ul> 050 */ 051public class HttpPartSchema { 052 053 //------------------------------------------------------------------------------------------------------------------- 054 // Predefined instances 055 //------------------------------------------------------------------------------------------------------------------- 056 057 /** Reusable instance of this object, all default settings. */ 058 public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build(); 059 060 final String name; 061 final Set<Integer> codes; 062 final String _default; 063 final Set<String> _enum; 064 final Map<String,HttpPartSchema> properties; 065 final boolean allowEmptyValue, exclusiveMaximum, exclusiveMinimum, required, uniqueItems, skipIfEmpty; 066 final CollectionFormat collectionFormat; 067 final Type type; 068 final Format format; 069 final Pattern pattern; 070 final HttpPartSchema items, additionalProperties; 071 final Number maximum, minimum, multipleOf; 072 final Long maxLength, minLength, maxItems, minItems, maxProperties, minProperties; 073 final Class<? extends HttpPartParser> parser; 074 final Class<? extends HttpPartSerializer> serializer; 075 final ClassMeta<?> parsedType; 076 077 /** 078 * Instantiates a new builder for this object. 079 * 080 * @return A new builder for this object. 081 */ 082 public static HttpPartSchemaBuilder create() { 083 return new HttpPartSchemaBuilder(); 084 } 085 086 /** 087 * Finds the schema information for the specified method parameter. 088 * 089 * <p> 090 * This method will gather all the schema information from the annotations at the following locations: 091 * <ul> 092 * <li>The method parameter. 093 * <li>The method parameter class. 094 * <li>The method parameter parent classes and interfaces. 095 * </ul> 096 * 097 * @param c 098 * The annotation to look for. 099 * <br>Valid values: 100 * <ul> 101 * <li>{@link Body} 102 * <li>{@link Header} 103 * <li>{@link Query} 104 * <li>{@link FormData} 105 * <li>{@link Path} 106 * <li>{@link Response} 107 * <li>{@link ResponseHeader} 108 * <li>{@link ResponseBody} 109 * <li>{@link HasQuery} 110 * <li>{@link HasFormData} 111 * </ul> 112 * @param m 113 * The Java method containing the parameter. 114 * @param mi 115 * The index of the parameter on the method. 116 * @return The schema information about the parameter. 117 */ 118 public static HttpPartSchema create(Class<? extends Annotation> c, Method m, int mi) { 119 return create().apply(c, m, mi).build(); 120 } 121 122 /** 123 * Finds the schema information for the specified method return. 124 * 125 * <p> 126 * This method will gather all the schema information from the annotations at the following locations: 127 * <ul> 128 * <li>The method. 129 * <li>The method return class. 130 * <li>The method return parent classes and interfaces. 131 * </ul> 132 * 133 * @param c 134 * The annotation to look for. 135 * <br>Valid values: 136 * <ul> 137 * <li>{@link Body} 138 * <li>{@link Header} 139 * <li>{@link Query} 140 * <li>{@link FormData} 141 * <li>{@link Path} 142 * <li>{@link Response} 143 * <li>{@link ResponseHeader} 144 * <li>{@link HasQuery} 145 * <li>{@link HasFormData} 146 * </ul> 147 * @param m 148 * The Java method with the return type being checked. 149 * @return The schema information about the parameter. 150 */ 151 public static HttpPartSchema create(Class<? extends Annotation> c, Method m) { 152 return create().apply(c, m).build(); 153 } 154 155 /** 156 * Finds the schema information for the specified class. 157 * 158 * <p> 159 * This method will gather all the schema information from the annotations on the class and all parent classes/interfaces. 160 * 161 * @param c 162 * The annotation to look for. 163 * <br>Valid values: 164 * <ul> 165 * <li>{@link Body} 166 * <li>{@link Header} 167 * <li>{@link Query} 168 * <li>{@link FormData} 169 * <li>{@link Path} 170 * <li>{@link Response} 171 * <li>{@link ResponseHeader} 172 * <li>{@link HasQuery} 173 * <li>{@link HasFormData} 174 * </ul> 175 * @param t 176 * The class containing the parameter. 177 * @return The schema information about the parameter. 178 */ 179 public static HttpPartSchema create(Class<? extends Annotation> c, java.lang.reflect.Type t) { 180 return create().apply(c, t).build(); 181 } 182 183 /** 184 * Shortcut for calling <code>create().type(type);</code> 185 * 186 * @param type The schema type value. 187 * @return A new builder. 188 */ 189 public static HttpPartSchemaBuilder create(String type) { 190 return create().type(type); 191 } 192 193 /** 194 * Shortcut for calling <code>create().type(type).format(format);</code> 195 * 196 * @param type The schema type value. 197 * @param format The schema format value. 198 * @return A new builder. 199 */ 200 public static HttpPartSchemaBuilder create(String type, String format) { 201 return create().type(type).format(format); 202 } 203 204 /** 205 * Finds the schema information on the specified annotation. 206 * 207 * @param a 208 * The annotation to find the schema information on.. 209 * @return The schema information found on the annotation. 210 */ 211 public static HttpPartSchema create(Annotation a) { 212 return create().apply(a).build(); 213 } 214 215 /** 216 * Finds the schema information on the specified annotation. 217 * 218 * @param a 219 * The annotation to find the schema information on.. 220 * @param defaultName The default part name if not specified on the annotation. 221 * @return The schema information found on the annotation. 222 */ 223 public static HttpPartSchema create(Annotation a, String defaultName) { 224 return create().name(defaultName).apply(a).build(); 225 } 226 227 HttpPartSchema(HttpPartSchemaBuilder b) { 228 this.name = b.name; 229 this.codes = copy(b.codes); 230 this._default = b._default; 231 this._enum = copy(b._enum); 232 this.properties = build(b.properties, b.noValidate); 233 this.allowEmptyValue = resolve(b.allowEmptyValue); 234 this.exclusiveMaximum = resolve(b.exclusiveMaximum); 235 this.exclusiveMinimum = resolve(b.exclusiveMinimum); 236 this.required = resolve(b.required); 237 this.uniqueItems = resolve(b.uniqueItems); 238 this.skipIfEmpty = resolve(b.skipIfEmpty); 239 this.collectionFormat = b.collectionFormat; 240 this.type = b.type; 241 this.format = b.format; 242 this.pattern = b.pattern; 243 this.items = build(b.items, b.noValidate); 244 this.additionalProperties = build(b.additionalProperties, b.noValidate); 245 this.maximum = b.maximum; 246 this.minimum = b.minimum; 247 this.multipleOf = b.multipleOf; 248 this.maxItems = b.maxItems; 249 this.maxLength = b.maxLength; 250 this.maxProperties = b.maxProperties; 251 this.minItems = b.minItems; 252 this.minLength = b.minLength; 253 this.minProperties = b.minProperties; 254 this.parser = b.parser; 255 this.serializer = b.serializer; 256 257 // Calculate parse type 258 Class<?> parsedType = Object.class; 259 if (type == ARRAY) { 260 if (items != null) 261 parsedType = Array.newInstance(items.parsedType.getInnerClass(), 0).getClass(); 262 } else if (type == BOOLEAN) { 263 parsedType = Boolean.class; 264 } else if (type == INTEGER) { 265 if (format == INT64) 266 parsedType = Long.class; 267 else 268 parsedType = Integer.class; 269 } else if (type == NUMBER) { 270 if (format == DOUBLE) 271 parsedType = Double.class; 272 else 273 parsedType = Float.class; 274 } else if (type == STRING) { 275 if (format == BYTE || format == BINARY || format == BINARY_SPACED) 276 parsedType = byte[].class; 277 else if (format == DATE || format == DATE_TIME) 278 parsedType = Calendar.class; 279 else 280 parsedType = String.class; 281 } 282 this.parsedType = BeanContext.DEFAULT.getClassMeta(parsedType); 283 284 if (b.noValidate) 285 return; 286 287 // Validation. 288 List<String> errors = new ArrayList<>(); 289 AList<String> notAllowed = new AList<>(); 290 boolean invalidFormat = false; 291 switch (type) { 292 case STRING: { 293 notAllowed.appendIf(properties != null, "properties"); 294 notAllowed.appendIf(additionalProperties != null, "additionalProperties"); 295 notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum"); 296 notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum"); 297 notAllowed.appendIf(uniqueItems, "uniqueItems"); 298 notAllowed.appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 299 notAllowed.appendIf(items != null, "items"); 300 notAllowed.appendIf(maximum != null, "maximum"); 301 notAllowed.appendIf(minimum != null, "minimum"); 302 notAllowed.appendIf(multipleOf != null, "multipleOf"); 303 notAllowed.appendIf(maxItems != null, "maxItems"); 304 notAllowed.appendIf(minItems != null, "minItems"); 305 notAllowed.appendIf(minProperties != null, "minProperties"); 306 invalidFormat = ! format.isOneOf(Format.BYTE, Format.BINARY, Format.BINARY_SPACED, Format.DATE, Format.DATE_TIME, Format.PASSWORD, Format.UON, Format.NO_FORMAT); 307 break; 308 } 309 case ARRAY: { 310 notAllowed.appendIf(properties != null, "properties"); 311 notAllowed.appendIf(additionalProperties != null, "additionalProperties"); 312 notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum"); 313 notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum"); 314 notAllowed.appendIf(pattern != null, "pattern"); 315 notAllowed.appendIf(maximum != null, "maximum"); 316 notAllowed.appendIf(minimum != null, "minimum"); 317 notAllowed.appendIf(multipleOf != null, "multipleOf"); 318 notAllowed.appendIf(maxLength != null, "maxLength"); 319 notAllowed.appendIf(minLength != null, "minLength"); 320 notAllowed.appendIf(maxProperties != null, "maxProperties"); 321 notAllowed.appendIf(minProperties != null, "minProperties"); 322 invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON); 323 break; 324 } 325 case BOOLEAN: { 326 notAllowed.appendIf(! _enum.isEmpty(), "_enum"); 327 notAllowed.appendIf(properties != null, "properties"); 328 notAllowed.appendIf(additionalProperties != null, "additionalProperties"); 329 notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum"); 330 notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum"); 331 notAllowed.appendIf(uniqueItems, "uniqueItems"); 332 notAllowed.appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 333 notAllowed.appendIf(pattern != null, "pattern"); 334 notAllowed.appendIf(items != null, "items"); 335 notAllowed.appendIf(maximum != null, "maximum"); 336 notAllowed.appendIf(minimum != null, "minimum"); 337 notAllowed.appendIf(multipleOf != null, "multipleOf"); 338 notAllowed.appendIf(maxItems != null, "maxItems"); 339 notAllowed.appendIf(maxLength != null, "maxLength"); 340 notAllowed.appendIf(maxProperties != null, "maxProperties"); 341 notAllowed.appendIf(minItems != null, "minItems"); 342 notAllowed.appendIf(minLength != null, "minLength"); 343 notAllowed.appendIf(minProperties != null, "minProperties"); 344 invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON); 345 break; 346 } 347 case FILE: { 348 break; 349 } 350 case INTEGER: { 351 notAllowed.appendIf(properties != null, "properties"); 352 notAllowed.appendIf(additionalProperties != null, "additionalProperties"); 353 notAllowed.appendIf(uniqueItems, "uniqueItems"); 354 notAllowed.appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 355 notAllowed.appendIf(pattern != null, "pattern"); 356 notAllowed.appendIf(items != null, "items"); 357 notAllowed.appendIf(maxItems != null, "maxItems"); 358 notAllowed.appendIf(maxLength != null, "maxLength"); 359 notAllowed.appendIf(maxProperties != null, "maxProperties"); 360 notAllowed.appendIf(minItems != null, "minItems"); 361 notAllowed.appendIf(minLength != null, "minLength"); 362 notAllowed.appendIf(minProperties != null, "minProperties"); 363 invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON, Format.INT32, Format.INT64); 364 break; 365 } 366 case NUMBER: { 367 notAllowed.appendIf(properties != null, "properties"); 368 notAllowed.appendIf(additionalProperties != null, "additionalProperties"); 369 notAllowed.appendIf(uniqueItems, "uniqueItems"); 370 notAllowed.appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 371 notAllowed.appendIf(pattern != null, "pattern"); 372 notAllowed.appendIf(items != null, "items"); 373 notAllowed.appendIf(maxItems != null, "maxItems"); 374 notAllowed.appendIf(maxLength != null, "maxLength"); 375 notAllowed.appendIf(maxProperties != null, "maxProperties"); 376 notAllowed.appendIf(minItems != null, "minItems"); 377 notAllowed.appendIf(minLength != null, "minLength"); 378 notAllowed.appendIf(minProperties != null, "minProperties"); 379 invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON, Format.FLOAT, Format.DOUBLE); 380 break; 381 } 382 case OBJECT: { 383 notAllowed.appendIf(exclusiveMaximum, "exclusiveMaximum"); 384 notAllowed.appendIf(exclusiveMinimum, "exclusiveMinimum"); 385 notAllowed.appendIf(uniqueItems, "uniqueItems"); 386 notAllowed.appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 387 notAllowed.appendIf(pattern != null, "pattern"); 388 notAllowed.appendIf(items != null, "items"); 389 notAllowed.appendIf(maximum != null, "maximum"); 390 notAllowed.appendIf(minimum != null, "minimum"); 391 notAllowed.appendIf(multipleOf != null, "multipleOf"); 392 notAllowed.appendIf(maxItems != null, "maxItems"); 393 notAllowed.appendIf(maxLength != null, "maxLength"); 394 notAllowed.appendIf(minItems != null, "minItems"); 395 notAllowed.appendIf(minLength != null, "minLength"); 396 invalidFormat = ! format.isOneOf(Format.NO_FORMAT, Format.UON); 397 break; 398 } 399 default: 400 break; 401 } 402 403 if (! notAllowed.isEmpty()) 404 errors.add("Attributes not allow for type='"+type+"': " + StringUtils.join(notAllowed, ",")); 405 if (invalidFormat) 406 errors.add("Invalid format for type='"+type+"': '"+format+"'"); 407 if (exclusiveMaximum && maximum == null) 408 errors.add("Cannot specify exclusiveMaximum with maximum."); 409 if (exclusiveMinimum && minimum == null) 410 errors.add("Cannot specify exclusiveMinimum with minimum."); 411 if (required && _default != null) 412 errors.add("Cannot specify a default value on a required value."); 413 if (minLength != null && maxLength != null && maxLength < minLength) 414 errors.add("maxLength cannot be less than minLength."); 415 if (minimum != null && maximum != null && maximum.doubleValue() < minimum.doubleValue()) 416 errors.add("maximum cannot be less than minimum."); 417 if (minItems != null && maxItems != null && maxItems < minItems) 418 errors.add("maxItems cannot be less than minItems."); 419 if (minProperties != null && maxProperties != null && maxProperties < minProperties) 420 errors.add("maxProperties cannot be less than minProperties."); 421 if (minLength != null && minLength < 0) 422 errors.add("minLength cannot be less than zero."); 423 if (maxLength != null && maxLength < 0) 424 errors.add("maxLength cannot be less than zero."); 425 if (minItems != null && minItems < 0) 426 errors.add("minItems cannot be less than zero."); 427 if (maxItems != null && maxItems < 0) 428 errors.add("maxItems cannot be less than zero."); 429 if (minProperties != null && minProperties < 0) 430 errors.add("minProperties cannot be less than zero."); 431 if (maxProperties != null && maxProperties < 0) 432 errors.add("maxProperties cannot be less than zero."); 433 if (type == ARRAY && items != null && items.getType() == OBJECT && (format != UON && format != Format.NO_FORMAT)) 434 errors.add("Cannot define an array of objects unless array format is 'uon'."); 435 436 if (! errors.isEmpty()) 437 throw new ContextRuntimeException("Schema specification errors: \n\t" + join(errors, "\n\t")); 438 } 439 440 /** 441 * Valid values for the <code>collectionFormat</code> field. 442 */ 443 public static enum CollectionFormat { 444 445 /** 446 * Comma-separated values (e.g. <js>"foo,bar"</js>). 447 */ 448 CSV, 449 450 /** 451 * Space-separated values (e.g. <js>"foo bar"</js>). 452 */ 453 SSV, 454 455 /** 456 * Tab-separated values (e.g. <js>"foo\tbar"</js>). 457 */ 458 TSV, 459 460 /** 461 * Pipe-separated values (e.g. <js>"foo|bar"</js>). 462 */ 463 PIPES, 464 465 /** 466 * Corresponds to multiple parameter instances instead of multiple values for a single instance (e.g. <js>"foo=bar&foo=baz"</js>). 467 */ 468 MULTI, 469 470 /** 471 * UON notation (e.g. <js>"@(foo,bar)"</js>). 472 */ 473 UON, 474 475 /** 476 * Not specified. 477 */ 478 NO_COLLECTION_FORMAT; 479 480 static CollectionFormat fromString(String value) { 481 482 return valueOf(value.toUpperCase()); 483 } 484 485 @Override 486 public String toString() { 487 return name().toLowerCase(); 488 } 489 } 490 491 /** 492 * Valid values for the <code>type</code> field. 493 */ 494 public static enum Type { 495 496 /** 497 * String. 498 */ 499 STRING, 500 501 /** 502 * Floating point number. 503 */ 504 NUMBER, 505 506 /** 507 * Decimal number. 508 */ 509 INTEGER, 510 511 /** 512 * Boolean. 513 */ 514 BOOLEAN, 515 516 /** 517 * Array or collection. 518 */ 519 ARRAY, 520 521 /** 522 * Map or bean. 523 */ 524 OBJECT, 525 526 /** 527 * File. 528 */ 529 FILE, 530 531 /** 532 * Not specified. 533 */ 534 NO_TYPE; 535 536 static Type fromString(String value) { 537 return valueOf(value.toUpperCase()); 538 } 539 540 @Override 541 public String toString() { 542 return name().toLowerCase(); 543 } 544 } 545 546 /** 547 * Valid values for the <code>format</code> field. 548 */ 549 public static enum Format { 550 551 /** 552 * Signed 32 bits. 553 */ 554 INT32, 555 556 /** 557 * Signed 64 bits. 558 */ 559 INT64, 560 561 /** 562 * 32-bit floating point number. 563 */ 564 FLOAT, 565 566 /** 567 * 64-bit floating point number. 568 */ 569 DOUBLE, 570 571 /** 572 * BASE-64 encoded characters. 573 */ 574 BYTE, 575 576 /** 577 * Hexadecimal encoded octets (e.g. <js>"00FF"</js>). 578 */ 579 BINARY, 580 581 /** 582 * Spaced-separated hexadecimal encoded octets (e.g. <js>"00 FF"</js>). 583 */ 584 BINARY_SPACED, 585 586 /** 587 * An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 full-date</a>. 588 */ 589 DATE, 590 591 /** 592 * An <a href='http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14'>RFC3339 date-time</a>. 593 */ 594 DATE_TIME, 595 596 /** 597 * Used to hint UIs the input needs to be obscured. 598 */ 599 PASSWORD, 600 601 /** 602 * UON notation (e.g. <js>"(foo=bar,baz=@(qux,123))"</js>). 603 */ 604 UON, 605 606 /** 607 * Not specified. 608 */ 609 NO_FORMAT; 610 611 static Format fromString(String value) { 612 value = value.toUpperCase().replace('-','_'); 613 return valueOf(value); 614 } 615 616 @Override 617 public String toString() { 618 String s = name().toLowerCase().replace('_','-'); 619 return s; 620 } 621 622 /** 623 * Returns <jk>true</jk> if this format is in the provided list. 624 * 625 * @param list The list of formats to check against. 626 * @return <jk>true</jk> if this format is in the provided list. 627 */ 628 public boolean isOneOf(Format...list) { 629 for (Format ff : list) 630 if (this == ff) 631 return true; 632 return false; 633 } 634 } 635 636 /** 637 * Returns the default parsed type for this schema. 638 * 639 * @return The default parsed type for this schema. Never <jk>null</jk>. 640 */ 641 public ClassMeta<?> getParsedType() { 642 return parsedType; 643 } 644 645 /** 646 * Returns the name of the object described by this schema, for example the query or form parameter name. 647 * 648 * @return The name, or <jk>null</jk> if not specified. 649 * @see HttpPartSchemaBuilder#name(String) 650 */ 651 public String getName() { 652 return name; 653 } 654 655 /** 656 * Returns the HTTP status code or codes defined on a schema. 657 * 658 * @return 659 * The list of HTTP status codes. 660 * <br>Never <jk>null</jk>. 661 * @see HttpPartSchemaBuilder#code(int) 662 * @see HttpPartSchemaBuilder#codes(int[]) 663 */ 664 public Set<Integer> getCodes() { 665 return codes; 666 } 667 668 /** 669 * Returns the HTTP status code or codes defined on a schema. 670 * 671 * @param def The default value if there are no codes defined. 672 * @return 673 * The list of HTTP status codes. 674 * <br>A singleton set containing the default value if the set is empty. 675 * <br>Never <jk>null</jk>. 676 * @see HttpPartSchemaBuilder#code(int) 677 * @see HttpPartSchemaBuilder#codes(int[]) 678 */ 679 public Set<Integer> getCodes(Integer def) { 680 return codes.isEmpty() ? Collections.singleton(def) : codes; 681 } 682 683 /** 684 * Returns the first HTTP status code on a schema. 685 * 686 * @param def The default value if there are no codes defined. 687 * @return 688 * The list of HTTP status codes. 689 * <br>A singleton set containing the default value if the set is empty. 690 * <br>Never <jk>null</jk>. 691 * @see HttpPartSchemaBuilder#code(int) 692 * @see HttpPartSchemaBuilder#codes(int[]) 693 */ 694 public Integer getCode(Integer def) { 695 return codes.isEmpty() ? def : codes.iterator().next(); 696 } 697 698 /** 699 * Returns the <code>type</code> field of this schema. 700 * 701 * @return The <code>type</code> field of this schema, or <jk>null</jk> if not specified. 702 * @see HttpPartSchemaBuilder#type(String) 703 */ 704 public Type getType() { 705 return type; 706 } 707 708 /** 709 * Returns the <code>type</code> field of this schema. 710 * 711 * @param cm 712 * The class meta of the object. 713 * <br>Used to auto-detect the type if the type was not specified. 714 * @return The format field of this schema, or <jk>null</jk> if not specified. 715 * @see HttpPartSchemaBuilder#format(String) 716 */ 717 public Type getType(ClassMeta<?> cm) { 718 if (type != Type.NO_TYPE) 719 return type; 720 if (cm.isMapOrBean()) 721 return Type.OBJECT; 722 if (cm.isCollectionOrArray()) 723 return Type.ARRAY; 724 if (cm.isNumber()) { 725 if (cm.isDecimal()) 726 return Type.NUMBER; 727 return Type.INTEGER; 728 } 729 if (cm.isBoolean()) 730 return Type.BOOLEAN; 731 return Type.STRING; 732 } 733 734 /** 735 * Returns the <code>default</code> field of this schema. 736 * 737 * @return The default value for this schema, or <jk>null</jk> if not specified. 738 * @see HttpPartSchemaBuilder#_default(String) 739 */ 740 public String getDefault() { 741 return _default; 742 } 743 744 /** 745 * Returns the <code>collectionFormat</code> field of this schema. 746 * 747 * @return The <code>collectionFormat</code> field of this schema, or <jk>null</jk> if not specified. 748 * @see HttpPartSchemaBuilder#collectionFormat(String) 749 */ 750 public CollectionFormat getCollectionFormat() { 751 return collectionFormat; 752 } 753 754 /** 755 * Returns the <code>format</code> field of this schema. 756 * 757 * @see HttpPartSchemaBuilder#format(String) 758 * @return The <code>format</code> field of this schema, or <jk>null</jk> if not specified. 759 */ 760 public Format getFormat() { 761 return format; 762 } 763 764 /** 765 * Returns the <code>format</code> field of this schema. 766 * 767 * @param cm 768 * The class meta of the object. 769 * <br>Used to auto-detect the format if the format was not specified. 770 * @return The <code>format</code> field of this schema, or <jk>null</jk> if not specified. 771 * @see HttpPartSchemaBuilder#format(String) 772 */ 773 public Format getFormat(ClassMeta<?> cm) { 774 if (format != Format.NO_FORMAT) 775 return format; 776 if (cm.isNumber()) { 777 if (cm.isDecimal()) { 778 if (cm.isDouble()) 779 return Format.DOUBLE; 780 return Format.FLOAT; 781 } 782 if (cm.isLong()) 783 return Format.INT64; 784 return Format.INT32; 785 } 786 return format; 787 } 788 789 /** 790 * Returns the <code>maximum</code> field of this schema. 791 * 792 * @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined. 793 * @see HttpPartSchemaBuilder#items(HttpPartSchemaBuilder) 794 */ 795 public HttpPartSchema getItems() { 796 return items; 797 } 798 799 /** 800 * Returns the <code>maximum</code> field of this schema. 801 * 802 * @return The <code>maximum</code> field of this schema, or <jk>null</jk> if not specified. 803 * @see HttpPartSchemaBuilder#maximum(Number) 804 */ 805 public Number getMaximum() { 806 return maximum; 807 } 808 809 /** 810 * Returns the <code>minimum</code> field of this schema. 811 * 812 * @return The <code>minimum</code> field of this schema, or <jk>null</jk> if not specified. 813 * @see HttpPartSchemaBuilder#minimum(Number) 814 */ 815 public Number getMinimum() { 816 return minimum; 817 } 818 819 /** 820 * Returns the <code>xxx</code> field of this schema. 821 * 822 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 823 * @see HttpPartSchemaBuilder#multipleOf(Number) 824 */ 825 public Number getMultipleOf() { 826 return multipleOf; 827 } 828 829 /** 830 * Returns the <code>xxx</code> field of this schema. 831 * 832 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 833 * @see HttpPartSchemaBuilder#pattern(String) 834 */ 835 public Pattern getPattern() { 836 return pattern; 837 } 838 839 /** 840 * Returns the <code>xxx</code> field of this schema. 841 * 842 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 843 * @see HttpPartSchemaBuilder#maxLength(Long) 844 */ 845 public Long getMaxLength() { 846 return maxLength; 847 } 848 849 /** 850 * Returns the <code>xxx</code> field of this schema. 851 * 852 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 853 * @see HttpPartSchemaBuilder#minLength(Long) 854 */ 855 public Long getMinLength() { 856 return minLength; 857 } 858 859 /** 860 * Returns the <code>xxx</code> field of this schema. 861 * 862 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 863 * @see HttpPartSchemaBuilder#maxItems(Long) 864 */ 865 public Long getMaxItems() { 866 return maxItems; 867 } 868 869 /** 870 * Returns the <code>xxx</code> field of this schema. 871 * 872 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 873 * @see HttpPartSchemaBuilder#minItems(Long) 874 */ 875 public Long getMinItems() { 876 return minItems; 877 } 878 879 /** 880 * Returns the <code>xxx</code> field of this schema. 881 * 882 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 883 * @see HttpPartSchemaBuilder#maxProperties(Long) 884 */ 885 public Long getMaxProperties() { 886 return maxProperties; 887 } 888 889 /** 890 * Returns the <code>xxx</code> field of this schema. 891 * 892 * @return The <code>xxx</code> field of this schema, or <jk>null</jk> if not specified. 893 * @see HttpPartSchemaBuilder#minProperties(Long) 894 */ 895 public Long getMinProperties() { 896 return minProperties; 897 } 898 899 /** 900 * Returns the <code>exclusiveMaximum</code> field of this schema. 901 * 902 * @return The <code>exclusiveMaximum</code> field of this schema. 903 * @see HttpPartSchemaBuilder#exclusiveMaximum(Boolean) 904 */ 905 public boolean isExclusiveMaximum() { 906 return exclusiveMaximum; 907 } 908 909 /** 910 * Returns the <code>exclusiveMinimum</code> field of this schema. 911 * 912 * @return The <code>exclusiveMinimum</code> field of this schema. 913 * @see HttpPartSchemaBuilder#exclusiveMinimum(Boolean) 914 */ 915 public boolean isExclusiveMinimum() { 916 return exclusiveMinimum; 917 } 918 919 /** 920 * Returns the <code>uniqueItems</code> field of this schema. 921 * 922 * @return The <code>uniqueItems</code> field of this schema. 923 * @see HttpPartSchemaBuilder#uniqueItems(Boolean) 924 */ 925 public boolean isUniqueItems() { 926 return uniqueItems; 927 } 928 929 /** 930 * Returns the <code>required</code> field of this schema. 931 * 932 * @return The <code>required</code> field of this schema. 933 * @see HttpPartSchemaBuilder#required(Boolean) 934 */ 935 public boolean isRequired() { 936 return required; 937 } 938 939 /** 940 * Returns the <code>skipIfEmpty</code> field of this schema. 941 * 942 * @return The <code>skipIfEmpty</code> field of this schema. 943 * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean) 944 */ 945 public boolean isSkipIfEmpty() { 946 return skipIfEmpty; 947 } 948 949 /** 950 * Returns the <code>allowEmptyValue</code> field of this schema. 951 * 952 * @return The <code>skipIfEmpty</code> field of this schema. 953 * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean) 954 */ 955 public boolean isAllowEmptyValue() { 956 return allowEmptyValue; 957 } 958 959 /** 960 * Returns the <code>enum</code> field of this schema. 961 * 962 * @return The <code>enum</code> field of this schema, or <jk>null</jk> if not specified. 963 * @see HttpPartSchemaBuilder#_enum(Set) 964 */ 965 public Set<String> getEnum() { 966 return _enum; 967 } 968 969 /** 970 * Returns the <code>parser</code> field of this schema. 971 * 972 * @return The <code>parser</code> field of this schema, or <jk>null</jk> if not specified. 973 * @see HttpPartSchemaBuilder#parser(Class) 974 */ 975 public Class<? extends HttpPartParser> getParser() { 976 return parser; 977 } 978 979 /** 980 * Returns the <code>serializer</code> field of this schema. 981 * 982 * @return The <code>serializer</code> field of this schema, or <jk>null</jk> if not specified. 983 * @see HttpPartSchemaBuilder#serializer(Class) 984 */ 985 public Class<? extends HttpPartSerializer> getSerializer() { 986 return serializer; 987 } 988 989 /** 990 * Throws a {@link ParseException} if the specified pre-parsed input does not validate against this schema. 991 * 992 * @param in The input. 993 * @return The same object passed in. 994 * @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema. 995 */ 996 public String validateInput(String in) throws SchemaValidationException { 997 if (! isValidRequired(in)) 998 throw new SchemaValidationException("No value specified."); 999 if (in != null) { 1000 if (! isValidAllowEmpty(in)) 1001 throw new SchemaValidationException("Empty value not allowed."); 1002 if (! isValidPattern(in)) 1003 throw new SchemaValidationException("Value does not match expected pattern. Must match pattern: {0}", pattern.pattern()); 1004 if (! isValidEnum(in)) 1005 throw new SchemaValidationException("Value does not match one of the expected values. Must be one of the following: {0}", _enum); 1006 if (! isValidMaxLength(in)) 1007 throw new SchemaValidationException("Maximum length of value exceeded."); 1008 if (! isValidMinLength(in)) 1009 throw new SchemaValidationException("Minimum length of value not met."); 1010 } 1011 return in; 1012 } 1013 1014 /** 1015 * Throws a {@link ParseException} if the specified parsed output does not validate against this schema. 1016 * 1017 * @param o The parsed output. 1018 * @param bc The bean context used to detect POJO types. 1019 * @return The same object passed in. 1020 * @throws SchemaValidationException if the specified parsed output does not validate against this schema. 1021 */ 1022 @SuppressWarnings("rawtypes") 1023 public <T> T validateOutput(T o, BeanContext bc) throws SchemaValidationException { 1024 if (o == null) { 1025 if (! isValidRequired(o)) 1026 throw new SchemaValidationException("Required value not provided."); 1027 return o; 1028 } 1029 ClassMeta<?> cm = bc.getClassMetaForObject(o); 1030 switch (getType(cm)) { 1031 case ARRAY: { 1032 if (cm.isArray()) { 1033 if (! isValidMinItems(o)) 1034 throw new SchemaValidationException("Minimum number of items not met."); 1035 if (! isValidMaxItems(o)) 1036 throw new SchemaValidationException("Maximum number of items exceeded."); 1037 if (! isValidUniqueItems(o)) 1038 throw new SchemaValidationException("Duplicate items not allowed."); 1039 HttpPartSchema items = getItems(); 1040 if (items != null) 1041 for (int i = 0; i < Array.getLength(o); i++) 1042 items.validateOutput(Array.get(o, i), bc); 1043 } else if (cm.isCollection()) { 1044 Collection<?> c = (Collection<?>)o; 1045 if (! isValidMinItems(c)) 1046 throw new SchemaValidationException("Minimum number of items not met."); 1047 if (! isValidMaxItems(c)) 1048 throw new SchemaValidationException("Maximum number of items exceeded."); 1049 if (! isValidUniqueItems(c)) 1050 throw new SchemaValidationException("Duplicate items not allowed."); 1051 HttpPartSchema items = getItems(); 1052 if (items != null) 1053 for (Object o2 : c) 1054 items.validateOutput(o2, bc); 1055 } 1056 break; 1057 } 1058 case INTEGER: { 1059 if (cm.isNumber()) { 1060 Number n = (Number)o; 1061 if (! isValidMinimum(n)) 1062 throw new SchemaValidationException("Minimum value not met."); 1063 if (! isValidMaximum(n)) 1064 throw new SchemaValidationException("Maximum value exceeded."); 1065 if (! isValidMultipleOf(n)) 1066 throw new SchemaValidationException("Multiple-of not met."); 1067 } 1068 break; 1069 } 1070 case NUMBER: { 1071 if (cm.isNumber()) { 1072 Number n = (Number)o; 1073 if (! isValidMinimum(n)) 1074 throw new SchemaValidationException("Minimum value not met."); 1075 if (! isValidMaximum(n)) 1076 throw new SchemaValidationException("Maximum value exceeded."); 1077 if (! isValidMultipleOf(n)) 1078 throw new SchemaValidationException("Multiple-of not met."); 1079 } 1080 break; 1081 } 1082 case OBJECT: { 1083 if (cm.isMapOrBean()) { 1084 Map<?,?> m = cm.isMap() ? (Map<?,?>)o : bc.createSession().toBeanMap(o); 1085 if (! isValidMinProperties(m)) 1086 throw new SchemaValidationException("Minimum number of properties not met."); 1087 if (! isValidMaxProperties(m)) 1088 throw new SchemaValidationException("Maximum number of properties exceeded."); 1089 for (Map.Entry e : m.entrySet()) { 1090 String key = e.getKey().toString(); 1091 HttpPartSchema s2 = getProperty(key); 1092 if (s2 != null) 1093 s2.validateOutput(e.getValue(), bc); 1094 } 1095 } else if (cm.isBean()) { 1096 1097 } 1098 break; 1099 } 1100 case BOOLEAN: 1101 case FILE: 1102 case STRING: 1103 case NO_TYPE: 1104 break; 1105 } 1106 return o; 1107 } 1108 1109 //----------------------------------------------------------------------------------------------------------------- 1110 // Helper methods. 1111 //----------------------------------------------------------------------------------------------------------------- 1112 1113 private boolean isValidRequired(Object x) { 1114 return x != null || ! required; 1115 } 1116 1117 private boolean isValidMinProperties(Map<?,?> x) { 1118 return minProperties == null || x.size() >= minProperties; 1119 } 1120 1121 private boolean isValidMaxProperties(Map<?,?> x) { 1122 return maxProperties == null || x.size() <= maxProperties; 1123 } 1124 1125 private boolean isValidMinimum(Number x) { 1126 if (x instanceof Integer || x instanceof AtomicInteger) 1127 return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum)); 1128 if (x instanceof Short || x instanceof Byte) 1129 return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum)); 1130 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1131 return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum)); 1132 if (x instanceof Float) 1133 return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum)); 1134 if (x instanceof Double || x instanceof BigDecimal) 1135 return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum)); 1136 return true; 1137 } 1138 1139 private boolean isValidMaximum(Number x) { 1140 if (x instanceof Integer || x instanceof AtomicInteger) 1141 return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum)); 1142 if (x instanceof Short || x instanceof Byte) 1143 return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum)); 1144 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1145 return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum)); 1146 if (x instanceof Float) 1147 return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum)); 1148 if (x instanceof Double || x instanceof BigDecimal) 1149 return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum)); 1150 return true; 1151 } 1152 1153 private boolean isValidMultipleOf(Number x) { 1154 if (x instanceof Integer || x instanceof AtomicInteger) 1155 return multipleOf == null || x.intValue() % multipleOf.intValue() == 0; 1156 if (x instanceof Short || x instanceof Byte) 1157 return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0; 1158 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1159 return multipleOf == null || x.longValue() % multipleOf.longValue() == 0; 1160 if (x instanceof Float) 1161 return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0; 1162 if (x instanceof Double || x instanceof BigDecimal) 1163 return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0; 1164 return true; 1165 } 1166 1167 private boolean isValidAllowEmpty(String x) { 1168 return allowEmptyValue || isNotEmpty(x); 1169 } 1170 1171 private boolean isValidPattern(String x) { 1172 return pattern == null || pattern.matcher(x).matches(); 1173 } 1174 1175 private boolean isValidEnum(String x) { 1176 return _enum.isEmpty() || _enum.contains(x); 1177 } 1178 1179 private boolean isValidMinLength(String x) { 1180 return minLength == null || x.length() >= minLength; 1181 } 1182 1183 private boolean isValidMaxLength(String x) { 1184 return maxLength == null || x.length() <= maxLength; 1185 } 1186 1187 private boolean isValidMinItems(Object x) { 1188 return minItems == null || Array.getLength(x) >= minItems; 1189 } 1190 1191 private boolean isValidMaxItems(Object x) { 1192 return maxItems == null || Array.getLength(x) <= maxItems; 1193 } 1194 1195 private boolean isValidUniqueItems(Object x) { 1196 if (uniqueItems) { 1197 Set<Object> s = new HashSet<>(); 1198 for (int i = 0; i < Array.getLength(x); i++) { 1199 Object o = Array.get(x, i); 1200 if (! s.add(o)) 1201 return false; 1202 } 1203 } 1204 return true; 1205 } 1206 1207 private boolean isValidMinItems(Collection<?> x) { 1208 return minItems == null || x.size() >= minItems; 1209 } 1210 1211 private boolean isValidMaxItems(Collection<?> x) { 1212 return maxItems == null || x.size() <= maxItems; 1213 } 1214 1215 private boolean isValidUniqueItems(Collection<?> x) { 1216 if (uniqueItems && ! (x instanceof Set)) { 1217 Set<Object> s = new HashSet<>(); 1218 for (Object o : x) 1219 if (! s.add(o)) 1220 return false; 1221 } 1222 return true; 1223 } 1224 1225 /** 1226 * Returns the schema information for the specified property. 1227 * 1228 * @param name The property name. 1229 * @return The schema information for the specified property, or <jk>null</jk> if properties are not defined on this schema. 1230 */ 1231 public HttpPartSchema getProperty(String name) { 1232 if (properties != null) { 1233 HttpPartSchema schema = properties.get(name); 1234 if (schema != null) 1235 return schema; 1236 } 1237 return additionalProperties; 1238 } 1239 1240 /** 1241 * Returns <jk>true</jk> if this schema has properties associated with it. 1242 * 1243 * @return <jk>true</jk> if this schema has properties associated with it. 1244 */ 1245 public boolean hasProperties() { 1246 return properties != null || additionalProperties != null; 1247 } 1248 1249 private static <T> Set<T> copy(Set<T> in) { 1250 return in == null ? Collections.EMPTY_SET : unmodifiableSet(new LinkedHashSet<>(in)); 1251 } 1252 1253 private static Map<String,HttpPartSchema> build(Map<String,HttpPartSchemaBuilder> in, boolean noValidate) { 1254 if (in == null) 1255 return null; 1256 Map<String,HttpPartSchema> m = new LinkedHashMap<>(); 1257 for (Map.Entry<String,HttpPartSchemaBuilder> e : in.entrySet()) 1258 m.put(e.getKey(), e.getValue().noValidate(noValidate).build()); 1259 return unmodifiableMap(m); 1260 } 1261 1262 private static HttpPartSchema build(HttpPartSchemaBuilder in, boolean noValidate) { 1263 return in == null ? null : in.noValidate(noValidate).build(); 1264 } 1265 1266 1267 //----------------------------------------------------------------------------------------------------------------- 1268 // Helper methods. 1269 //----------------------------------------------------------------------------------------------------------------- 1270 1271 private boolean resolve(Boolean b) { 1272 return b == null ? false : b; 1273 } 1274 1275 final static Set<String> toSet(String[] s) { 1276 return toSet(joinnl(s)); 1277 } 1278 1279 final static Set<String> toSet(String s) { 1280 if (isEmpty(s)) 1281 return null; 1282 Set<String> set = new ASet<>(); 1283 try { 1284 for (Object o : StringUtils.parseListOrCdl(s)) 1285 set.add(o.toString()); 1286 } catch (ParseException e) { 1287 throw new RuntimeException(e); 1288 } 1289 return set; 1290 } 1291 1292 final static Number toNumber(String s) { 1293 try { 1294 if (isNotEmpty(s)) 1295 return parseNumber(s, Number.class); 1296 return null; 1297 } catch (ParseException e) { 1298 throw new RuntimeException(e); 1299 } 1300 } 1301 1302 final static ObjectMap toObjectMap(String[] ss) { 1303 String s = joinnl(ss); 1304 if (s.isEmpty()) 1305 return null; 1306 if (! isObjectMap(s, true)) 1307 s = "{" + s + "}"; 1308 try { 1309 return new ObjectMap(s); 1310 } catch (ParseException e) { 1311 throw new RuntimeException(e); 1312 } 1313 } 1314 1315 @Override 1316 public String toString() { 1317 try { 1318 ObjectMap m = new ObjectMap() 1319 .appendSkipEmpty("name", name) 1320 .appendSkipEmpty("type", type) 1321 .appendSkipEmpty("format", format) 1322 .appendSkipEmpty("codes", codes) 1323 .appendSkipEmpty("default", _default) 1324 .appendSkipEmpty("enum", _enum) 1325 .appendSkipEmpty("properties", properties) 1326 .appendSkipFalse("allowEmptyValue", allowEmptyValue) 1327 .appendSkipFalse("exclusiveMaximum", exclusiveMaximum) 1328 .appendSkipFalse("exclusiveMinimum", exclusiveMinimum) 1329 .appendSkipFalse("required", required) 1330 .appendSkipFalse("uniqueItems", uniqueItems) 1331 .appendSkipFalse("skipIfEmpty", skipIfEmpty) 1332 .appendIf(collectionFormat != CollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat) 1333 .appendSkipEmpty("pattern", pattern) 1334 .appendSkipNull("items", items) 1335 .appendSkipNull("additionalProperties", additionalProperties) 1336 .appendSkipMinusOne("maximum", maximum) 1337 .appendSkipMinusOne("minimum", minimum) 1338 .appendSkipMinusOne("multipleOf", multipleOf) 1339 .appendSkipMinusOne("maxLength", maxLength) 1340 .appendSkipMinusOne("minLength", minLength) 1341 .appendSkipMinusOne("maxItems", maxItems) 1342 .appendSkipMinusOne("minItems", minItems) 1343 .appendSkipMinusOne("maxProperties", maxProperties) 1344 .appendSkipMinusOne("minProperties", minProperties) 1345 .append("parsedType", parsedType) 1346 ; 1347 return m.toString(); 1348 } catch (Exception e) { 1349 e.printStackTrace(); 1350 return ""; 1351 } 1352 } 1353}