001/*
002 * Copyright 2013-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2013-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) 2013-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.Iterator;
044import java.util.LinkedHashMap;
045import java.util.List;
046import java.util.Map;
047
048import com.unboundid.asn1.ASN1Boolean;
049import com.unboundid.asn1.ASN1Element;
050import com.unboundid.asn1.ASN1Enumerated;
051import com.unboundid.asn1.ASN1OctetString;
052import com.unboundid.asn1.ASN1Sequence;
053import com.unboundid.ldap.sdk.Control;
054import com.unboundid.ldap.sdk.DecodeableControl;
055import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
056import com.unboundid.ldap.sdk.LDAPException;
057import com.unboundid.ldap.sdk.LDAPResult;
058import com.unboundid.ldap.sdk.ResultCode;
059import com.unboundid.util.Debug;
060import com.unboundid.util.NotMutable;
061import com.unboundid.util.NotNull;
062import com.unboundid.util.Nullable;
063import com.unboundid.util.StaticUtils;
064import com.unboundid.util.ThreadSafety;
065import com.unboundid.util.ThreadSafetyLevel;
066import com.unboundid.util.json.JSONArray;
067import com.unboundid.util.json.JSONBoolean;
068import com.unboundid.util.json.JSONField;
069import com.unboundid.util.json.JSONNumber;
070import com.unboundid.util.json.JSONObject;
071import com.unboundid.util.json.JSONString;
072import com.unboundid.util.json.JSONValue;
073
074import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
075
076
077
078/**
079 * This class provides an implementation of an LDAP control that can be included
080 * in add, bind, modify, modify DN, and certain extended responses to provide
081 * information about the result of replication assurance processing for that
082 * operation.
083 * <BR>
084 * <BLOCKQUOTE>
085 *   <B>NOTE:</B>  This class, and other classes within the
086 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
087 *   supported for use against Ping Identity, UnboundID, and
088 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
089 *   for proprietary functionality or for external specifications that are not
090 *   considered stable or mature enough to be guaranteed to work in an
091 *   interoperable way with other types of LDAP servers.
092 * </BLOCKQUOTE>
093 * <BR>
094 * The OID for this control is 1.3.6.1.4.1.30221.2.5.29.  It will have a
095 * criticality of FALSE, and will have a value with the following encoding:
096 * <PRE>
097 *   AssuredReplicationResponse ::= SEQUENCE {
098 *        localLevel                   [0] LocalLevel OPTIONAL,
099 *        localAssuranceSatisfied      [1] BOOLEAN,
100 *        localAssuranceMessage        [2] OCTET STRING OPTIONAL,
101 *        remoteLevel                  [3] RemoteLevel OPTIONAL,
102 *        remoteAssuranceSatisfied     [4] BOOLEAN,
103 *        remoteAssuranceMessage       [5] OCTET STRING OPTIONAL,
104 *        csn                          [6] OCTET STRING OPTIONAL,
105 *        serverResults                [7] SEQUENCE OF ServerResult OPTIONAL,
106 *        ... }
107 *
108 *   ServerResult ::= SEQUENCE {
109 *        resultCode              [0] ENUMERATED {
110 *             complete           (0),
111 *             timeout            (1),
112 *             conflict           (2),
113 *             serverShutdown     (3),
114 *             unavailable        (4),
115 *             duplicate          (5),
116 *             ... },
117 *        replicationServerID     [1] INTEGER OPTIONAL,
118 *        replicaID               [2] INTEGER OPTIONAL,
119 *        ... }
120 * </PRE>
121 *
122 * @see  AssuredReplicationRequestControl
123 */
124@NotMutable()
125@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
126public final class AssuredReplicationResponseControl
127       extends Control
128       implements DecodeableControl
129{
130  /**
131   * The OID (1.3.6.1.4.1.30221.2.5.29) for the assured replication response
132   * control.
133   */
134  @NotNull public static final String ASSURED_REPLICATION_RESPONSE_OID =
135       "1.3.6.1.4.1.30221.2.5.29";
136
137
138  /**
139   * The BER type for the local level element.
140   */
141  private static final byte TYPE_LOCAL_LEVEL = (byte) 0x80;
142
143
144  /**
145   * The BER type for the local assurance satisfied element.
146   */
147  private static final byte TYPE_LOCAL_SATISFIED = (byte) 0x81;
148
149
150  /**
151   * The BER type for the local message element.
152   */
153  private static final byte TYPE_LOCAL_MESSAGE = (byte) 0x82;
154
155
156  /**
157   * The BER type for the remote level element.
158   */
159  private static final byte TYPE_REMOTE_LEVEL = (byte) 0x83;
160
161
162  /**
163   * The BER type for the remote assurance satisfied element.
164   */
165  private static final byte TYPE_REMOTE_SATISFIED = (byte) 0x84;
166
167
168  /**
169   * The BER type for the remote message element.
170   */
171  private static final byte TYPE_REMOTE_MESSAGE = (byte) 0x85;
172
173
174  /**
175   * The BER type for the CSN element.
176   */
177  private static final byte TYPE_CSN = (byte) 0x86;
178
179
180  /**
181   * The BER type for the server results element.
182   */
183  private static final byte TYPE_SERVER_RESULTS = (byte) 0xA7;
184
185
186
187  /**
188   * The name of the field used to specify the replication CSN in the JSON
189   * representation of this control.
190   */
191  @NotNull private static final String JSON_FIELD_CSN = "csn";
192
193
194
195  /**
196   * The name of the field used to specify the local assurance level in the JSON
197   * representation of this control.
198   */
199  @NotNull private static final String JSON_FIELD_LOCAL_LEVEL = "local-level";
200
201
202
203  /**
204   * The name of the field used to indicate whether the local assurance level
205   * was satisfied in the JSON representation of this control.
206   */
207  @NotNull private static final String JSON_FIELD_LOCAL_ASSURANCE_SATISFIED =
208       "local-assurance-satisfied";
209
210
211
212  /**
213   * The name of the field used to provide an additional message about local
214   * assurance processing in the JSON representation of this control.
215   */
216  @NotNull private static final String JSON_FIELD_LOCAL_ASSURANCE_MESSAGE =
217       "local-assurance-message";
218
219
220
221  /**
222   * The name of the field used to specify the remote assurance level in the
223   * JSON representation of this control.
224   */
225  @NotNull private static final String JSON_FIELD_REMOTE_LEVEL = "remote-level";
226
227
228
229  /**
230   * The name of the field used to indicate whether the remote assurance level
231   * was satisfied in the JSON representation of this control.
232   */
233  @NotNull private static final String JSON_FIELD_REMOTE_ASSURANCE_SATISFIED =
234       "remote-assurance-satisfied";
235
236
237
238  /**
239   * The name of the field used to provide an additional message about remote
240   * assurance processing in the JSON representation of this control.
241   */
242  @NotNull private static final String JSON_FIELD_REMOTE_ASSURANCE_MESSAGE =
243       "remote-assurance-message";
244
245
246
247  /**
248   * The name of the field used to hold the server results in the JSON
249   * representation of this control.
250   */
251  @NotNull private static final String JSON_FIELD_SERVER_RESULTS =
252       "server-results";
253
254
255
256  /**
257   * The name of the field used to hold the result code value in the server
258   * results element in the JSON representation of this control.
259   */
260  @NotNull private static final String
261       JSON_FIELD_SERVER_RESULTS_RESULT_CODE_VALUE = "result-code-value";
262
263
264
265  /**
266   * The name of the field used to hold the result code name in the server
267   * results element in the JSON representation of this control.
268   */
269  @NotNull private static final String
270       JSON_FIELD_SERVER_RESULTS_RESULT_CODE_NAME = "result-code-name";
271
272
273
274  /**
275   * The name of the field used to hold the replica ID in the server results
276   * element in the JSON representation of this control.
277   */
278  @NotNull private static final String JSON_FIELD_SERVER_RESULTS_REPLICA_ID =
279       "replica-id";
280
281
282
283  /**
284   * The name of the field used to hold the replication server ID in the server
285   * results element in the JSON representation of this control.
286   */
287  @NotNull private static final String
288       JSON_FIELD_SERVER_RESULTS_REPLICATION_SERVER_ID =
289       "replication-server-id";
290
291
292
293  /**
294   * The serial version UID for this serializable class.
295   */
296  private static final long serialVersionUID = -4521456074629871607L;
297
298
299
300  // The assurance level for local processing.
301  @Nullable private final AssuredReplicationLocalLevel localLevel;
302
303  // The assurance level for remote processing.
304  @Nullable private final AssuredReplicationRemoteLevel remoteLevel;
305
306  // Indicates whether the desired local assurance has been satisfied.
307  private final boolean localAssuranceSatisfied;
308
309  // Indicates whether the desired remote assurance has been satisfied.
310  private final boolean remoteAssuranceSatisfied;
311
312  // The results from individual replication and/or directory servers.
313  @NotNull private final List<AssuredReplicationServerResult> serverResults;
314
315  // The replication change sequence number for the associated operation.
316  @Nullable private final String csn;
317
318  // An optional message with additional information about local assurance
319  // processing.
320  @Nullable private final String localAssuranceMessage;
321
322  // An optional message with additional information about local assurance
323  // processing.
324  @Nullable private final String remoteAssuranceMessage;
325
326
327
328  /**
329   * Creates a new empty control instance that is intended to be used only for
330   * decoding controls via the {@code DecodeableControl} interface.
331   */
332  AssuredReplicationResponseControl()
333  {
334    localLevel = null;
335    localAssuranceSatisfied = false;
336    localAssuranceMessage = null;
337    remoteLevel = null;
338    remoteAssuranceSatisfied = false;
339    remoteAssuranceMessage = null;
340    csn = null;
341    serverResults = null;
342  }
343
344
345
346  /**
347   * Creates a new assured replication response control with the provided
348   * information.
349   *
350   * @param  localLevel                The local assurance level selected by the
351   *                                   server for the associated operation.  It
352   *                                   may be {@code null} if this is not
353   *                                   available.
354   * @param  localAssuranceSatisfied   Indicates whether the desired local level
355   *                                   of assurance is known to have been
356   *                                   satisfied.
357   * @param  localAssuranceMessage     An optional message providing additional
358   *                                   information about local assurance
359   *                                   processing.  This may be {@code null} if
360   *                                   no additional message is needed.
361   * @param  remoteLevel               The remote assurance level selected by
362   *                                   the server for the associated operation.
363   *                                   It may be {@code null} if this is not
364   *                                   available.
365   * @param  remoteAssuranceSatisfied  Indicates whether the desired remote
366   *                                   level of assurance is known to have been
367   *                                   satisfied.
368   * @param  remoteAssuranceMessage    An optional message providing additional
369   *                                   information about remote assurance
370   *                                   processing.  This may be {@code null} if
371   *                                   no additional message is needed.
372   * @param  csn                       The change sequence number (CSN) that has
373   *                                   been assigned to the associated
374   *                                   operation.  It may be {@code null} if no
375   *                                   CSN is available.
376   * @param  serverResults             The set of individual results from the
377   *                                   local and/or remote replication servers
378   *                                   and/or directory servers used in
379   *                                   assurance processing.  This may be
380   *                                   {@code null} or empty if no server
381   *                                   results are available.
382   */
383  public AssuredReplicationResponseControl(
384       @Nullable final AssuredReplicationLocalLevel localLevel,
385       final boolean localAssuranceSatisfied,
386       @Nullable final String localAssuranceMessage,
387       @Nullable final AssuredReplicationRemoteLevel remoteLevel,
388       final boolean remoteAssuranceSatisfied,
389       @Nullable final String remoteAssuranceMessage,
390       @Nullable final String csn,
391       @Nullable final Collection<AssuredReplicationServerResult> serverResults)
392  {
393    super(ASSURED_REPLICATION_RESPONSE_OID, false,
394         encodeValue(localLevel, localAssuranceSatisfied,
395              localAssuranceMessage, remoteLevel, remoteAssuranceSatisfied,
396              remoteAssuranceMessage, csn, serverResults));
397
398    this.localLevel               = localLevel;
399    this.localAssuranceSatisfied  = localAssuranceSatisfied;
400    this.localAssuranceMessage    = localAssuranceMessage;
401    this.remoteLevel              = remoteLevel;
402    this.remoteAssuranceSatisfied = remoteAssuranceSatisfied;
403    this.remoteAssuranceMessage   = remoteAssuranceMessage;
404    this.csn                      = csn;
405
406    if (serverResults == null)
407    {
408      this.serverResults = Collections.emptyList();
409    }
410    else
411    {
412      this.serverResults = Collections.unmodifiableList(
413           new ArrayList<>(serverResults));
414    }
415  }
416
417
418
419  /**
420   * Creates a new assured replication response control with the provided
421   * information.
422   *
423   * @param  oid         The OID for the control.
424   * @param  isCritical  Indicates whether the control should be marked
425   *                     critical.
426   * @param  value       The encoded value for the control.  This may be
427   *                     {@code null} if no value was provided.
428   *
429   * @throws  LDAPException  If the provided control cannot be decoded as an
430   *                         assured replication response control.
431   */
432  public AssuredReplicationResponseControl(@NotNull final String oid,
433              final boolean isCritical,
434              @Nullable final ASN1OctetString value)
435         throws LDAPException
436  {
437    super(oid, isCritical, value);
438
439    if (value == null)
440    {
441      throw new LDAPException(ResultCode.DECODING_ERROR,
442           ERR_ASSURED_REPLICATION_RESPONSE_NO_VALUE.get());
443    }
444
445    AssuredReplicationLocalLevel         lLevel     = null;
446    Boolean                              lSatisfied = null;
447    String                               lMessage   = null;
448    AssuredReplicationRemoteLevel        rLevel     = null;
449    Boolean                              rSatisfied = null;
450    String                               rMessage   = null;
451    String                               seqNum     = null;
452    List<AssuredReplicationServerResult> sResults   = Collections.emptyList();
453
454    try
455    {
456      for (final ASN1Element e :
457           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
458      {
459        switch (e.getType())
460        {
461          case TYPE_LOCAL_LEVEL:
462            int intValue = ASN1Enumerated.decodeAsEnumerated(e).intValue();
463            lLevel = AssuredReplicationLocalLevel.valueOf(intValue);
464            if (lLevel == null)
465            {
466              throw new LDAPException(ResultCode.DECODING_ERROR,
467                   ERR_ASSURED_REPLICATION_RESPONSE_INVALID_LOCAL_LEVEL.get(
468                        intValue));
469            }
470            break;
471
472          case TYPE_LOCAL_SATISFIED:
473            lSatisfied = ASN1Boolean.decodeAsBoolean(e).booleanValue();
474            break;
475
476          case TYPE_LOCAL_MESSAGE:
477            lMessage = ASN1OctetString.decodeAsOctetString(e).stringValue();
478            break;
479
480          case TYPE_REMOTE_LEVEL:
481            intValue = ASN1Enumerated.decodeAsEnumerated(e).intValue();
482            rLevel = AssuredReplicationRemoteLevel.valueOf(intValue);
483            if (lLevel == null)
484            {
485              throw new LDAPException(ResultCode.DECODING_ERROR,
486                   ERR_ASSURED_REPLICATION_RESPONSE_INVALID_REMOTE_LEVEL.get(
487                        intValue));
488            }
489            break;
490
491          case TYPE_REMOTE_SATISFIED:
492            rSatisfied = ASN1Boolean.decodeAsBoolean(e).booleanValue();
493            break;
494
495          case TYPE_REMOTE_MESSAGE:
496            rMessage = ASN1OctetString.decodeAsOctetString(e).stringValue();
497            break;
498
499          case TYPE_CSN:
500            seqNum = ASN1OctetString.decodeAsOctetString(e).stringValue();
501            break;
502
503          case TYPE_SERVER_RESULTS:
504            final ASN1Element[] srElements =
505                 ASN1Sequence.decodeAsSequence(e).elements();
506            final ArrayList<AssuredReplicationServerResult> srList =
507                 new ArrayList<>(srElements.length);
508            for (final ASN1Element srElement : srElements)
509            {
510              try
511              {
512                srList.add(AssuredReplicationServerResult.decode(srElement));
513              }
514              catch (final Exception ex)
515              {
516                Debug.debugException(ex);
517                throw new LDAPException(ResultCode.DECODING_ERROR,
518                     ERR_ASSURED_REPLICATION_RESPONSE_ERROR_DECODING_SR.get(
519                          StaticUtils.getExceptionMessage(ex)),
520                     ex);
521              }
522            }
523            sResults = Collections.unmodifiableList(srList);
524            break;
525
526          default:
527            throw new LDAPException(ResultCode.DECODING_ERROR,
528                 ERR_ASSURED_REPLICATION_RESPONSE_UNEXPECTED_ELEMENT_TYPE.get(
529                      StaticUtils.toHex(e.getType())));
530        }
531      }
532    }
533    catch (final LDAPException le)
534    {
535      Debug.debugException(le);
536      throw le;
537    }
538    catch (final Exception e)
539    {
540      Debug.debugException(e);
541      throw new LDAPException(ResultCode.DECODING_ERROR,
542           ERR_ASSURED_REPLICATION_RESPONSE_ERROR_DECODING_VALUE.get(
543                StaticUtils.getExceptionMessage(e)),
544           e);
545    }
546
547    if (lSatisfied == null)
548    {
549      throw new LDAPException(ResultCode.DECODING_ERROR,
550           ERR_ASSURED_REPLICATION_RESPONSE_NO_LOCAL_SATISFIED.get());
551    }
552
553    if (rSatisfied == null)
554    {
555      throw new LDAPException(ResultCode.DECODING_ERROR,
556           ERR_ASSURED_REPLICATION_RESPONSE_NO_REMOTE_SATISFIED.get());
557    }
558
559    localLevel               = lLevel;
560    localAssuranceSatisfied  = lSatisfied;
561    localAssuranceMessage    = lMessage;
562    remoteLevel              = rLevel;
563    remoteAssuranceSatisfied = rSatisfied;
564    remoteAssuranceMessage   = rMessage;
565    csn                      = seqNum;
566    serverResults            = sResults;
567  }
568
569
570
571  /**
572   * Encodes the provided information to an ASN.1 octet string suitable for
573   * use as an assured replication response control value.
574   *
575   * @param  localLevel                The local assurance level selected by the
576   *                                   server for the associated operation.  It
577   *                                   may be {@code null} if this is not
578   *                                   available.
579   * @param  localAssuranceSatisfied   Indicates whether the desired local level
580   *                                   of assurance is known to have been
581   *                                   satisfied.
582   * @param  localAssuranceMessage     An optional message providing additional
583   *                                   information about local assurance
584   *                                   processing.  This may be {@code null} if
585   *                                   no additional message is needed.
586   * @param  remoteLevel               The remote assurance level selected by
587   *                                   the server for the associated operation.
588   *                                   It may be {@code null} if this is not
589   *                                   available.
590   * @param  remoteAssuranceSatisfied  Indicates whether the desired remote
591   *                                   level of assurance is known to have been
592   *                                   satisfied.
593   * @param  remoteAssuranceMessage    An optional message providing additional
594   *                                   information about remote assurance
595   *                                   processing.  This may be {@code null} if
596   *                                   no additional message is needed.
597   * @param  csn                       The change sequence number (CSN) that has
598   *                                   been assigned to the associated
599   *                                   operation.  It may be {@code null} if no
600   *                                   CSN is available.
601   * @param  serverResults             The set of individual results from the
602   *                                   local and/or remote replication servers
603   *                                   and/or directory servers used in
604   *                                   assurance processing.  This may be
605   *                                   {@code null} or empty if no server
606   *                                   results are available.
607   *
608   * @return  The ASN.1 octet string containing the encoded value.
609   */
610  @NotNull()
611  private static ASN1OctetString encodeValue(
612       @Nullable final AssuredReplicationLocalLevel localLevel,
613       final boolean localAssuranceSatisfied,
614       @Nullable final String localAssuranceMessage,
615       @Nullable final AssuredReplicationRemoteLevel remoteLevel,
616       final boolean remoteAssuranceSatisfied,
617       @Nullable final String remoteAssuranceMessage,
618       @Nullable final String csn,
619       @Nullable final Collection<AssuredReplicationServerResult> serverResults)
620  {
621    final ArrayList<ASN1Element> elements = new ArrayList<>(8);
622
623    if (localLevel != null)
624    {
625      elements.add(new ASN1Enumerated(TYPE_LOCAL_LEVEL, localLevel.intValue()));
626    }
627
628    elements.add(new ASN1Boolean(TYPE_LOCAL_SATISFIED,
629         localAssuranceSatisfied));
630
631    if (localAssuranceMessage != null)
632    {
633      elements.add(new ASN1OctetString(TYPE_LOCAL_MESSAGE,
634           localAssuranceMessage));
635    }
636
637    if (remoteLevel != null)
638    {
639      elements.add(new ASN1Enumerated(TYPE_REMOTE_LEVEL,
640           remoteLevel.intValue()));
641    }
642
643    elements.add(new ASN1Boolean(TYPE_REMOTE_SATISFIED,
644         remoteAssuranceSatisfied));
645
646    if (remoteAssuranceMessage != null)
647    {
648      elements.add(new ASN1OctetString(TYPE_REMOTE_MESSAGE,
649           remoteAssuranceMessage));
650    }
651
652    if (csn != null)
653    {
654      elements.add(new ASN1OctetString(TYPE_CSN, csn));
655    }
656
657    if ((serverResults !=  null) && (! serverResults.isEmpty()))
658    {
659      final ArrayList<ASN1Element> srElements =
660           new ArrayList<>(serverResults.size());
661      for (final AssuredReplicationServerResult r : serverResults)
662      {
663        srElements.add(r.encode());
664      }
665      elements.add(new ASN1Sequence(TYPE_SERVER_RESULTS, srElements));
666    }
667
668    return new ASN1OctetString(new ASN1Sequence(elements).encode());
669  }
670
671
672
673  /**
674   * {@inheritDoc}
675   */
676  @Override()
677  @NotNull()
678  public AssuredReplicationResponseControl decodeControl(
679              @NotNull final String oid, final boolean isCritical,
680              @Nullable final ASN1OctetString value)
681         throws LDAPException
682  {
683    return new AssuredReplicationResponseControl(oid, isCritical, value);
684  }
685
686
687
688  /**
689   * Extracts an assured replication response control from the provided LDAP
690   * result.  If there are multiple assured replication response controls
691   * included in the result, then only the first will be returned.
692   *
693   * @param  result  The LDAP result from which to retrieve the assured
694   *                 replication response control.
695   *
696   * @return  The assured replication response control contained in the provided
697   *          LDAP result, or {@code null} if the result did not contain an
698   *          assured replication response control.
699   *
700   * @throws  LDAPException  If a problem is encountered while attempting to
701   *                         decode the assured replication response control
702   *                         contained in the provided result.
703   */
704  @Nullable()
705  public static AssuredReplicationResponseControl get(
706                     @NotNull final LDAPResult result)
707         throws LDAPException
708  {
709    final Control c =
710         result.getResponseControl(ASSURED_REPLICATION_RESPONSE_OID);
711    if (c == null)
712    {
713      return null;
714    }
715
716    if (c instanceof AssuredReplicationResponseControl)
717    {
718      return (AssuredReplicationResponseControl) c;
719    }
720    else
721    {
722      return new AssuredReplicationResponseControl(c.getOID(), c.isCritical(),
723           c.getValue());
724    }
725  }
726
727
728
729  /**
730   * Extracts all assured replication response controls from the provided LDAP
731   * result.
732   *
733   * @param  result  The LDAP result from which to retrieve the assured
734   *                 replication response controls.
735   *
736   * @return  A list containing the assured replication response controls
737   *          contained in the provided LDAP result, or an empty list if the
738   *          result did not contain any assured replication response control.
739   *
740   * @throws  LDAPException  If a problem is encountered while attempting to
741   *                         decode any assured replication response control
742   *                         contained in the provided result.
743   */
744  @NotNull()
745  public static List<AssuredReplicationResponseControl> getAll(
746                     @NotNull final LDAPResult result)
747         throws LDAPException
748  {
749    final Control[] controls = result.getResponseControls();
750    final ArrayList<AssuredReplicationResponseControl> decodedControls =
751         new ArrayList<>(controls.length);
752    for (final Control c : controls)
753    {
754      if (c.getOID().equals(ASSURED_REPLICATION_RESPONSE_OID))
755      {
756        if (c instanceof AssuredReplicationResponseControl)
757        {
758          decodedControls.add((AssuredReplicationResponseControl) c);
759        }
760        else
761        {
762          decodedControls.add(new AssuredReplicationResponseControl(c.getOID(),
763               c.isCritical(), c.getValue()));
764        }
765      }
766    }
767
768    return Collections.unmodifiableList(decodedControls);
769  }
770
771
772
773  /**
774   * Retrieves the local assurance level selected by the server for the
775   * associated operation, if available.
776   *
777   * @return  The local assurance level selected by the server for the
778   *          associated operation, or {@code null} if this is not available.
779   */
780  @Nullable()
781  public AssuredReplicationLocalLevel getLocalLevel()
782  {
783    return localLevel;
784  }
785
786
787
788  /**
789   * Indicates whether the desired local level of assurance is known to have
790   * been satisfied.
791   *
792   * @return  {@code true} if the desired local level of assurance is known to
793   *          have been satisfied, or {@code false} if not.
794   */
795  public boolean localAssuranceSatisfied()
796  {
797    return localAssuranceSatisfied;
798  }
799
800
801
802  /**
803   * Retrieves a message with additional information about local assurance
804   * processing, if available.
805   *
806   * @return  A message with additional information about local assurance
807   *          processing, or {@code null} if none is available.
808   */
809  @Nullable()
810  public String getLocalAssuranceMessage()
811  {
812    return localAssuranceMessage;
813  }
814
815
816
817  /**
818   * Retrieves the remote assurance level selected by the server for the
819   * associated operation, if available.
820   *
821   * @return  The remote assurance level selected by the server for the
822   *          associated operation, or {@code null} if the remote assurance
823   *          level is not available.
824   */
825  @Nullable()
826  public AssuredReplicationRemoteLevel getRemoteLevel()
827  {
828    return remoteLevel;
829  }
830
831
832
833  /**
834   * Indicates whether the desired remote level of assurance is known to have
835   * been satisfied.
836   *
837   * @return  {@code true} if the desired remote level of assurance is known to
838   *          have been satisfied, or {@code false} if not.
839   */
840  public boolean remoteAssuranceSatisfied()
841  {
842    return remoteAssuranceSatisfied;
843  }
844
845
846
847  /**
848   * Retrieves a message with additional information about remote assurance
849   * processing, if available.
850   *
851   * @return  A message with additional information about remote assurance
852   *          processing, or {@code null} if none is available.
853   */
854  @Nullable()
855  public String getRemoteAssuranceMessage()
856  {
857    return remoteAssuranceMessage;
858  }
859
860
861
862  /**
863   * Retrieves the replication change sequence number (CSN) assigned to the
864   * associated operation, if available.
865   *
866   * @return  The replication CSN assigned to the associated operation, or
867   *          {@code null} if the CSN is not available.
868   */
869  @Nullable()
870  public String getCSN()
871  {
872    return csn;
873  }
874
875
876
877  /**
878   * Retrieves a list of the results from individual replication servers and/or
879   * directory servers used in assurance processing.  It may be empty if no
880   * server results are available.
881   *
882   * @return  A list of the results from individual replication servers and/or
883   *          directory servers used in assurance processing.
884   */
885  @NotNull()
886  public List<AssuredReplicationServerResult> getServerResults()
887  {
888    return serverResults;
889  }
890
891
892
893  /**
894   * {@inheritDoc}
895   */
896  @Override()
897  @NotNull()
898  public String getControlName()
899  {
900    return INFO_CONTROL_NAME_ASSURED_REPLICATION_RESPONSE.get();
901  }
902
903
904
905  /**
906   * Retrieves a representation of this assured replication response control as
907   * a JSON object.  The JSON object uses the following fields:
908   * <UL>
909   *   <LI>
910   *     {@code oid} -- A mandatory string field whose value is the object
911   *     identifier for this control.  For the assured replication response
912   *     control, the OID is "1.3.6.1.4.1.30221.2.5.29".
913   *   </LI>
914   *   <LI>
915   *     {@code control-name} -- An optional string field whose value is a
916   *     human-readable name for this control.  This field is only intended for
917   *     descriptive purposes, and when decoding a control, the {@code oid}
918   *     field should be used to identify the type of control.
919   *   </LI>
920   *   <LI>
921   *     {@code criticality} -- A mandatory Boolean field used to indicate
922   *     whether this control is considered critical.
923   *   </LI>
924   *   <LI>
925   *     {@code value-base64} -- An optional string field whose value is a
926   *     base64-encoded representation of the raw value for this assured
927   *     replication response control.  Exactly one of the {@code value-base64}
928   *     and {@code value-json} fields must be present.
929   *   </LI>
930   *   <LI>
931   *     {@code value-json} -- An optional JSON object field whose value is a
932   *     user-friendly representation of the value for this assured replication
933   *     response control.  Exactly one of the {@code value-base64} and
934   *     {@code value-json} fields must be present, and if the
935   *     {@code value-json} field is used, then it will use the following
936   *     fields:
937   *     <UL>
938   *       <LI>
939   *         {@code local-level} -- An optional string field whose value is the
940   *         local assurance level used for the operation.  If present, its
941   *         value will be one of "{@code none}", "{@code received-any-server}",
942   *         or "{@code processed-all-servers}".
943   *       </LI>
944   *       <LI>
945   *         {@code local-assurance-satisfied} -- A Boolean field that indicates
946   *         whether local assurance was satisfied for the operation.
947   *       </LI>
948   *       <LI>
949   *         {@code local-assurance-message} -- An optional string field whose
950   *         value is a message that provides additional information about the
951   *         local assurance processing.
952   *       </LI>
953   *       <LI>
954   *         {@code remote-level} -- An optional string field whose value is the
955   *         remote assurance level used for the operation.  If present, its
956   *         value will be one of "{@code none}",
957   *         "{@code received-any-remote-location}",
958   *         "{@code received-all-remote-locations}", or
959   *         "{@code processed-all-remote-servers}".
960   *       </LI>
961   *       <LI>
962   *         {@code remote-assurance-satisfied} -- A Boolean field that
963   *         indicates whether remote assurance was satisfied for the operation.
964   *       </LI>
965   *       <LI>
966   *         {@code remote-assurance-message} -- An optional string field whose
967   *         value is a message that provides additional information about the
968   *         remote assurance processing.
969   *       </LI>
970   *       <LI>
971   *         {@code csn} -- An optional string field whose
972   *         value is the change sequence number that the server assigned for
973   *         the operation.
974   *       </LI>
975   *       <LI>
976   *         {@code server-results} -- An optional array field whose values are
977   *         JSON objects with information about the individual results from the
978   *         local and/or remote servers used in replication assurance
979   *         processing.  These JSON objects will use the following fields:
980   *         <UL>
981   *           <LI>
982   *             {@code result-code-value} -- An integer field whose value is
983   *             the numeric value for the
984   *             {@link AssuredReplicationServerResultCode} for the server
985   *             result.
986   *           </LI>
987   *           <LI>
988   *             {@code result-code-name} -- An optional string field whose
989   *             value is the name of the result code for the server result.
990   *           </LI>
991   *           <LI>
992   *             {@code replication-server-id} -- An optional integer field
993   *             whose value is the server ID for the associated replication
994   *             server.
995   *           </LI>
996   *           <LI>
997   *             {@code replica-id} -- An optional integer field whose value is
998   *             the replica ID for the associated replica.
999   *           </LI>
1000   *         </UL>
1001   *       </LI>
1002   *     </UL>
1003   *   </LI>
1004   * </UL>
1005   *
1006   * @return  A JSON object that contains a representation of this control.
1007   */
1008  @Override()
1009  @NotNull()
1010  public JSONObject toJSONControl()
1011  {
1012    final Map<String,JSONValue> jsonValueFields = new LinkedHashMap<>();
1013
1014    if (localLevel != null)
1015    {
1016      jsonValueFields.put(JSON_FIELD_LOCAL_LEVEL,
1017           new JSONString(localLevel.getName()));
1018    }
1019
1020    jsonValueFields.put(JSON_FIELD_LOCAL_ASSURANCE_SATISFIED,
1021         new JSONBoolean(localAssuranceSatisfied));
1022
1023    if (localAssuranceMessage != null)
1024    {
1025      jsonValueFields.put(JSON_FIELD_LOCAL_ASSURANCE_MESSAGE,
1026           new JSONString(localAssuranceMessage));
1027    }
1028
1029    if (remoteLevel != null)
1030    {
1031      jsonValueFields.put(JSON_FIELD_REMOTE_LEVEL,
1032           new JSONString(remoteLevel.getName()));
1033    }
1034
1035    jsonValueFields.put(JSON_FIELD_REMOTE_ASSURANCE_SATISFIED,
1036         new JSONBoolean(remoteAssuranceSatisfied));
1037
1038    if (remoteAssuranceMessage != null)
1039    {
1040      jsonValueFields.put(JSON_FIELD_REMOTE_ASSURANCE_MESSAGE,
1041           new JSONString(remoteAssuranceMessage));
1042    }
1043
1044    if (csn != null)
1045    {
1046      jsonValueFields.put(JSON_FIELD_CSN, new JSONString(csn));
1047    }
1048
1049    if ((serverResults != null) && (! serverResults.isEmpty()))
1050    {
1051      final List<JSONValue> serverResultValues =
1052           new ArrayList<>(serverResults.size());
1053      for (final AssuredReplicationServerResult serverResult : serverResults)
1054      {
1055        final Map<String,JSONValue> serverResultFields = new LinkedHashMap<>();
1056        serverResultFields.put(JSON_FIELD_SERVER_RESULTS_RESULT_CODE_VALUE,
1057             new JSONNumber(serverResult.getResultCode().intValue()));
1058        serverResultFields.put(JSON_FIELD_SERVER_RESULTS_RESULT_CODE_NAME,
1059             new JSONString(serverResult.getResultCode().name()));
1060
1061        final Short replicationServerID = serverResult.getReplicationServerID();
1062        if (replicationServerID != null)
1063        {
1064          serverResultFields.put(
1065               JSON_FIELD_SERVER_RESULTS_REPLICATION_SERVER_ID,
1066               new JSONNumber(replicationServerID.longValue()));
1067        }
1068
1069        final Short replicaID = serverResult.getReplicaID();
1070        if (replicaID != null)
1071        {
1072          serverResultFields.put(JSON_FIELD_SERVER_RESULTS_REPLICA_ID,
1073               new JSONNumber(replicaID.longValue()));
1074        }
1075
1076        serverResultValues.add(new JSONObject(serverResultFields));
1077      }
1078
1079      jsonValueFields.put(JSON_FIELD_SERVER_RESULTS,
1080           new JSONArray(serverResultValues));
1081    }
1082
1083
1084    return new JSONObject(
1085         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
1086              ASSURED_REPLICATION_RESPONSE_OID),
1087         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
1088              INFO_CONTROL_NAME_ASSURED_REPLICATION_RESPONSE.get()),
1089         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
1090              isCritical()),
1091         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
1092              new JSONObject(jsonValueFields)));
1093  }
1094
1095
1096
1097  /**
1098   * Attempts to decode the provided object as a JSON representation of an
1099   * assured replication response control.
1100   *
1101   * @param  controlObject  The JSON object to be decoded.  It must not be
1102   *                        {@code null}.
1103   * @param  strict         Indicates whether to use strict mode when decoding
1104   *                        the provided JSON object.  If this is {@code true},
1105   *                        then this method will throw an exception if the
1106   *                        provided JSON object contains any unrecognized
1107   *                        fields.  If this is {@code false}, then unrecognized
1108   *                        fields will be ignored.
1109   *
1110   * @return  The assured replication control that was decoded from the provided
1111   *          JSON object.
1112   *
1113   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
1114   *                         valid assured replication response control.
1115   */
1116  @NotNull()
1117  public static AssuredReplicationResponseControl decodeJSONControl(
1118              @NotNull final JSONObject controlObject,
1119              final boolean strict)
1120         throws LDAPException
1121  {
1122    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
1123         controlObject, strict, true, true);
1124
1125    final ASN1OctetString rawValue = jsonControl.getRawValue();
1126    if (rawValue != null)
1127    {
1128      return new AssuredReplicationResponseControl(jsonControl.getOID(),
1129           jsonControl.getCriticality(), rawValue);
1130    }
1131
1132
1133    AssuredReplicationLocalLevel localLevel = null;
1134    AssuredReplicationRemoteLevel remoteLevel = null;
1135    Boolean localAssuranceSatisfied = null;
1136    Boolean remoteAssuranceSatisfied = null;
1137    String csn = null;
1138    String localAssuranceMessage = null;
1139    String remoteAssuranceMessage = null;
1140    final List<AssuredReplicationServerResult> serverResults =
1141         new ArrayList<>();
1142    final JSONObject valueObject = jsonControl.getValueObject();
1143
1144    final String localLevelStr =
1145         valueObject.getFieldAsString(JSON_FIELD_LOCAL_LEVEL);
1146    if (localLevelStr != null)
1147    {
1148      localLevel = AssuredReplicationLocalLevel.forName(localLevelStr);
1149      if (localLevel == null)
1150      {
1151        throw new LDAPException(ResultCode.DECODING_ERROR,
1152             ERR_ASSURED_REPLICATION_RESPONSE_JSON_INVALID_LOCAL_LEVEL.get(
1153                  controlObject.toSingleLineString(), localLevelStr));
1154      }
1155    }
1156
1157    localAssuranceSatisfied =
1158         valueObject.getFieldAsBoolean(JSON_FIELD_LOCAL_ASSURANCE_SATISFIED);
1159    if (localAssuranceSatisfied == null)
1160    {
1161      throw new LDAPException(ResultCode.DECODING_ERROR,
1162           ERR_ASSURED_REPLICATION_RESPONSE_JSON_MISSING_VALUE_FIELD.get(
1163                controlObject.toSingleLineString(),
1164                JSON_FIELD_LOCAL_ASSURANCE_SATISFIED));
1165    }
1166
1167    localAssuranceMessage =
1168         valueObject.getFieldAsString(JSON_FIELD_LOCAL_ASSURANCE_MESSAGE);
1169
1170    final String remoteLevelStr =
1171         valueObject.getFieldAsString(JSON_FIELD_REMOTE_LEVEL);
1172    if (remoteLevelStr != null)
1173    {
1174      remoteLevel = AssuredReplicationRemoteLevel.forName(remoteLevelStr);
1175      if (remoteLevel == null)
1176      {
1177        throw new LDAPException(ResultCode.DECODING_ERROR,
1178             ERR_ASSURED_REPLICATION_RESPONSE_JSON_INVALID_REMOTE_LEVEL.get(
1179                  controlObject.toSingleLineString(), remoteLevelStr));
1180      }
1181    }
1182
1183    remoteAssuranceSatisfied =
1184         valueObject.getFieldAsBoolean(JSON_FIELD_REMOTE_ASSURANCE_SATISFIED);
1185    if (remoteAssuranceSatisfied == null)
1186    {
1187      throw new LDAPException(ResultCode.DECODING_ERROR,
1188           ERR_ASSURED_REPLICATION_RESPONSE_JSON_MISSING_VALUE_FIELD.get(
1189                controlObject.toSingleLineString(),
1190                JSON_FIELD_REMOTE_ASSURANCE_SATISFIED));
1191    }
1192
1193    remoteAssuranceMessage =
1194         valueObject.getFieldAsString(JSON_FIELD_REMOTE_ASSURANCE_MESSAGE);
1195
1196    csn = valueObject.getFieldAsString(JSON_FIELD_CSN);
1197
1198    final List<JSONValue> serverResultValues =
1199         valueObject.getFieldAsArray(JSON_FIELD_SERVER_RESULTS);
1200    if (serverResultValues != null)
1201    {
1202      for (final JSONValue serverResultValue : serverResultValues)
1203      {
1204        serverResults.add(decodeServerResult(controlObject, serverResultValue,
1205             strict));
1206      }
1207    }
1208
1209
1210    if (strict)
1211    {
1212      final List<String> unrecognizedFields =
1213           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1214                valueObject, JSON_FIELD_LOCAL_LEVEL,
1215                JSON_FIELD_LOCAL_ASSURANCE_SATISFIED,
1216                JSON_FIELD_LOCAL_ASSURANCE_MESSAGE, JSON_FIELD_REMOTE_LEVEL,
1217                JSON_FIELD_REMOTE_ASSURANCE_SATISFIED,
1218                JSON_FIELD_REMOTE_ASSURANCE_MESSAGE, JSON_FIELD_CSN,
1219                JSON_FIELD_SERVER_RESULTS);
1220      if (! unrecognizedFields.isEmpty())
1221      {
1222        throw new LDAPException(ResultCode.DECODING_ERROR,
1223             ERR_ASSURED_REPLICATION_RESPONSE_JSON_UNRECOGNIZED_VALUE_FIELD.get(
1224                  controlObject.toSingleLineString(),
1225                  unrecognizedFields.get(0)));
1226      }
1227    }
1228
1229
1230    return new AssuredReplicationResponseControl(localLevel,
1231         localAssuranceSatisfied, localAssuranceMessage, remoteLevel,
1232         remoteAssuranceSatisfied, remoteAssuranceMessage, csn, serverResults);
1233  }
1234
1235
1236
1237  /**
1238   * Decodes the provided JSON value as an assured replication server result
1239   * object.
1240   *
1241   * @param  controlObject      The JSON object that contains an encoded
1242   *                            representation of a control being decoded.  It
1243   *                            must not be {@code null}.
1244   * @param  serverResultValue  The JSON value to be decoded.  It must not be
1245   *                            {@code null}.
1246   * @param  strict             Indicates whether to use strict mode when
1247   *                            decoding the server result.
1248   *
1249   * @return  The server result value that was decoded.
1250   *
1251   * @throws  LDAPException  If the provided value cannot be decoded as a server
1252   *                         result.
1253   */
1254  @NotNull()
1255  private static AssuredReplicationServerResult decodeServerResult(
1256               @NotNull final JSONObject controlObject,
1257               @NotNull final JSONValue serverResultValue,
1258               final boolean strict)
1259          throws LDAPException
1260  {
1261    final JSONObject resultObject;
1262    if (serverResultValue instanceof JSONObject)
1263    {
1264      resultObject = (JSONObject) serverResultValue;
1265    }
1266    else
1267    {
1268      throw new LDAPException(ResultCode.DECODING_ERROR,
1269           ERR_ASSURED_REPLICATION_RESPONSE_JSON_SERVER_RESULT_NOT_OBJECT.get(
1270                controlObject.toSingleLineString(),
1271                JSON_FIELD_SERVER_RESULTS));
1272    }
1273
1274    final Integer resultCodeValue = resultObject.getFieldAsInteger(
1275         JSON_FIELD_SERVER_RESULTS_RESULT_CODE_VALUE);
1276    if (resultCodeValue == null)
1277    {
1278      throw new LDAPException(ResultCode.DECODING_ERROR,
1279           ERR_ASSURED_REPLICATION_RESPONSE_JSON_SERVER_RESULT_NO_RC.get(
1280                controlObject.toSingleLineString(),
1281                JSON_FIELD_SERVER_RESULTS_RESULT_CODE_VALUE));
1282    }
1283
1284    final AssuredReplicationServerResultCode resultCode =
1285         AssuredReplicationServerResultCode.valueOf(resultCodeValue);
1286    if (resultCode == null)
1287    {
1288      throw new LDAPException(ResultCode.DECODING_ERROR,
1289           ERR_ASSURED_REPLICATION_RESPONSE_JSON_SERVER_RESULT_UNKNOWN_RC.get(
1290                controlObject.toSingleLineString(), resultCodeValue));
1291    }
1292
1293    final Integer replicationServerID = resultObject.getFieldAsInteger(
1294         JSON_FIELD_SERVER_RESULTS_REPLICATION_SERVER_ID);
1295    final Integer replicaID = resultObject.getFieldAsInteger(
1296         JSON_FIELD_SERVER_RESULTS_REPLICA_ID);
1297
1298
1299    if (strict)
1300    {
1301      final List<String> unrecognizedFields =
1302           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1303                resultObject, JSON_FIELD_SERVER_RESULTS_RESULT_CODE_VALUE,
1304                JSON_FIELD_SERVER_RESULTS_RESULT_CODE_NAME,
1305                JSON_FIELD_SERVER_RESULTS_REPLICATION_SERVER_ID,
1306                JSON_FIELD_SERVER_RESULTS_REPLICA_ID);
1307      if (! unrecognizedFields.isEmpty())
1308      {
1309        throw new LDAPException(ResultCode.DECODING_ERROR,
1310             ERR_ASSURED_REPLICATION_RESPONSE_JSON_UNRECOGNIZED_SR_FIELD.get(
1311                  controlObject.toSingleLineString(),
1312                  unrecognizedFields.get(0)));
1313      }
1314    }
1315
1316
1317    return new AssuredReplicationServerResult(resultCode,
1318         (replicationServerID != null)
1319              ? replicationServerID.shortValue()
1320              : null,
1321         (replicaID != null)
1322              ? replicaID.shortValue()
1323              : null);
1324  }
1325
1326
1327
1328  /**
1329   * {@inheritDoc}
1330   */
1331  @Override()
1332  public void toString(@NotNull final StringBuilder buffer)
1333  {
1334    buffer.append("AssuredReplicationResponseControl(isCritical=");
1335    buffer.append(isCritical());
1336
1337    if (localLevel != null)
1338    {
1339      buffer.append(", localLevel=");
1340      buffer.append(localLevel.name());
1341    }
1342
1343    buffer.append(", localAssuranceSatisfied=");
1344    buffer.append(localAssuranceSatisfied);
1345
1346    if (localAssuranceMessage != null)
1347    {
1348      buffer.append(", localMessage='");
1349      buffer.append(localAssuranceMessage);
1350      buffer.append('\'');
1351    }
1352
1353    if (remoteLevel != null)
1354    {
1355      buffer.append(", remoteLevel=");
1356      buffer.append(remoteLevel.name());
1357    }
1358
1359    buffer.append(", remoteAssuranceSatisfied=");
1360    buffer.append(remoteAssuranceSatisfied);
1361
1362    if (remoteAssuranceMessage != null)
1363    {
1364      buffer.append(", remoteMessage='");
1365      buffer.append(remoteAssuranceMessage);
1366      buffer.append('\'');
1367    }
1368
1369    if (csn != null)
1370    {
1371      buffer.append(", csn='");
1372      buffer.append(csn);
1373      buffer.append('\'');
1374    }
1375
1376    if ((serverResults != null) && (! serverResults.isEmpty()))
1377    {
1378      buffer.append(", serverResults={");
1379
1380      final Iterator<AssuredReplicationServerResult> iterator =
1381           serverResults.iterator();
1382      while (iterator.hasNext())
1383      {
1384        iterator.next().toString(buffer);
1385
1386        if (iterator.hasNext())
1387        {
1388          buffer.append(", ");
1389        }
1390      }
1391
1392      buffer.append('}');
1393    }
1394
1395    buffer.append(')');
1396  }
1397}