001/* 002 * Copyright 2022-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2022-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) 2022-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.controls; 037 038 039 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.List; 044 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.DecodeableControl; 048import com.unboundid.ldap.sdk.IntermediateResponse; 049import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 050import com.unboundid.ldap.sdk.LDAPException; 051import com.unboundid.ldap.sdk.LDAPResult; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.ldap.sdk.SearchResultEntry; 054import com.unboundid.ldap.sdk.SearchResultReference; 055import com.unboundid.util.Debug; 056import com.unboundid.util.NotMutable; 057import com.unboundid.util.NotNull; 058import com.unboundid.util.Nullable; 059import com.unboundid.util.StaticUtils; 060import com.unboundid.util.ThreadSafety; 061import com.unboundid.util.ThreadSafetyLevel; 062import com.unboundid.util.json.JSONArray; 063import com.unboundid.util.json.JSONField; 064import com.unboundid.util.json.JSONObject; 065import com.unboundid.util.json.JSONValue; 066 067import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 068 069 070 071/** 072 * This class provides an implementation of a response control that may be used 073 * to encapsulate a set of one or more other controls represented as JSON 074 * objects. 075 * <BR> 076 * <BLOCKQUOTE> 077 * <B>NOTE:</B> This class, and other classes within the 078 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 079 * supported for use against Ping Identity, UnboundID, and 080 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 081 * for proprietary functionality or for external specifications that are not 082 * considered stable or mature enough to be guaranteed to work in an 083 * interoperable way with other types of LDAP servers. 084 * </BLOCKQUOTE> 085 * <BR> 086 * This control has an OID of 1.3.6.1.4.1.30221.2.5.65, and it takes a value 087 * that must be a JSON object that contains a single field, {@code controls}, 088 * whose value is an array of the JSON representations of the response controls 089 * returned by the server. The JSON representations of the controls is the one 090 * generated by the {@link Control#toJSONControl()} method, and is the one 091 * expected by the {@link Control#decodeJSONControl} method. In particular, 092 * each control should have at least an {@code oid} field that specifies the OID 093 * for the control, and a {@code criticality} field that indicates whether the 094 * control is considered critical. If the control has a value, then either the 095 * {@code value-base64} field should be used to provide a base64-encoded 096 * representation of the value, or the {@code value-json} field should be used 097 * to provide a JSON-formatted representation of the value for controls that 098 * support it. 099 * <BR><BR> 100 * As with all response controls, the criticality for JSON-formatted response 101 * controls should be {@code false}. 102 * 103 * @see JSONFormattedRequestControl 104 */ 105@NotMutable() 106@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 107public final class JSONFormattedResponseControl 108 extends Control 109 implements DecodeableControl 110{ 111 /** 112 * The OID (1.3.6.1.4.1.30221.2.5.64) for the JSON-formatted response control. 113 */ 114 @NotNull public static final String JSON_FORMATTED_RESPONSE_OID = 115 "1.3.6.1.4.1.30221.2.5.65"; 116 117 118 119 /** 120 * The name of the field used to hold the array of embedded controls in the 121 * JSON representation of this control. 122 */ 123 @NotNull private static final String JSON_FIELD_CONTROLS = "controls"; 124 125 126 127 /** 128 * The serial version UID for this serializable class. 129 */ 130 private static final long serialVersionUID = -5437134160392183276L; 131 132 133 134 // A JSON object with an encoded representation of the value for this control. 135 @NotNull private final JSONObject encodedValue; 136 137 // A list of the JSON objects representing embedded controls within this 138 // response control. 139 @NotNull private final List<JSONObject> controlObjects; 140 141 142 143 /** 144 * Creates a new empty control instance that is intended to be used only for 145 * decoding controls via the {@code DecodeableControl} interface. 146 */ 147 JSONFormattedResponseControl() 148 { 149 encodedValue = null; 150 controlObjects = Collections.emptyList(); 151 } 152 153 154 155 /** 156 * Creates a new instance of this control with the specified criticality and 157 * set of controls. 158 * 159 * @param encodedValue A JSON object with an encoded representation of 160 * the value for this control. It may be 161 * {@code null} if the control should not have a 162 * value. 163 * @param controlObjects A collection of JSON objects representing the 164 * response controls generated by the server. It 165 * must not be {@code null}, and should not be empty. 166 */ 167 private JSONFormattedResponseControl( 168 @NotNull final JSONObject encodedValue, 169 @NotNull final List<JSONObject> controlObjects) 170 { 171 super(JSON_FORMATTED_RESPONSE_OID, false, 172 new ASN1OctetString(encodedValue.toSingleLineString())); 173 174 this.encodedValue = encodedValue; 175 this.controlObjects = controlObjects; 176 } 177 178 179 180 /** 181 * Creates a new {@code JSONFormattedResponseControl} with the provided set of 182 * embedded controls. 183 * 184 * @param controls The collection of controls to embed within this 185 * response. This must not be {@code null} or empty. 186 * 187 * @return The {@code JSONFormattedResponseControl} that was created. 188 */ 189 @NotNull() 190 public static JSONFormattedResponseControl createWithControls( 191 @NotNull final Control... controls) 192 { 193 return createWithControls(StaticUtils.toList(controls)); 194 } 195 196 197 198 /** 199 * Creates a new {@code JSONFormattedResponseControl} with the provided set of 200 * embedded controls. 201 * 202 * @param controls The collection of controls to embed within this 203 * response control. This must not be {@code null} or 204 * empty. 205 * 206 * @return The {@code JSONFormattedResponseControl} that was created. 207 */ 208 @NotNull() 209 public static JSONFormattedResponseControl createWithControls( 210 @NotNull final Collection<Control> controls) 211 { 212 final List<JSONObject> controlObjects = new ArrayList<>(controls.size()); 213 for (final Control c : controls) 214 { 215 controlObjects.add(c.toJSONControl()); 216 } 217 218 final JSONObject encodedValue = new JSONObject( 219 new JSONField(JSON_FIELD_CONTROLS, new JSONArray(controlObjects))); 220 221 return new JSONFormattedResponseControl(encodedValue, 222 Collections.unmodifiableList(controlObjects)); 223 } 224 225 226 227 /** 228 * Creates a new {@code JSONFormattedResponseControl} with the provided set of 229 * embedded JSON objects. 230 * 231 * @param controlObjects The collection of JSON objects that represent the 232 * encoded controls to embed within this response 233 * control. This must not be {@code null} or empty. 234 * Note that no attempt will be made to validate the 235 * JSON objects as controls. 236 * 237 * @return The {@code JSONFormattedResponseControl} that was created. 238 */ 239 @NotNull() 240 public static JSONFormattedResponseControl createWithControlObjects( 241 @Nullable final JSONObject... controlObjects) 242 { 243 return createWithControlObjects(StaticUtils.toList(controlObjects)); 244 } 245 246 247 248 /** 249 * Creates a new {@code JSONFormattedResponseControl} with the provided set of 250 * embedded JSON objects. 251 * 252 * @param controlObjects The collection of JSON objects that represent the 253 * encoded controls to embed within this response 254 * control. This must not be {@code null} or empty. 255 * Note that no attempt will be made to validate the 256 * JSON objects as controls. 257 * 258 * @return The {@code JSONFormattedResponseControl} that was created. 259 */ 260 @NotNull() 261 public static JSONFormattedResponseControl createWithControlObjects( 262 @NotNull final Collection<JSONObject> controlObjects) 263 { 264 final List<JSONObject> controlObjectList = new ArrayList<>(controlObjects); 265 final JSONObject encodedValue = new JSONObject( 266 new JSONField(JSON_FIELD_CONTROLS, new JSONArray(controlObjectList))); 267 268 return new JSONFormattedResponseControl(encodedValue, 269 Collections.unmodifiableList(controlObjectList)); 270 } 271 272 273 274 /** 275 * Creates a new instance of this control that is decoded from the provided 276 * generic control information. 277 * generic control. Note that if the provided control has a value, it will be 278 * validated to ensure that it is a JSON object containing only a 279 * {@code controls} field whose value is an array of JSON objects that appear 280 * to be well-formed generic JSON controls, but it will not make any attempt 281 * to validate in a control-specific manner. 282 * 283 * @param oid The OID for the control. It must not be {@code null}. 284 * @param isCritical Indicates whether the control is considered critical. 285 * @param value The value for this control. It should not be 286 * {@code null} because this control requires a value, and 287 * an exception will be thrown if the given value is 288 * {@code null}. 289 * 290 * @throws LDAPException If a problem is encountered while attempting to 291 * decode the provided information as a JSON-formatted 292 * response control. 293 */ 294 public JSONFormattedResponseControl(@NotNull final String oid, 295 final boolean isCritical, 296 @Nullable final ASN1OctetString value) 297 throws LDAPException 298 { 299 super(oid, isCritical, value); 300 301 if (value == null) 302 { 303 throw new LDAPException(ResultCode.DECODING_ERROR, 304 ERR_JSON_FORMATTED_RESPONSE_NO_VALUE.get()); 305 } 306 307 try 308 { 309 encodedValue = new JSONObject(value.stringValue()); 310 } 311 catch (final Exception e) 312 { 313 Debug.debugException(e); 314 throw new LDAPException(ResultCode.DECODING_ERROR, 315 ERR_JSON_FORMATTED_RESPONSE_VALUE_NOT_JSON.get(), e); 316 } 317 318 319 final List<String> unrecognizedFields = 320 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 321 encodedValue, JSON_FIELD_CONTROLS); 322 if (! unrecognizedFields.isEmpty()) 323 { 324 throw new LDAPException(ResultCode.DECODING_ERROR, 325 ERR_JSON_FORMATTED_RESPONSE_UNRECOGNIZED_FIELD.get( 326 unrecognizedFields.get(0))); 327 } 328 329 330 final List<JSONValue> controlValues = 331 encodedValue.getFieldAsArray(JSON_FIELD_CONTROLS); 332 if (controlValues == null) 333 { 334 throw new LDAPException(ResultCode.DECODING_ERROR, 335 ERR_JSON_FORMATTED_RESPONSE_VALUE_MISSING_CONTROLS.get( 336 JSON_FIELD_CONTROLS)); 337 } 338 339 if (controlValues.isEmpty()) 340 { 341 throw new LDAPException(ResultCode.DECODING_ERROR, 342 ERR_JSON_FORMATTED_RESPONSE_VALUE_EMPTY_CONTROLS.get( 343 JSON_FIELD_CONTROLS)); 344 } 345 346 final List<JSONObject> controlObjectsList = 347 new ArrayList<>(controlValues.size()); 348 for (final JSONValue controlValue : controlValues) 349 { 350 if (controlValue instanceof JSONObject) 351 { 352 final JSONObject embeddedControlObject = (JSONObject) controlValue; 353 354 try 355 { 356 new JSONControlDecodeHelper(embeddedControlObject, true, true, false); 357 controlObjectsList.add(embeddedControlObject); 358 } 359 catch (final LDAPException e) 360 { 361 Debug.debugException(e); 362 throw new LDAPException(ResultCode.DECODING_ERROR, 363 ERR_JSON_FORMATTED_RESPONSE_VALUE_NOT_CONTROL.get( 364 JSON_FIELD_CONTROLS, 365 embeddedControlObject.toSingleLineString(), e.getMessage()), 366 e); 367 } 368 } 369 else 370 { 371 throw new LDAPException(ResultCode.DECODING_ERROR, 372 ERR_JSON_FORMATTED_RESPONSE_VALUE_CONTROL_NOT_OBJECT.get( 373 JSON_FIELD_CONTROLS)); 374 } 375 } 376 377 378 controlObjects = Collections.unmodifiableList(controlObjectsList); 379 } 380 381 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override() 387 @NotNull() 388 public JSONFormattedResponseControl decodeControl( 389 @NotNull final String oid, 390 final boolean isCritical, 391 @Nullable final ASN1OctetString value) 392 throws LDAPException 393 { 394 return new JSONFormattedResponseControl(oid, isCritical, value); 395 } 396 397 398 399 /** 400 * Retrieves a list of the JSON objects that represent the embedded response 401 * controls. These JSON objects may not have been validated to ensure that 402 * they represent valid controls. 403 * 404 * @return A list of the JSON objects that represent the embedded response 405 * controls. 406 */ 407 @NotNull() 408 public List<JSONObject> getControlObjects() 409 { 410 return controlObjects; 411 } 412 413 414 415 /** 416 * Attempts to retrieve a decoded representation of the embedded response 417 * controls using the specified behavior. 418 * 419 * @param behavior The behavior to use when parsing JSON 420 * objects as controls. It must not be 421 * {@code null}. 422 * @param nonFatalDecodeMessages An optional list that may be updated with 423 * messages about any JSON objects that could 424 * not be parsed as valid controls, but that 425 * should not result in an exception as per 426 * the provided behavior. This may be 427 * {@code null} if no such messages are 428 * desired. If it is non-{@code null}, then 429 * the list must be updatable. 430 * 431 * @return A decoded representation of the embedded response controls, or an 432 * empty list if none of the embedded JSON objects can be parsed as 433 * valid controls but that should not result in an exception as per 434 * the provided behavior. 435 * 436 * @throws LDAPException If any of the JSON objects cannot be parsed as a 437 * valid control 438 */ 439 @NotNull() 440 public synchronized List<Control> decodeEmbeddedControls( 441 @NotNull final JSONFormattedControlDecodeBehavior behavior, 442 @Nullable final List<String> nonFatalDecodeMessages) 443 throws LDAPException 444 { 445 // Iterate through the controls and try to decode them. 446 boolean errorEncountered = false; 447 final List<Control> controlList = new ArrayList<>(controlObjects.size()); 448 final List<String> fatalMessages = new ArrayList<>(controlObjects.size()); 449 for (final JSONObject controlObject : controlObjects) 450 { 451 // First, try to decode the JSON object as a generic control without any 452 // specific decoding based on its OID. 453 final JSONControlDecodeHelper jsonControl; 454 try 455 { 456 jsonControl = new JSONControlDecodeHelper(controlObject, 457 behavior.strict(), true, false); 458 } 459 catch (final LDAPException e) 460 { 461 Debug.debugException(e); 462 errorEncountered = true; 463 464 if (behavior.throwOnUnparsableObject()) 465 { 466 fatalMessages.add(e.getMessage()); 467 } 468 else if (nonFatalDecodeMessages != null) 469 { 470 nonFatalDecodeMessages.add(e.getMessage()); 471 } 472 473 continue; 474 } 475 476 477 // If the control is itself an embedded JSON-formatted response control, 478 // see how we should handle it. 479 if (jsonControl.getOID().equals(JSON_FORMATTED_RESPONSE_OID)) 480 { 481 if (! behavior.allowEmbeddedJSONFormattedControl()) 482 { 483 final String message = 484 ERR_JSON_FORMATTED_RESPONSE_DISALLOWED_EMBEDDED_CONTROL.get(); 485 if (jsonControl.getCriticality()) 486 { 487 fatalMessages.add(message); 488 } 489 else if (nonFatalDecodeMessages != null) 490 { 491 nonFatalDecodeMessages.add(message); 492 } 493 494 continue; 495 } 496 } 497 498 499 // Try to actually decode the JSON object as a control, potentially using 500 // control-specific logic based on its OID. 501 try 502 { 503 controlList.add(Control.decodeJSONControl(controlObject, 504 behavior.strict(), false)); 505 } 506 catch (final LDAPException e) 507 { 508 Debug.debugException(e); 509 errorEncountered = true; 510 511 if (jsonControl.getCriticality()) 512 { 513 if (behavior.throwOnInvalidCriticalControl()) 514 { 515 fatalMessages.add(e.getMessage()); 516 } 517 else if (nonFatalDecodeMessages != null) 518 { 519 nonFatalDecodeMessages.add(e.getMessage()); 520 } 521 } 522 else 523 { 524 if (behavior.throwOnInvalidNonCriticalControl()) 525 { 526 fatalMessages.add(e.getMessage()); 527 } 528 else if (nonFatalDecodeMessages != null) 529 { 530 nonFatalDecodeMessages.add(e.getMessage()); 531 } 532 } 533 } 534 } 535 536 537 // If there are any fatal messages, then we'll throw an exception with 538 // them. 539 if (! fatalMessages.isEmpty()) 540 { 541 throw new LDAPException(ResultCode.DECODING_ERROR, 542 StaticUtils.concatenateStrings(fatalMessages)); 543 } 544 545 546 return Collections.unmodifiableList(controlList); 547 } 548 549 550 551 /** 552 * Extracts a JSON-formatted control from the provided LDAP result. 553 * 554 * @param result The LDAP result from which to retrieve the JSON-formatted 555 * response control. 556 * 557 * @return The JSON-formatted response control contained in the provided 558 * LDAP result, or {@code null} if the result did not contain a 559 * JSON-formatted response control. 560 * 561 * @throws LDAPException If a problem is encountered while attempting to 562 * decode the JSON-formatted response control 563 * contained in the provided LDAP result. 564 */ 565 @Nullable() 566 public static JSONFormattedResponseControl get( 567 @NotNull final LDAPResult result) 568 throws LDAPException 569 { 570 return get(result.getResponseControl(JSON_FORMATTED_RESPONSE_OID)); 571 } 572 573 574 575 /** 576 * Extracts a JSON-formatted control from the provided search result entry. 577 * 578 * @param entry The search result entry from which to retrieve the 579 * JSON-formatted response control. 580 * 581 * @return The JSON-formatted response control contained in the provided 582 * search result entry, or {@code null} if the entry did not contain 583 * a JSON-formatted response control. 584 * 585 * @throws LDAPException If a problem is encountered while attempting to 586 * decode the JSON-formatted response control 587 * contained in the provided search result entry. 588 */ 589 @Nullable() 590 public static JSONFormattedResponseControl get( 591 @NotNull final SearchResultEntry entry) 592 throws LDAPException 593 { 594 return get(entry.getControl(JSON_FORMATTED_RESPONSE_OID)); 595 } 596 597 598 599 /** 600 * Extracts a JSON-formatted control from the provided search result 601 * reference. 602 * 603 * @param reference The search result reference from which to retrieve the 604 * JSON-formatted response control. 605 * 606 * @return The JSON-formatted response control contained in the provided 607 * search result reference, or {@code null} if the reference did not 608 * contain a JSON-formatted response control. 609 * 610 * @throws LDAPException If a problem is encountered while attempting to 611 * decode the JSON-formatted response control 612 * contained in the provided search result reference. 613 */ 614 @Nullable() 615 public static JSONFormattedResponseControl get( 616 @NotNull final SearchResultReference reference) 617 throws LDAPException 618 { 619 return get(reference.getControl(JSON_FORMATTED_RESPONSE_OID)); 620 } 621 622 623 624 /** 625 * Extracts a JSON-formatted control from the provided intermediate response. 626 * 627 * @param response The intermediate response from which to retrieve the 628 * JSON-formatted response control. 629 * 630 * @return The JSON-formatted response control contained in the provided 631 * intermediate response, or {@code null} if the response did not 632 * contain a JSON-formatted response control. 633 * 634 * @throws LDAPException If a problem is encountered while attempting to 635 * decode the JSON-formatted response control 636 * contained in the provided intermediate response. 637 */ 638 @Nullable() 639 public static JSONFormattedResponseControl get( 640 @NotNull final IntermediateResponse response) 641 throws LDAPException 642 { 643 return get(response.getControl(JSON_FORMATTED_RESPONSE_OID)); 644 } 645 646 647 648 /** 649 * Retrieves the provided control as a JSON-formatted response control. 650 * 651 * @param c The control to retrieve as a JSON-formatted response control. 652 * It may optionally be {@code null}. 653 * 654 * @return A JSON-formatted response control that is a representation of the 655 * provided control, or {@code null} if the provided control is 656 * {@code null}. 657 * 658 * @throws LDAPException If the provided control cannot be parsed as a valid 659 * JSON-formatted response control. 660 */ 661 @Nullable() 662 private static JSONFormattedResponseControl get(@Nullable final Control c) 663 throws LDAPException 664 { 665 if (c == null) 666 { 667 return null; 668 } 669 670 if (c instanceof JSONFormattedResponseControl) 671 { 672 return (JSONFormattedResponseControl) c; 673 } 674 else 675 { 676 return new JSONFormattedResponseControl(c.getOID(), c.isCritical(), 677 c.getValue()); 678 } 679 } 680 681 682 683 /** 684 * {@inheritDoc} 685 */ 686 @Override() 687 @NotNull() 688 public String getControlName() 689 { 690 return INFO_CONTROL_NAME_JSON_FORMATTED_RESPONSE.get(); 691 } 692 693 694 695 /** 696 * Retrieves a representation of this JSON-formatted response control as a 697 * JSON object. The JSON object uses the following fields: 698 * <UL> 699 * <LI> 700 * {@code oid} -- A mandatory string field whose value is the object 701 * identifier for this control. For the JSON-formatted response control, 702 * the OID is "1.3.6.1.4.1.30221.2.5.65". 703 * </LI> 704 * <LI> 705 * {@code control-name} -- An optional string field whose value is a 706 * human-readable name for this control. This field is only intended for 707 * descriptive purposes, and when decoding a control, the {@code oid} 708 * field should be used to identify the type of control. 709 * </LI> 710 * <LI> 711 * {@code criticality} -- A mandatory Boolean field used to indicate 712 * whether this control is considered critical. 713 * </LI> 714 * <LI> 715 * {@code value-base64} -- An optional string field whose value is a 716 * base64-encoded representation of the raw value for this JSON-formatted 717 * response control. Exactly one of the {@code value-base64} and 718 * {@code value-json} fields must be present. 719 * </LI> 720 * <LI> 721 * {@code value-json} -- An optional JSON object field whose value is a 722 * user-friendly representation of the value for this JSON-formatted 723 * response control. Exactly one of the {@code value-base64} and 724 * {@code value-json} fields must be present, and if the 725 * {@code value-json} field is used, then it will use the following 726 * fields: 727 * <UL> 728 * <LI> 729 * {@code controls} -- An array field whose values are JSON objects 730 * that represent the JSON-formatted request controls to send to the 731 * server. 732 * </LI> 733 * </UL> 734 * </LI> 735 * </UL> 736 * 737 * @return A JSON object that contains a representation of this control. 738 */ 739 @Override() 740 @NotNull() 741 public JSONObject toJSONControl() 742 { 743 return new JSONObject( 744 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 745 JSON_FORMATTED_RESPONSE_OID), 746 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 747 INFO_CONTROL_NAME_JSON_FORMATTED_RESPONSE.get()), 748 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 749 isCritical()), 750 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 751 encodedValue)); 752 } 753 754 755 756 /** 757 * Attempts to decode the provided object as a JSON representation of a 758 * JSON-formatted response control. 759 * 760 * @param controlObject The JSON object to be decoded. It must not be 761 * {@code null}. 762 * @param strict Indicates whether to use strict mode when decoding 763 * the provided JSON object. If this is {@code true}, 764 * then this method will throw an exception if the 765 * provided JSON object contains any unrecognized 766 * fields. If this is {@code false}, then unrecognized 767 * fields will be ignored. 768 * 769 * @return The JSON-formatted response control that was decoded from the 770 * provided JSON object. 771 * 772 * @throws LDAPException If the provided JSON object cannot be parsed as a 773 * valid JSON-formatted response control. 774 */ 775 @NotNull() 776 public static JSONFormattedResponseControl decodeJSONControl( 777 @NotNull final JSONObject controlObject, 778 final boolean strict) 779 throws LDAPException 780 { 781 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 782 controlObject, strict, true, true); 783 784 final ASN1OctetString rawValue = jsonControl.getRawValue(); 785 if (rawValue != null) 786 { 787 return new JSONFormattedResponseControl(jsonControl.getOID(), 788 jsonControl.getCriticality(), rawValue); 789 } 790 791 792 final JSONObject valueObject = jsonControl.getValueObject(); 793 794 final List<JSONValue> controlValues = 795 valueObject.getFieldAsArray(JSON_FIELD_CONTROLS); 796 if (controlValues == null) 797 { 798 throw new LDAPException(ResultCode.DECODING_ERROR, 799 ERR_JSON_FORMATTED_RESPONSE_DECODE_VALUE_MISSING_CONTROLS.get( 800 controlObject.toSingleLineString(), JSON_FIELD_CONTROLS)); 801 } 802 803 if (controlValues.isEmpty()) 804 { 805 throw new LDAPException(ResultCode.DECODING_ERROR, 806 ERR_JSON_FORMATTED_RESPONSE_DECODE_VALUE_EMPTY_CONTROLS.get( 807 controlObject.toSingleLineString(), JSON_FIELD_CONTROLS)); 808 } 809 810 final List<JSONObject> controlObjectsList = 811 new ArrayList<>(controlValues.size()); 812 for (final JSONValue controlValue : controlValues) 813 { 814 if (controlValue instanceof JSONObject) 815 { 816 final JSONObject embeddedControlObject = (JSONObject) controlValue; 817 818 try 819 { 820 new JSONControlDecodeHelper(embeddedControlObject, strict, true, 821 false); 822 controlObjectsList.add(embeddedControlObject); 823 } 824 catch (final LDAPException e) 825 { 826 Debug.debugException(e); 827 throw new LDAPException(ResultCode.DECODING_ERROR, 828 ERR_JSON_FORMATTED_RESPONSE_DECODE_VALUE_NOT_CONTROL.get( 829 controlObject.toSingleLineString(), JSON_FIELD_CONTROLS, 830 embeddedControlObject.toSingleLineString(), e.getMessage()), 831 e); 832 } 833 } 834 else 835 { 836 throw new LDAPException(ResultCode.DECODING_ERROR, 837 ERR_JSON_FORMATTED_RESPONSE_DECODE_VALUE_CONTROL_NOT_OBJECT.get( 838 controlObject.toSingleLineString(), 839 JSON_FIELD_CONTROLS)); 840 } 841 } 842 843 844 if (strict) 845 { 846 final List<String> unrecognizedFields = 847 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 848 valueObject, JSON_FIELD_CONTROLS); 849 if (! unrecognizedFields.isEmpty()) 850 { 851 throw new LDAPException(ResultCode.DECODING_ERROR, 852 ERR_JSON_FORMATTED_RESPONSE_DECODE_UNRECOGNIZED_FIELD.get( 853 controlObject.toSingleLineString(), 854 unrecognizedFields.get(0))); 855 } 856 } 857 858 859 return new JSONFormattedResponseControl(valueObject, 860 Collections.unmodifiableList(controlObjectsList)); 861 } 862 863 864 865 /** 866 * {@inheritDoc} 867 */ 868 @Override() 869 public void toString(@NotNull final StringBuilder buffer) 870 { 871 buffer.append("JSONFormattedResponseControl(isCritical="); 872 buffer.append(isCritical()); 873 buffer.append(", valueObject="); 874 encodedValue.toSingleLineString(buffer); 875 buffer.append(')'); 876 } 877}