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}