001/*
002 * Copyright 2010-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2010-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) 2010-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.extensions;
037
038
039
040import java.util.ArrayList;
041import java.util.Collections;
042import java.util.List;
043
044import com.unboundid.asn1.ASN1Boolean;
045import com.unboundid.asn1.ASN1Element;
046import com.unboundid.asn1.ASN1Integer;
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.asn1.ASN1Sequence;
049import com.unboundid.ldap.sdk.Control;
050import com.unboundid.ldap.sdk.ExtendedResult;
051import com.unboundid.ldap.sdk.LDAPException;
052import com.unboundid.ldap.sdk.LDAPResult;
053import com.unboundid.ldap.sdk.ResultCode;
054import com.unboundid.util.Base64;
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;
062
063import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
064
065
066
067/**
068 * This class provides an extended result that may be used to obtain information
069 * about the results of processing a get changelog batch extended request.
070 * <BR>
071 * <BLOCKQUOTE>
072 *   <B>NOTE:</B>  This class, and other classes within the
073 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
074 *   supported for use against Ping Identity, UnboundID, and
075 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
076 *   for proprietary functionality or for external specifications that are not
077 *   considered stable or mature enough to be guaranteed to work in an
078 *   interoperable way with other types of LDAP servers.
079 * </BLOCKQUOTE>
080 * <BR>
081 * The changelog batch result value is encoded as follows:
082 * <PRE>
083 *   ChangelogBatchResult ::= SEQUENCE {
084 *        resumeToken                   [0] OCTET STRING OPTIONAL,
085 *        moreChangesAvailable          [1] BOOLEAN,
086 *        changesAlreadyPurged          [2] BOOLEAN DEFAULT FALSE,
087 *        additionalInfo                [3] OCTET STRING OPTIONAL,
088 *        estimatedChangesRemaining     [4] INTEGER (0 .. MAXINT) OPTIONAL,
089 *        ... }
090 * </PRE>
091 * <BR><BR>
092 * See the documentation for the {@link GetChangelogBatchExtendedRequest} class
093 * for an example demonstrating its use.
094 */
095@NotMutable()
096@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
097public final class GetChangelogBatchExtendedResult
098       extends ExtendedResult
099{
100  /**
101   * The BER type for the resume token element.
102   */
103  private static final byte TYPE_RESUME_TOKEN = (byte) 0x80;
104
105
106
107  /**
108   * The BER type for the more changes available element.
109   */
110  private static final byte TYPE_MORE_CHANGES_AVAILABLE = (byte) 0x81;
111
112
113
114  /**
115   * The BER type for the changes already purged element.
116   */
117  private static final byte TYPE_CHANGES_ALREADY_PURGED = (byte) 0x82;
118
119
120
121  /**
122   * The BER type for the additional info element.
123   */
124  private static final byte TYPE_ADDITIONAL_INFO = (byte) 0x83;
125
126
127
128  /**
129   * The BER type for the estimated changes remaining element.
130   */
131  private static final byte TYPE_ESTIMATED_CHANGES_REMAINING = (byte) 0x84;
132
133
134
135  /**
136   * The serial version UID for this serializable object.
137   */
138  private static final long serialVersionUID = -1997815252100989148L;
139
140
141
142  // The resume token for this extended result.
143  @Nullable private final ASN1OctetString resumeToken;
144
145  // Indicates whether some changes in the requested batch may have already
146  // been purged.
147  private final boolean changesAlreadyPurged;
148
149  // Indicates whether the server has additional results that are immediately
150  // available without waiting.
151  private final boolean moreChangesAvailable;
152
153  // The estimated number of remaining changes, if available.
154  private final int estimatedChangesRemaining;
155
156  // The number of entries returned to the client.
157  private final int entryCount;
158
159  // A list of the entries returned to the client.
160  @Nullable private final List<ChangelogEntryIntermediateResponse> entryList;
161
162  // A message with additional information about the result.
163  @Nullable private final String additionalInfo;
164
165
166
167  /**
168   * Creates a new get changelog batch extended result with only the generic
169   * LDAP result information and no extended value.
170   *
171   * @param  r  An LDAP result with general details of the response.  It must
172   *            not be {@code null}.
173   */
174  public GetChangelogBatchExtendedResult(@NotNull final LDAPResult r)
175  {
176    super(r.getMessageID(), r.getResultCode(), r.getDiagnosticMessage(),
177         r.getMatchedDN(), r.getReferralURLs(), null, null,
178         r.getResponseControls());
179
180    resumeToken               = null;
181    changesAlreadyPurged      = false;
182    moreChangesAvailable      = false;
183    estimatedChangesRemaining = -1;
184    entryCount                = -1;
185    entryList                 = null;
186    additionalInfo            = null;
187  }
188
189
190
191  /**
192   * Creates a new get changelog batch extended result with the provided
193   * information.
194   *
195   * @param  r                     An LDAP result with general details of the
196   *                               response.  It must not be {@code null}.
197   * @param  entryCount            The number of entries returned.  It may be
198   *                               less than zero to indicate that the number of
199   *                               entries is unknown.
200   * @param  resumeToken           A token which may be used to resume
201   *                               retrieving changes at the point immediately
202   *                               after the last change returned.  It may be
203   *                               {@code null} only if this result represents
204   *                               an error that prevented the operation from
205   *                               being successfully processed.
206   * @param  moreChangesAvailable  Indicates whether there may be more changes
207   *                               immediately available to retrieve from the
208   *                               server.
209   * @param  changesAlreadyPurged  Indicates whether the server may have already
210   *                               purged changes after the starting point
211   *                               referenced by the associated request.
212   * @param  additionalInfo        A message with additional information about
213   *                               the status of the processing.  It may be
214   *                               {@code null} if no additional message is
215   *                               available.
216   */
217  public GetChangelogBatchExtendedResult(@NotNull final LDAPResult r,
218              final int entryCount,
219              @Nullable final ASN1OctetString resumeToken,
220              final boolean moreChangesAvailable,
221              final boolean changesAlreadyPurged,
222              @Nullable final String additionalInfo)
223  {
224    this(r, entryCount, resumeToken, moreChangesAvailable, -1,
225         changesAlreadyPurged, additionalInfo);
226  }
227
228
229
230  /**
231   * Creates a new get changelog batch extended result with the provided
232   * information.
233   *
234   * @param  r                          An LDAP result with general details of
235   *                                    the response.  It must not be
236   *                                    {@code null}.
237   * @param  entryCount                 The number of entries returned.  It may
238   *                                    be less than zero to indicate that the
239   *                                    number of entries is unknown.
240   * @param  resumeToken                A token which may be used to resume
241   *                                    retrieving changes at the point
242   *                                    immediately after the last change
243   *                                    returned.  It may be {@code null} only
244   *                                    if this result represents an error that
245   *                                    prevented the operation from being
246   *                                    successfully processed.
247   * @param  moreChangesAvailable       Indicates whether there may be more
248   *                                    changes immediately available to
249   *                                    retrieve from the server.
250   * @param  estimatedChangesRemaining  An estimate of the number of changes
251   *                                    remaining to be retrieved.  A value less
252   *                                    than zero will be interpreted as
253   *                                    "unknown".
254   * @param  changesAlreadyPurged       Indicates whether the server may have
255   *                                    already purged changes after the
256   *                                    starting point referenced by the
257   *                                    associated request.
258   * @param  additionalInfo             A message with additional information
259   *                                    about the status of the processing.  It
260   *                                    may be {@code null} if no additional
261   *                                    message is available.
262   */
263  public GetChangelogBatchExtendedResult(@NotNull final LDAPResult r,
264              final int entryCount,
265              @Nullable final ASN1OctetString resumeToken,
266              final boolean moreChangesAvailable,
267              final int estimatedChangesRemaining,
268              final boolean changesAlreadyPurged,
269              @Nullable final String additionalInfo)
270  {
271    super(r.getMessageID(), r.getResultCode(), r.getDiagnosticMessage(),
272         r.getMatchedDN(), r.getReferralURLs(), null,
273         encodeValue(resumeToken, moreChangesAvailable,
274              estimatedChangesRemaining, changesAlreadyPurged, additionalInfo),
275         r.getResponseControls());
276
277    this.resumeToken          = resumeToken;
278    this.moreChangesAvailable = moreChangesAvailable;
279    this.changesAlreadyPurged = changesAlreadyPurged;
280    this.additionalInfo       = additionalInfo;
281
282    if (estimatedChangesRemaining >= 0)
283    {
284      this.estimatedChangesRemaining = estimatedChangesRemaining;
285    }
286    else
287    {
288      this.estimatedChangesRemaining = -1;
289    }
290
291    entryList = null;
292    if (entryCount < 0)
293    {
294      this.entryCount = -1;
295    }
296    else
297    {
298      this.entryCount = entryCount;
299    }
300  }
301
302
303
304  /**
305   * Creates a new get changelog batch extended result with the provided
306   * information.
307   *
308   * @param  extendedResult  A generic extended result to be parsed as a get
309   *                         changelog batch extended result.  It must not be
310   *                         {@code null}.
311   * @param  entryCount      The number of entries returned to the client.  It
312   *                         may be less than zero to indicate that the entry
313   *                         count is unknown.
314   *
315   * @throws  LDAPException  If the provided extended result cannot be parsed as
316   *                         a get changelog batch result.
317   */
318  public GetChangelogBatchExtendedResult(
319              @NotNull final ExtendedResult extendedResult,
320              final int entryCount)
321         throws LDAPException
322  {
323    this(extendedResult, entryCount, null);
324  }
325
326
327
328  /**
329   * Creates a new get changelog batch extended result with the provided
330   * information.
331   *
332   * @param  extendedResult  A generic extended result to be parsed as a get
333   *                         changelog batch extended result.  It must not be
334   *                         {@code null}.
335   * @param  entryList       A list of the entries returned to the client.  It
336   *                         may be empty to indicate that no entries were
337   *                         returned, but it must not be {@code null}.
338   *
339   * @throws  LDAPException  If the provided extended result cannot be parsed as
340   *                         a get changelog batch result.
341   */
342  public GetChangelogBatchExtendedResult(
343              @NotNull final ExtendedResult extendedResult,
344              @NotNull final List<ChangelogEntryIntermediateResponse> entryList)
345         throws LDAPException
346  {
347    this(extendedResult, entryList.size(), entryList);
348  }
349
350
351
352  /**
353   * Creates a new get changelog batch extended result with the provided
354   * information.
355   *
356   * @param  r           A generic extended result to be parsed as a get
357   *                     changelog batch extended result.  It must not be
358   *                     {@code null}.
359   * @param  entryCount  The number of entries returned to the client.  It may
360   *                     be less than zero to indicate that the entry count is
361   *                     unknown.
362   * @param  entryList   A list of the entries returned to the client.  It may
363   *                     be empty to indicate that no entries were returned, or
364   *                     {@code null} if the entry list is not available.
365   *
366   * @throws  LDAPException  If the provided extended result cannot be parsed as
367   *                         a get changelog batch result.
368   */
369  private GetChangelogBatchExtendedResult(@NotNull final ExtendedResult r,
370       final int entryCount,
371       @Nullable final List<ChangelogEntryIntermediateResponse> entryList)
372       throws LDAPException
373  {
374    super(r);
375
376    if (entryList == null)
377    {
378      this.entryList = null;
379    }
380    else
381    {
382      this.entryList = Collections.unmodifiableList(entryList);
383    }
384
385    if (entryCount < 0)
386    {
387      this.entryCount = -1;
388    }
389    else
390    {
391      this.entryCount = entryCount;
392    }
393
394    final ASN1OctetString value = r.getValue();
395    if (value == null)
396    {
397      // See if an entry list was provided and we can get a resume token from
398      // it.
399      if ((entryList != null) && (! entryList.isEmpty()))
400      {
401        resumeToken = entryList.get(entryList.size() - 1).getResumeToken();
402      }
403      else
404      {
405        resumeToken = null;
406      }
407
408      moreChangesAvailable      = false;
409      estimatedChangesRemaining = -1;
410      changesAlreadyPurged      = false;
411      additionalInfo            = null;
412      return;
413    }
414
415    final ASN1Element[] valueElements;
416    try
417    {
418      valueElements =
419           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
420    }
421    catch (final Exception e)
422    {
423      Debug.debugException(e);
424      throw new LDAPException(ResultCode.DECODING_ERROR,
425           ERR_GET_CHANGELOG_BATCH_RES_VALUE_NOT_SEQUENCE.get(
426                StaticUtils.getExceptionMessage(e)), e);
427    }
428
429    ASN1OctetString token = null;
430    Boolean moreChanges = null;
431    boolean missingChanges = false;
432    int changesRemaining = -1;
433    String message = null;
434
435    try
436    {
437      for (final ASN1Element e : valueElements)
438      {
439        final byte type = e.getType();
440        switch (type)
441        {
442          case TYPE_RESUME_TOKEN:
443            token = ASN1OctetString.decodeAsOctetString(e);
444            break;
445          case TYPE_MORE_CHANGES_AVAILABLE:
446            moreChanges = ASN1Boolean.decodeAsBoolean(e).booleanValue();
447            break;
448          case TYPE_CHANGES_ALREADY_PURGED:
449            missingChanges = ASN1Boolean.decodeAsBoolean(e).booleanValue();
450            break;
451          case TYPE_ADDITIONAL_INFO:
452            message = ASN1OctetString.decodeAsOctetString(e).stringValue();
453            break;
454          case TYPE_ESTIMATED_CHANGES_REMAINING:
455            changesRemaining = ASN1Integer.decodeAsInteger(e).intValue();
456            if (changesRemaining < 0)
457            {
458              changesRemaining = -1;
459            }
460            break;
461          default:
462            throw new LDAPException(ResultCode.DECODING_ERROR,
463                 ERR_GET_CHANGELOG_BATCH_RES_UNEXPECTED_VALUE_ELEMENT.get(
464                      StaticUtils.toHex(type)));
465        }
466      }
467    }
468    catch (final LDAPException le)
469    {
470      Debug.debugException(le);
471      throw le;
472    }
473    catch (final Exception e)
474    {
475      Debug.debugException(e);
476      throw new LDAPException(ResultCode.DECODING_ERROR,
477           ERR_GET_CHANGELOG_BATCH_RES_ERROR_PARSING_VALUE.get(
478                StaticUtils.getExceptionMessage(e)), e);
479    }
480
481    if (moreChanges == null)
482    {
483      throw new LDAPException(ResultCode.DECODING_ERROR,
484           ERR_GET_CHANGELOG_BATCH_RES_MISSING_MORE.get());
485    }
486
487    resumeToken               = token;
488    moreChangesAvailable      = moreChanges;
489    changesAlreadyPurged      = missingChanges;
490    estimatedChangesRemaining = changesRemaining;
491    additionalInfo            = message;
492  }
493
494
495
496  /**
497   * Encodes the provided information in a form suitable for use as the value of
498   * this extended result.
499   *
500   * @param  resumeToken                A token which may be used to resume
501   *                                    retrieving changes at the point
502   *                                    immediately after the last change
503   *                                    returned.  It may be {@code null} only
504   *                                    if this result represents an error that
505   *                                    prevented the operation from being
506   *                                    successfully processed.
507   * @param  moreChangesAvailable       Indicates whether there may be more
508   *                                    changes immediately available to
509   *                                    retrieve from the server.
510   * @param  estimatedChangesRemaining  An estimate of the number of changes
511   *                                    remaining to be retrieved.  A value less
512   *                                    than zero will be interpreted as
513   *                                    "unknown".
514   * @param  changesAlreadyPurged       Indicates whether the server may have
515   *                                    already purged changes after the
516   *                                    starting point referenced by the
517   *                                    associated request.
518   * @param  additionalInfo             A message with additional information
519   *                                    about the status of the processing.  It
520   *                                    may be {@code null} if no additional
521   *                                    message is available.
522   *
523   * @return  The ASN.1 octet string to use as the result, or {@code null} if
524   *          there should be no value.
525   */
526  @Nullable()
527  private static ASN1OctetString encodeValue(
528               @Nullable final ASN1OctetString resumeToken,
529               final boolean moreChangesAvailable,
530               final int estimatedChangesRemaining,
531               final boolean changesAlreadyPurged,
532               @Nullable final String additionalInfo)
533  {
534    final ArrayList<ASN1Element> elements = new ArrayList<>(5);
535
536    if (resumeToken != null)
537    {
538      elements.add(new ASN1OctetString(TYPE_RESUME_TOKEN,
539           resumeToken.getValue()));
540    }
541
542    elements.add(new ASN1Boolean(TYPE_MORE_CHANGES_AVAILABLE,
543         moreChangesAvailable));
544
545    if (estimatedChangesRemaining >= 0)
546    {
547      elements.add(new ASN1Integer(TYPE_ESTIMATED_CHANGES_REMAINING,
548           estimatedChangesRemaining));
549    }
550
551    if (changesAlreadyPurged)
552    {
553      elements.add(new ASN1Boolean(TYPE_CHANGES_ALREADY_PURGED,
554           changesAlreadyPurged));
555    }
556
557    if (additionalInfo != null)
558    {
559      elements.add(new ASN1OctetString(TYPE_ADDITIONAL_INFO, additionalInfo));
560    }
561
562    return new ASN1OctetString(new ASN1Sequence(elements).encode());
563  }
564
565
566
567  /**
568   * Retrieves a token that may be used to resume the process of retrieving
569   * changes at the point after the last change received.  It may be
570   * {@code null} if this result represents an error that prevented the
571   * operation from being processed successfully.
572   *
573   * @return  A token that may be used to resume the process of retrieving
574   *          changes at the point after the last change received, or
575   *          {@code null} if none is available.
576   */
577  @Nullable()
578  public ASN1OctetString getResumeToken()
579  {
580    return resumeToken;
581  }
582
583
584
585  /**
586   * Indicates whether the server indicated that more changes may be immediately
587   * available without waiting.  The value of this argument is only meaningful
588   * if {@link #hasValue()} returns {@code true}.
589   *
590   * @return  {@code true} if the server indicated that more changes may be
591   *          immediately available without waiting, or {@code false} if not.
592   */
593  public boolean moreChangesAvailable()
594  {
595    return moreChangesAvailable;
596  }
597
598
599
600  /**
601   * Retrieves an estimate of the number of changes that may be immediately
602   * available to be retrieved from the server, if available.
603   *
604   * @return  An estimate of the number of changes that may be immediately
605   *          available to be retrieved from the server, or -1 if that
606   *          information is not available.
607   */
608  public int getEstimatedChangesRemaining()
609  {
610    return estimatedChangesRemaining;
611  }
612
613
614
615  /**
616   * Indicates whether the server indicated that it may have already purged one
617   * or more changes after the starting point for the associated request and
618   * therefore the results returned may be missing changes.  The value of this
619   * argument is only meaningful if {@link #hasValue()} returns {@code true}.
620   *
621   * @return  {@code true} if the server indicated that it may have already
622   *          purged one or more changes after the starting point, or
623   *          {@code false} if not.
624   */
625  public boolean changesAlreadyPurged()
626  {
627    return changesAlreadyPurged;
628  }
629
630
631
632  /**
633   * Retrieves a message with additional information about the processing that
634   * occurred, if available.
635   *
636   * @return  A message with additional information about the processing that
637   *          occurred, or {@code null} if none is available.
638   */
639  @Nullable()
640  public String getAdditionalInfo()
641  {
642    return additionalInfo;
643  }
644
645
646
647  /**
648   * Retrieves the number of entries returned by the server in the course of
649   * processing the extended operation.  A value of -1 indicates that the entry
650   * count is not known.
651   *
652   * @return  The number of entries returned by the server in the course of
653   *          processing the extended operation, 0 if no entries were returned,
654   *          or -1 if the entry count is not known.
655   */
656  public int getEntryCount()
657  {
658    return entryCount;
659  }
660
661
662
663  /**
664   * Retrieves a list containing the entries that were returned by the server in
665   * the course of processing the extended operation, if available.  An entry
666   * list will not be available if a custom {@link ChangelogEntryListener} was
667   * used for the request, and it may not be available if an error was
668   * encountered during processing.
669   *
670   * @return  A list containing the entries that were returned by the server in
671   *          the course of processing the extended operation, or {@code null}
672   *          if an entry list is not available.
673   */
674  @Nullable()
675  public List<ChangelogEntryIntermediateResponse> getChangelogEntries()
676  {
677    return entryList;
678  }
679
680
681
682  /**
683   * {@inheritDoc}
684   */
685  @Override()
686  @NotNull()
687  public String getExtendedResultName()
688  {
689    return INFO_GET_CHANGELOG_BATCH_RES_NAME.get();
690  }
691
692
693
694  /**
695   * {@inheritDoc}
696   */
697  @Override()
698  public void toString(@NotNull final StringBuilder buffer)
699  {
700    buffer.append("ExtendedResult(resultCode=");
701    buffer.append(getResultCode());
702
703    final int messageID = getMessageID();
704    if (messageID >= 0)
705    {
706      buffer.append(", messageID=");
707      buffer.append(messageID);
708    }
709
710    final String diagnosticMessage = getDiagnosticMessage();
711    if (diagnosticMessage != null)
712    {
713      buffer.append(", diagnosticMessage='");
714      buffer.append(diagnosticMessage);
715      buffer.append('\'');
716    }
717
718    final String matchedDN = getMatchedDN();
719    if (matchedDN != null)
720    {
721      buffer.append(", matchedDN='");
722      buffer.append(matchedDN);
723      buffer.append('\'');
724    }
725
726    final String[] referralURLs = getReferralURLs();
727    if (referralURLs.length > 0)
728    {
729      buffer.append(", referralURLs={");
730      for (int i=0; i < referralURLs.length; i++)
731      {
732        if (i > 0)
733        {
734          buffer.append(", ");
735        }
736
737        buffer.append(referralURLs[i]);
738      }
739      buffer.append('}');
740    }
741
742    if (resumeToken != null)
743    {
744      buffer.append(", resumeToken='");
745      Base64.encode(resumeToken.getValue(), buffer);
746      buffer.append('\'');
747    }
748
749    buffer.append(", moreChangesAvailable=");
750    buffer.append(moreChangesAvailable);
751
752    buffer.append(", estimatedChangesRemaining=");
753    buffer.append(estimatedChangesRemaining);
754
755    buffer.append(", changesAlreadyPurged=");
756    buffer.append(changesAlreadyPurged);
757
758    if (additionalInfo != null)
759    {
760      buffer.append(", additionalInfo='");
761      buffer.append(additionalInfo);
762      buffer.append('\'');
763    }
764
765    buffer.append(", entryCount=");
766    buffer.append(entryCount);
767
768
769    final Control[] responseControls = getResponseControls();
770    if (responseControls.length > 0)
771    {
772      buffer.append(", responseControls={");
773      for (int i=0; i < responseControls.length; i++)
774      {
775        if (i > 0)
776        {
777          buffer.append(", ");
778        }
779
780        buffer.append(responseControls[i]);
781      }
782      buffer.append('}');
783    }
784
785    buffer.append(')');
786  }
787}