001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2020-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds; 037 038 039 040import java.io.Serializable; 041import java.util.Date; 042import java.util.LinkedHashMap; 043import java.util.Map; 044 045import com.unboundid.ldap.sdk.Modification; 046import com.unboundid.ldap.sdk.ModificationType; 047import com.unboundid.ldap.sdk.ModifyRequest; 048import com.unboundid.ldap.sdk.unboundidds.extensions. 049 PasswordPolicyStateExtendedRequest; 050import com.unboundid.util.Mutable; 051import com.unboundid.util.NotNull; 052import com.unboundid.util.Nullable; 053import com.unboundid.util.StaticUtils; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.json.JSONBoolean; 057import com.unboundid.util.json.JSONNull; 058import com.unboundid.util.json.JSONObject; 059import com.unboundid.util.json.JSONString; 060import com.unboundid.util.json.JSONValue; 061 062import static com.unboundid.ldap.sdk.unboundidds. 063 ModifiablePasswordPolicyStateJSONField.*; 064 065 066 067/** 068 * This class provides support for generating a JSON object that may be included 069 * in a REPLACE modification to the ds-pwp-modifiable-state-json operational 070 * attribute to manipulate elements in the user's password policy state. 071 * <BR> 072 * <BLOCKQUOTE> 073 * <B>NOTE:</B> This class, and other classes within the 074 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 075 * supported for use against Ping Identity, UnboundID, and 076 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 077 * for proprietary functionality or for external specifications that are not 078 * considered stable or mature enough to be guaranteed to work in an 079 * interoperable way with other types of LDAP servers. 080 * </BLOCKQUOTE> 081 * 082 * @see ModifiablePasswordPolicyStateJSON 083 * @see ModifiablePasswordPolicyStateJSONField 084 * @see PasswordPolicyStateJSON 085 * @see PasswordPolicyStateExtendedRequest 086 */ 087@Mutable() 088@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 089public final class ModifiablePasswordPolicyStateJSONBuilder 090 implements Serializable 091{ 092 /** 093 * The serial version UID for this serializable class. 094 */ 095 private static final long serialVersionUID = -1059372199527400142L; 096 097 098 099 // A flag that indicates whether the user's account is disabled. 100 @Nullable private Boolean accountIsDisabled; 101 102 // A flag that indicates whether the user's account is failure-locked. 103 @Nullable private Boolean accountIsFailureLocked; 104 105 // A flag that indicates whether the user must change their password. 106 @Nullable private Boolean mustChangePassword; 107 108 // A timestamp representing the user's account activation time. 109 @Nullable private Long accountActivationTime; 110 111 // A timestamp representing the user's account expiration time. 112 @Nullable private Long accountExpirationTime; 113 114 // A timestamp representing the time the user's password was last changed. 115 @Nullable private Long passwordChangedTime; 116 117 // A timestamp representing the time the user was first warned about an 118 // upcoming password expiration. 119 @Nullable private Long passwordExpirationWarnedTime; 120 121 122 123 /** 124 * Creates a new builder instance with none of the fields set. 125 */ 126 public ModifiablePasswordPolicyStateJSONBuilder() 127 { 128 accountIsDisabled = null; 129 accountIsFailureLocked = null; 130 mustChangePassword = null; 131 accountActivationTime = null; 132 accountExpirationTime = null; 133 passwordChangedTime = null; 134 passwordExpirationWarnedTime = null; 135 } 136 137 138 139 /** 140 * Creates a new builder instance with values set from the provided modifiable 141 * password policy state object. 142 * 143 * @param state The modifiable password policy state object to use to set 144 * the initial values for all of the fields. 145 */ 146 public ModifiablePasswordPolicyStateJSONBuilder( 147 @NotNull final ModifiablePasswordPolicyStateJSON state) 148 { 149 accountIsDisabled = state.getAccountIsDisabled(); 150 accountIsFailureLocked = state.getAccountIsFailureLocked(); 151 mustChangePassword = state.getMustChangePassword(); 152 accountActivationTime = state.getAccountActivationTime(); 153 accountExpirationTime = state.getAccountExpirationTime(); 154 passwordChangedTime = state.getPasswordChangedTime(); 155 passwordExpirationWarnedTime = state.getPasswordExpirationWarnedTime(); 156 } 157 158 159 160 /** 161 * Retrieves a timestamp that indicates the time the user's password was last 162 * changed. 163 * 164 * @return A non-negative value that represents the password changed time in 165 * number of milliseconds since the epoch (the same format used by 166 * {@code System.currentTimeMillis}), a negative value if the field 167 * was present with a JSON null value (indicating that the user 168 * doesn't have a password changed time), or {@code null} if the 169 * field was not included in the JSON object. 170 */ 171 @Nullable() 172 public Long getPasswordChangedTime() 173 { 174 return passwordChangedTime; 175 } 176 177 178 179 /** 180 * Updates this builder with a new password changed time. 181 * 182 * @param passwordChangedTime 183 * The new password changed time value to use. It may be a 184 * positive value representing the number of milliseconds since 185 * the epoch (the same format used by 186 * {@code System.currentTimeMillis}) for the password changed 187 * time, a negative value to indicate that any existing password 188 * changed time value should be cleared, or {@code null} if the 189 * value should not be set in this builder (and therefore omitted 190 * from any JSON object or 191 * {@link ModifiablePasswordPolicyStateJSON} that is created). 192 * 193 * @return This builder object. 194 */ 195 @NotNull() 196 public ModifiablePasswordPolicyStateJSONBuilder setPasswordChangedTime( 197 @Nullable final Long passwordChangedTime) 198 { 199 if ((passwordChangedTime != null) && (passwordChangedTime < 0L)) 200 { 201 this.passwordChangedTime = -1L; 202 } 203 else 204 { 205 this.passwordChangedTime = passwordChangedTime; 206 } 207 208 return this; 209 } 210 211 212 213 /** 214 * Updates this builder with a new password changed time. 215 * 216 * @param passwordChangedTime 217 * The new password changed time value to use. It may be 218 * {@code null} if any existing password changed time value 219 * should be cleared. 220 * 221 * @return This builder object. 222 */ 223 @NotNull() 224 public ModifiablePasswordPolicyStateJSONBuilder setPasswordChangedTime( 225 @Nullable final Date passwordChangedTime) 226 { 227 if (passwordChangedTime == null) 228 { 229 this.passwordChangedTime = -1L; 230 } 231 else 232 { 233 this.passwordChangedTime = passwordChangedTime.getTime(); 234 } 235 236 return this; 237 } 238 239 240 241 /** 242 * Updates this builder so that any existing password changed time value will 243 * be cleared in the user entry. 244 * 245 * @return This builder object. 246 */ 247 @NotNull() 248 public ModifiablePasswordPolicyStateJSONBuilder clearPasswordChangedTime() 249 { 250 passwordChangedTime = -1L; 251 return this; 252 } 253 254 255 256 /** 257 * Retrieves the value of a flag that indicates whether the user's account has 258 * been administratively disabled. 259 * 260 * @return {@code Boolean.TRUE} if the account has been administratively 261 * disabled, {@code Boolean.FALSE} if the account has not been 262 * administratively disabled, or {@code null} if this flag was not 263 * included in the password policy state JSON object. 264 */ 265 @Nullable() 266 public Boolean getAccountIsDisabled() 267 { 268 return accountIsDisabled; 269 } 270 271 272 273 /** 274 * Updates this builder with a new value for the flag indicating whether the 275 * user's account should be considered disabled. 276 * 277 * @param accountIsDisabled 278 * The new account is disabled value to use. It may be 279 * {@code null} if the value should not be set in this builder 280 * (and therefore omitted from any JSON object or 281 * {@link ModifiablePasswordPolicyStateJSON} that is created). 282 * 283 * @return This builder object. 284 */ 285 @NotNull() 286 public ModifiablePasswordPolicyStateJSONBuilder setAccountIsDisabled( 287 @Nullable final Boolean accountIsDisabled) 288 { 289 this.accountIsDisabled = accountIsDisabled; 290 return this; 291 } 292 293 294 295 /** 296 * Retrieves a timestamp that indicates the time the user's account became (or 297 * will become) active. 298 * 299 * @return A non-negative value that represents the account activation time 300 * in number of milliseconds since the epoch (the same format used by 301 * {@code System.currentTimeMillis}), a negative value if the field 302 * was present with a JSON null value (indicating that the user 303 * doesn't have an account activation time), or {@code null} if the 304 * field was not included in the JSON object. 305 */ 306 @Nullable() 307 public Long getAccountActivationTime() 308 { 309 return accountActivationTime; 310 } 311 312 313 314 /** 315 * Updates this builder with a new account activation time. 316 * 317 * @param accountActivationTime 318 * The new account activation time value to use. It may be a 319 * positive value representing the number of milliseconds since 320 * the epoch (the same format used by 321 * {@code System.currentTimeMillis}) for the account activation 322 * time, a negative value to indicate that any existing account 323 * activation time value should be cleared, or {@code null} if 324 * the value should not be set in this builder (and therefore 325 * omitted from any JSON object or 326 * {@link ModifiablePasswordPolicyStateJSON} that is created). 327 * 328 * @return This builder object. 329 */ 330 @NotNull() 331 public ModifiablePasswordPolicyStateJSONBuilder setAccountActivationTime( 332 @Nullable final Long accountActivationTime) 333 { 334 if ((accountActivationTime != null) && (accountActivationTime < 0L)) 335 { 336 this.accountActivationTime = -1L; 337 } 338 else 339 { 340 this.accountActivationTime = accountActivationTime; 341 } 342 343 return this; 344 } 345 346 347 348 /** 349 * Updates this builder with a new account activation time. 350 * 351 * @param accountActivationTime 352 * The new account activation time value to use. It may be 353 * {@code null} if any existing account activation time value 354 * should be cleared. 355 * 356 * @return This builder object. 357 */ 358 @NotNull() 359 public ModifiablePasswordPolicyStateJSONBuilder setAccountActivationTime( 360 @Nullable final Date accountActivationTime) 361 { 362 if (accountActivationTime == null) 363 { 364 this.accountActivationTime = -1L; 365 } 366 else 367 { 368 this.accountActivationTime = accountActivationTime.getTime(); 369 } 370 371 return this; 372 } 373 374 375 376 /** 377 * Updates this builder so that any existing account activation time value 378 * will be cleared in the user entry. 379 * 380 * @return This builder object. 381 */ 382 @NotNull() 383 public ModifiablePasswordPolicyStateJSONBuilder clearAccountActivationTime() 384 { 385 accountActivationTime = -1L; 386 return this; 387 } 388 389 390 391 /** 392 * Retrieves a timestamp that indicates the time the user's account will (or 393 * did) expire. 394 * 395 * @return A non-negative value that represents the account expiration time 396 * in number of milliseconds since the epoch (the same format used by 397 * {@code System.currentTimeMillis}), a negative value if the field 398 * was present with a JSON null value (indicating that the user 399 * doesn't have an account expiration time), or {@code null} if the 400 * field was not included in the JSON object. 401 */ 402 @Nullable() 403 public Long getAccountExpirationTime() 404 { 405 return accountExpirationTime; 406 } 407 408 409 410 /** 411 * Updates this builder with a new account expiration time. 412 * 413 * @param accountExpirationTime 414 * The new account expiration time value to use. It may be a 415 * positive value representing the number of milliseconds since 416 * the epoch (the same format used by 417 * {@code System.currentTimeMillis}) for the account expiration 418 * time, a negative value to indicate that any existing account 419 * expiration time value should be cleared, or {@code null} if 420 * the value should not be set in this builder (and therefore 421 * omitted from any JSON object or 422 * {@link ModifiablePasswordPolicyStateJSON} that is created). 423 * 424 * @return This builder object. 425 */ 426 @NotNull() 427 public ModifiablePasswordPolicyStateJSONBuilder setAccountExpirationTime( 428 @Nullable final Long accountExpirationTime) 429 { 430 if ((accountExpirationTime != null) && (accountExpirationTime < 0L)) 431 { 432 this.accountExpirationTime = -1L; 433 } 434 else 435 { 436 this.accountExpirationTime = accountExpirationTime; 437 } 438 439 return this; 440 } 441 442 443 444 /** 445 * Updates this builder with a new account expiration time. 446 * 447 * @param accountExpirationTime 448 * The new account expiration time value to use. It may be 449 * {@code null} if any existing account expiration time value 450 * should be cleared. 451 * 452 * @return This builder object. 453 */ 454 @NotNull() 455 public ModifiablePasswordPolicyStateJSONBuilder setAccountExpirationTime( 456 @Nullable final Date accountExpirationTime) 457 { 458 if (accountExpirationTime == null) 459 { 460 this.accountExpirationTime = -1L; 461 } 462 else 463 { 464 this.accountExpirationTime = accountExpirationTime.getTime(); 465 } 466 467 return this; 468 } 469 470 471 472 /** 473 * Updates this builder so that any existing account expiration time value 474 * will be cleared in the user entry. 475 * 476 * @return This builder object. 477 */ 478 @NotNull() 479 public ModifiablePasswordPolicyStateJSONBuilder clearAccountExpirationTime() 480 { 481 accountExpirationTime = -1L; 482 return this; 483 } 484 485 486 487 /** 488 * Retrieves the value of a flag that indicates whether the user account is 489 * currently locked as a result of too many failed authentication attempts. 490 * 491 * @return {@code Boolean.TRUE} if the user account is locked as a result of 492 * too many failed authentication attempts, {@code Boolean.FALSE} if 493 * the user account is not locked because of too many failed 494 * authentication attempts, or {@code null} if this flag was not 495 * included in the password policy state JSON object. 496 */ 497 @Nullable() 498 public Boolean getAccountIsFailureLocked() 499 { 500 return accountIsFailureLocked; 501 } 502 503 504 505 /** 506 * Updates this builder with a new value for the flag indicating whether the 507 * user's account should be considered locked as a result of too many failed 508 * authentication attempts. Note that the server may reject an attempt to set 509 * the value to {@code Boolean.TRUE} if failure lockout is not enabled in the 510 * server. 511 * 512 * @param accountIsFailureLocked 513 * The new account is failure-locked value to use. It may be 514 * {@code null} if the value should not be set in this builder 515 * (and therefore omitted from any JSON object or 516 * {@link ModifiablePasswordPolicyStateJSON} that is created). 517 * 518 * @return This builder object. 519 */ 520 @NotNull() 521 public ModifiablePasswordPolicyStateJSONBuilder setAccountIsFailureLocked( 522 @Nullable final Boolean accountIsFailureLocked) 523 { 524 this.accountIsFailureLocked = accountIsFailureLocked; 525 return this; 526 } 527 528 529 530 /** 531 * Retrieves a timestamp that indicates the time the user was first warned 532 * about an upcoming password expiration. 533 * 534 * @return A non-negative value that represents the password expiration 535 * warned time in number of milliseconds since the epoch (the same 536 * format used by {@code System.currentTimeMillis}), a negative value 537 * if the field was present with a JSON null value (indicating that 538 * the user doesn't have an password expiration warned time), or 539 * {@code null} if the field was not included in the JSON object. 540 */ 541 @Nullable() 542 public Long getPasswordExpirationWarnedTime() 543 { 544 return passwordExpirationWarnedTime; 545 } 546 547 548 549 /** 550 * Updates this builder with a new password expiration warned time. 551 * 552 * @param passwordExpirationWarnedTime 553 * The new password expiration warned time value to use. It may 554 * be a positive value representing the number of milliseconds 555 * since the epoch (the same format used by 556 * {@code System.currentTimeMillis}) for the password expiration 557 * warned time, a negative value to indicate that any existing 558 * password expiration warned time value should be cleared, or 559 * {@code null} if the value should not be set in this builder 560 * (and therefore omitted from any JSON object or 561 * {@link ModifiablePasswordPolicyStateJSON} that is created). 562 * 563 * @return This builder object. 564 */ 565 @NotNull() 566 public ModifiablePasswordPolicyStateJSONBuilder 567 setPasswordExpirationWarnedTime( 568 @Nullable final Long passwordExpirationWarnedTime) 569 { 570 if ((passwordExpirationWarnedTime != null) && 571 (passwordExpirationWarnedTime < 0L)) 572 { 573 this.passwordExpirationWarnedTime = -1L; 574 } 575 else 576 { 577 this.passwordExpirationWarnedTime = passwordExpirationWarnedTime; 578 } 579 580 return this; 581 } 582 583 584 585 /** 586 * Updates this builder with a new password expiration warned time. 587 * 588 * @param passwordExpirationWarnedTime 589 * The new password expiration warned time value to use. It may 590 * be {@code null} if any existing password expiration warned 591 * time value should be cleared. 592 * 593 * @return This builder object. 594 */ 595 @NotNull() 596 public ModifiablePasswordPolicyStateJSONBuilder 597 setPasswordExpirationWarnedTime( 598 @Nullable final Date passwordExpirationWarnedTime) 599 { 600 if (passwordExpirationWarnedTime == null) 601 { 602 this.passwordExpirationWarnedTime = -1L; 603 } 604 else 605 { 606 this.passwordExpirationWarnedTime = 607 passwordExpirationWarnedTime.getTime(); 608 } 609 610 return this; 611 } 612 613 614 615 /** 616 * Updates this builder so that any existing password expiration warned time 617 * value will be cleared in the user entry. 618 * 619 * @return This builder object. 620 */ 621 @NotNull() 622 public ModifiablePasswordPolicyStateJSONBuilder 623 clearPasswordExpirationWarnedTime() 624 { 625 passwordExpirationWarnedTime = -1L; 626 return this; 627 } 628 629 630 631 /** 632 * Retrieves the value of a flag that indicates whether the user must change 633 * their password before they will be allowed to perform any other operations 634 * in the server. 635 * 636 * @return {@code Boolean.TRUE} if the user must change their password before 637 * they will be allowed to perform any other operations in the 638 * server, {@code Boolean.FALSE} if the user is not required to 639 * change their password, or {@code null} if this flag was not 640 * included in the password policy state JSON object. 641 */ 642 @Nullable() 643 public Boolean getMustChangePassword() 644 { 645 return mustChangePassword; 646 } 647 648 649 650 /** 651 * Updates this builder with a new value for the flag indicating whether the 652 * user must change their password before they will be allowed to perform 653 * other operations in the server. 654 * 655 * @param mustChangePassword 656 * The new must change password value to use. It may be 657 * {@code null} if the value should not be set in this builder 658 * (and therefore omitted from any JSON object or 659 * {@link ModifiablePasswordPolicyStateJSON} that is created). 660 * 661 * @return This builder object. 662 */ 663 @NotNull() 664 public ModifiablePasswordPolicyStateJSONBuilder setMustChangePassword( 665 @Nullable final Boolean mustChangePassword) 666 { 667 this.mustChangePassword = mustChangePassword; 668 return this; 669 } 670 671 672 673 /** 674 * Retrieves a JSON object with an encoded representation of the modifiable 675 * password policy state created from this builder. 676 * 677 * @return A JSON object with an encoded representation of the modifiable 678 * password policy state created from this builder. 679 */ 680 @NotNull() 681 public JSONObject toJSONObject() 682 { 683 final Map<String,JSONValue> fields = 684 new LinkedHashMap<>(StaticUtils.computeMapCapacity(7)); 685 686 if (passwordChangedTime != null) 687 { 688 if (passwordChangedTime >= 0L) 689 { 690 fields.put(PASSWORD_CHANGED_TIME.getFieldName(), 691 new JSONString(StaticUtils.encodeRFC3339Time( 692 passwordChangedTime))); 693 } 694 else 695 { 696 fields.put(PASSWORD_CHANGED_TIME.getFieldName(), JSONNull.NULL); 697 } 698 } 699 700 if (accountIsDisabled != null) 701 { 702 fields.put(ACCOUNT_IS_DISABLED.getFieldName(), 703 new JSONBoolean(accountIsDisabled)); 704 } 705 706 if (accountActivationTime != null) 707 { 708 if (accountActivationTime >= 0L) 709 { 710 fields.put(ACCOUNT_ACTIVATION_TIME.getFieldName(), 711 new JSONString(StaticUtils.encodeRFC3339Time( 712 accountActivationTime))); 713 } 714 else 715 { 716 fields.put(ACCOUNT_ACTIVATION_TIME.getFieldName(), JSONNull.NULL); 717 } 718 } 719 720 if (accountExpirationTime != null) 721 { 722 if (accountExpirationTime >= 0L) 723 { 724 fields.put(ACCOUNT_EXPIRATION_TIME.getFieldName(), 725 new JSONString(StaticUtils.encodeRFC3339Time( 726 accountExpirationTime))); 727 } 728 else 729 { 730 fields.put(ACCOUNT_EXPIRATION_TIME.getFieldName(), JSONNull.NULL); 731 } 732 } 733 734 if (accountIsFailureLocked != null) 735 { 736 fields.put(ACCOUNT_IS_FAILURE_LOCKED.getFieldName(), 737 new JSONBoolean(accountIsFailureLocked)); 738 } 739 740 if (passwordExpirationWarnedTime != null) 741 { 742 if (passwordExpirationWarnedTime >= 0L) 743 { 744 fields.put(PASSWORD_EXPIRATION_WARNED_TIME.getFieldName(), 745 new JSONString(StaticUtils.encodeRFC3339Time( 746 passwordExpirationWarnedTime))); 747 } 748 else 749 { 750 fields.put(PASSWORD_EXPIRATION_WARNED_TIME.getFieldName(), 751 JSONNull.NULL); 752 } 753 } 754 755 if (mustChangePassword != null) 756 { 757 fields.put(MUST_CHANGE_PASSWORD.getFieldName(), 758 new JSONBoolean(mustChangePassword)); 759 } 760 761 return new JSONObject(fields); 762 } 763 764 765 766 /** 767 * Creates a {@code ModifiablePasswordPolicyStateJSON} object from the 768 * contents of this builder. 769 * 770 * @return The {@code ModifiablePasswordPolicyStateJSON} object created from 771 * the contents of this builder. 772 */ 773 @NotNull() 774 public ModifiablePasswordPolicyStateJSON build() 775 { 776 return new ModifiablePasswordPolicyStateJSON(toJSONObject()); 777 } 778 779 780 781 /** 782 * Creates a modify request that may be used to update the specified user with 783 * the appropriate password policy state changes from this builder. 784 * 785 * @param userDN The DN of the user whose password policy state should be 786 * updated. 787 * 788 * @return A modify request that may be used to update the specified user 789 * with the appropriate password policy state changes from this 790 * builder. 791 */ 792 @NotNull() 793 public ModifyRequest toModifyRequest(@NotNull final String userDN) 794 { 795 return new ModifyRequest(userDN, 796 new Modification( 797 ModificationType.REPLACE, 798 ModifiablePasswordPolicyStateJSON. 799 MODIFIABLE_PASSWORD_POLICY_STATE_JSON_ATTRIBUTE, 800 toJSONObject().toSingleLineString())); 801 } 802 803 804 805 /** 806 * Retrieves a string representation of the password policy state information. 807 * 808 * @return A string representation of the password policy state information. 809 */ 810 @Override() 811 @NotNull() 812 public String toString() 813 { 814 return toJSONObject().toString(); 815 } 816}