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}