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