001/*
002 * Copyright 2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020 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) 2020 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.tasks;
037
038
039
040import java.io.Serializable;
041import java.util.ArrayList;
042import java.util.Date;
043import java.util.Iterator;
044import java.util.List;
045import java.util.concurrent.TimeUnit;
046
047import com.unboundid.util.Debug;
048import com.unboundid.util.Mutable;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051import com.unboundid.util.args.ArgumentException;
052import com.unboundid.util.args.DurationArgument;
053
054
055
056/**
057 * This class defines a set of properties that may be used when creating a
058 * {@link CollectSupportDataTask}.
059 * <BR>
060 * <BLOCKQUOTE>
061 *   <B>NOTE:</B>  This class, and other classes within the
062 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
063 *   supported for use against Ping Identity, UnboundID, and
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with other types of LDAP servers.
068 * </BLOCKQUOTE>
069 */
070@Mutable()
071@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
072public final class CollectSupportDataTaskProperties
073       implements Serializable
074{
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = -3920803030511838640L;
079
080
081
082  // Indicates whether to generate an administrative alert if the task completes
083  // with an error.
084  private Boolean alertOnError;
085
086  // Indicates whether to generate an administrative alert when the task starts
087  // running.
088  private Boolean alertOnStart;
089
090  // Indicates whether to generate an administrative alert if the task completes
091  // successfully.
092  private Boolean alertOnSuccess;
093
094  // Indicates whether to include binary files in the support data archive.
095  private Boolean includeBinaryFiles;
096
097  // Indicates whether to include expensive data in the support data archive.
098  private Boolean includeExpensiveData;
099
100  // Indicates whether to include third-party extension source code in the
101  // support data archive.
102  private Boolean includeExtensionSource;
103
104  // Indicates whether to include a replication state dump in the support data
105  // archive.
106  private Boolean includeReplicationStateDump;
107
108  // Indicates whether to capture information sequentially rather than in
109  // parallel.
110  private Boolean useSequentialMode;
111
112  // The security level to use for data included in the support data archive.
113  private CollectSupportDataSecurityLevel securityLevel;
114
115  // The time at which the task should start running.
116  private Date scheduledStartTime;
117
118  // The action to take if any of the dependencies for this task complete
119  // unsuccessfully.
120  private FailedDependencyAction failedDependencyAction;
121
122  // The number of jstacks to include in the support data archive.
123  private Integer jstackCount;
124
125  // The report count to use for sampled metrics.
126  private Integer reportCount;
127
128  // The report interval in seconds to use for sampled metrics.
129  private Integer reportIntervalSeconds;
130
131  // The minimum number of existing support data archives that should be
132  // retained.
133  private Integer retainPreviousSupportDataArchiveCount;
134
135  // The dependency IDs of any tasks on which the collect support data task
136  // should depend.
137  private final List<String> dependencyIDs;
138
139  // The addresses to email whenever the task completes, regardless of success
140  // or failure.
141  private final List<String> notifyOnCompletion;
142
143  // The addresses to email if the task completes with an error.
144  private final List<String> notifyOnError;
145
146  // The addresses to email when the task starts.
147  private final List<String> notifyOnStart;
148
149  // The addresses to email if the task completes successfully.
150  private final List<String> notifyOnSuccess;
151
152  // A comment to include in the support data archive.
153  private String comment;
154
155  // The path to the encryption passphrase file.
156  private String encryptionPassphraseFile;
157
158  // A string representation of the log duration to capture.
159  private String logDuration;
160
161  // The path to which the support data archive should be written.
162  private String outputPath;
163
164  // The minimum age for existing support data archives that should be retained.
165  private String retainPreviousSupportDataArchiveAge;
166
167  // The task ID to use for the collect support data task.
168  private String taskID;
169
170
171
172  /**
173   * Creates a new set of collect support data task properties without any of
174   * the properties set (so that the server will use default values for all of
175   * them).
176   */
177  public CollectSupportDataTaskProperties()
178  {
179    alertOnError = null;
180    alertOnStart = null;
181    alertOnSuccess = null;
182    includeBinaryFiles = null;
183    includeExpensiveData = null;
184    includeExtensionSource = null;
185    includeReplicationStateDump = null;
186    useSequentialMode = null;
187    securityLevel = null;
188    scheduledStartTime = null;
189    failedDependencyAction = null;
190    jstackCount = null;
191    reportCount = null;
192    reportIntervalSeconds = null;
193    retainPreviousSupportDataArchiveCount = null;
194    dependencyIDs = new ArrayList<>(5);
195    notifyOnCompletion = new ArrayList<>(5);
196    notifyOnError = new ArrayList<>(5);
197    notifyOnStart = new ArrayList<>(5);
198    notifyOnSuccess = new ArrayList<>(5);
199    comment = null;
200    encryptionPassphraseFile = null;
201    logDuration = null;
202    outputPath = null;
203    retainPreviousSupportDataArchiveAge = null;
204    taskID = null;
205  }
206
207
208
209  /**
210   * Creates a new set of collect support data task properties as a copy of the
211   * provided properties.
212   *
213   * @param  properties  The collect support data task properties to duplicate.
214   */
215  public CollectSupportDataTaskProperties(
216              final CollectSupportDataTaskProperties properties)
217  {
218    alertOnError = properties.getAlertOnError();
219    alertOnStart = properties.getAlertOnStart();
220    alertOnSuccess = properties.getAlertOnSuccess();
221    includeBinaryFiles = properties.getIncludeBinaryFiles();
222    includeExpensiveData = properties.getIncludeExpensiveData();
223    includeExtensionSource = properties.getIncludeExtensionSource();
224    includeReplicationStateDump = properties.getIncludeReplicationStateDump();
225    useSequentialMode = properties.getUseSequentialMode();
226    securityLevel = properties.getSecurityLevel();
227    scheduledStartTime = properties.getScheduledStartTime();
228    failedDependencyAction = properties.getFailedDependencyAction();
229    jstackCount = properties.getJStackCount();
230    reportCount = properties.getReportCount();
231    reportIntervalSeconds = properties.getReportIntervalSeconds();
232    retainPreviousSupportDataArchiveCount =
233         properties.getRetainPreviousSupportDataArchiveCount();
234    dependencyIDs = new ArrayList<>(properties.getDependencyIDs());
235    notifyOnCompletion = new ArrayList<>(properties.getNotifyOnCompletion());
236    notifyOnError = new ArrayList<>(properties.getNotifyOnError());
237    notifyOnStart = new ArrayList<>(properties.getNotifyOnStart());
238    notifyOnSuccess = new ArrayList<>(properties.getNotifyOnSuccess());
239    comment = properties.getComment();
240    encryptionPassphraseFile = properties.getEncryptionPassphraseFile();
241    logDuration = properties.getLogDuration();
242    outputPath = properties.getOutputPath();
243    retainPreviousSupportDataArchiveAge =
244         properties.getRetainPreviousSupportDataArchiveAge();
245    taskID = properties.getTaskID();
246  }
247
248
249
250  /**
251   * Creates a new set of collect support data task properties set from the
252   * provided task instance.
253   *
254   * @param  task  The collect support data task instance from which the
255   *               properties should be set.
256   */
257  public CollectSupportDataTaskProperties(final CollectSupportDataTask task)
258  {
259    alertOnError = task.getAlertOnError();
260    alertOnStart = task.getAlertOnStart();
261    alertOnSuccess = task.getAlertOnSuccess();
262    includeBinaryFiles = task.getIncludeBinaryFiles();
263    includeExpensiveData = task.getIncludeExpensiveData();
264    includeExtensionSource = task.getIncludeExtensionSource();
265    includeReplicationStateDump = task.getIncludeReplicationStateDump();
266    useSequentialMode = task.getUseSequentialMode();
267    securityLevel = task.getSecurityLevel();
268    scheduledStartTime = task.getScheduledStartTime();
269    failedDependencyAction = task.getFailedDependencyAction();
270    jstackCount = task.getJStackCount();
271    reportCount = task.getReportCount();
272    reportIntervalSeconds = task.getReportIntervalSeconds();
273    retainPreviousSupportDataArchiveCount =
274         task.getRetainPreviousSupportDataArchiveCount();
275    dependencyIDs = new ArrayList<>(task.getDependencyIDs());
276    notifyOnCompletion = new ArrayList<>(task.getNotifyOnCompletionAddresses());
277    notifyOnError = new ArrayList<>(task.getNotifyOnErrorAddresses());
278    notifyOnStart = new ArrayList<>(task.getNotifyOnStartAddresses());
279    notifyOnSuccess = new ArrayList<>(task.getNotifyOnSuccessAddresses());
280    comment = task.getComment();
281    encryptionPassphraseFile = task.getEncryptionPassphraseFile();
282    logDuration = task.getLogDuration();
283    outputPath = task.getOutputPath();
284    retainPreviousSupportDataArchiveAge =
285         task.getRetainPreviousSupportDataArchiveAge();
286    taskID = task.getTaskID();
287  }
288
289
290
291  /**
292   * Retrieves the path on the server filesystem to which the support data
293   * archive should be written.
294   *
295   * @return  The path on the server filesystem to which the support data
296   *          archive should be written, or {@code null} if no value has been
297   *          specified for the property.
298   */
299  public String getOutputPath()
300  {
301    return outputPath;
302  }
303
304
305
306  /**
307   * Specifies the path on the server filesystem to which the support data '
308   * archive should be written.  If this is provided, then the value may be
309   * one of the following:
310   * <UL>
311   *   <LI>If the path refers to a file that exists, then the file will be
312   *       overwritten with the new support data archive.</LI>
313   *   <LI>If the path refers to a directory that exists, then the support data
314   *       archive will be written into that directory with a name generated
315   *       by the server.</LI>
316   *   <LI>If the path refers to a file that does not exist, then its parent
317   *       directory must exist, and the support data archive will be written
318   *       with the specified path and name.</LI>
319   * </UL>
320   *
321   * @param  outputPath  The path on the server filesystem to which the support
322   *                     data archive should be written.  It may be {@code null}
323   *                     if the server should choose the path and name for the
324   *                     output file.
325   */
326  public void setOutputPath(final String outputPath)
327  {
328    this.outputPath = outputPath;
329  }
330
331
332
333  /**
334   * Retrieves the path on the server filesystem to a file that contains the
335   * passphrase to use to encrypt the support data archive.
336   *
337   * @return  The path on the server filesystem to a file that contains the
338   *          passphrase to use to encrypt the support data archive, or
339   *          {@code null} if no value has been specified for the property, and
340   *          the support data archive should not be encrypted.
341   */
342  public String getEncryptionPassphraseFile()
343  {
344    return encryptionPassphraseFile;
345  }
346
347
348
349  /**
350   * Specifies the path on the server filesystem to a file that contains the
351   * passphrase to use to encrypt the support data archive.  If this is
352   * provided, then this must refer to a file that exists and that contains
353   * exactly one line whose entire content is the desired encryption passphrase.
354   *
355   * @param  encryptionPassphraseFile  The path on the server filesystem to a
356   *                                   file that contains the passphrase to use
357   *                                   to encrypt the support data archive.  It
358   *                                   may be {@code null} if the support data
359   *                                   archive should not be encrypted.
360   */
361  public void setEncryptionPassphraseFile(final String encryptionPassphraseFile)
362  {
363    this.encryptionPassphraseFile = encryptionPassphraseFile;
364  }
365
366
367
368  /**
369   * Retrieves the value of a flag that indicates whether the support data
370   * archive may include data that is potentially expensive to collect and
371   * could affect the performance or responsiveness of the server.
372   *
373   * @return  The value of a flag that indicates whether the support data
374   *          archive may include data that is potentially expensive to collect,
375   *          or {@code null} if the property should not be specified when the
376   *          task is created (in which case the server will use a default
377   *          behavior of excluding expensive data).
378   */
379  public Boolean getIncludeExpensiveData()
380  {
381    return includeExpensiveData;
382  }
383
384
385
386  /**
387   * Specifies the value of a flag that indicates whether the support data
388   * archive may include data that is potentially expensive to collect and could
389   * affect the performance or responsiveness of the server.
390   *
391   * @param  includeExpensiveData  The value of a flag that indicates whether
392   *                               the support data archive may include data
393   *                               that is potentially expensive to collect.  It
394   *                               may be {@code null} if the flag should not be
395   *                               specified when the task is created (in which
396   *                               case the server will use a default behavior
397   *                               of excluding expensive data).
398   */
399  public void setIncludeExpensiveData(final Boolean includeExpensiveData)
400  {
401    this.includeExpensiveData = includeExpensiveData;
402  }
403
404
405
406  /**
407   * Retrieves the value of a flag that indicates whether the support data
408   * archive may include a replication state dump, which may be several
409   * megabytes in size.
410   *
411   * @return  The value of a flag that indicates whether the support data
412   *          archive may include a replication state dump, or {@code null} if
413   *          the property should not be specified when the task is created (in
414   *          which case the server will use a default behavior of excluding the
415   *          state dump).
416   */
417  public Boolean getIncludeReplicationStateDump()
418  {
419    return includeReplicationStateDump;
420  }
421
422
423
424  /**
425   * Specifies the value of a flag that indicates whether the support data
426   * archive may include a replication state dump, which may be several
427   * megabytes in size.
428   *
429   * @param  includeReplicationStateDump  The value of a flag that indicates
430   *                                      whether the support data archive may
431   *                                      include a replication state dump.  It
432   *                                      may be {@code null} if the flag should
433   *                                      not be specified when the task is
434   *                                      created (in which case the server will
435   *                                      use a default behavior of excluding
436   *                                      the state dump).
437   */
438  public void setIncludeReplicationStateDump(
439                   final Boolean includeReplicationStateDump)
440  {
441    this.includeReplicationStateDump = includeReplicationStateDump;
442  }
443
444
445
446  /**
447   * Retrieves the value of a flag that indicates whether the support data
448   * archive may include binary files.
449   *
450   * @return  The value of a flag that indicates whether the support data
451   *          archive may include binary files, or {@code null} if the property
452   *          should not be specified when the task is created (in which case
453   *          the server will use a default behavior of excluding binary files).
454   */
455  public Boolean getIncludeBinaryFiles()
456  {
457    return includeBinaryFiles;
458  }
459
460
461
462  /**
463   * Specifies the value of a flag that that indicates whether the support data
464   * archive may include binary files.
465   *
466   * @param  includeBinaryFiles  The value of a flag that indicates whether the
467   *                             support data archive may include binary files.
468   *                             It may be {@code null} if the property should
469   *                             not be specified when the task is created (in
470   *                             which case the server will use a default
471   *                             behavior of excluding binary files).
472   */
473  public void setIncludeBinaryFiles(final Boolean includeBinaryFiles)
474  {
475    this.includeBinaryFiles = includeBinaryFiles;
476  }
477
478
479
480  /**
481   * Retrieves the value of a flag that indicates whether the support data
482   * archive should include source code (if available) for any third-party
483   * extensions installed in the server.
484   *
485   * @return  The value of a flag that indicates whether the support data
486   *          archive should include source code (if available) for any
487   *          third-party extensions installed in the server, or {@code null} if
488   *          the property should not be specified when the task is created (in
489   *          which case the server will use a default behavior of excluding
490   *          extension source code).
491   */
492  public Boolean getIncludeExtensionSource()
493  {
494    return includeExtensionSource;
495  }
496
497
498
499  /**
500   * Specifies the value of a flag that indicates whether the support data
501   * archive should include source code (if available) for any third-party
502   * extensions installed in the server.
503   *
504   * @param  includeExtensionSource  The value of a flag that indicates whether
505   *                                 the support data archive should include
506   *                                 source code (if available) for any
507   *                                 third-party extensions in the server.  It
508   *                                 may be {@code null} if the property should
509   *                                 not be specified when the task is
510   *                                 created (in which case the server will use
511   *                                 a default behavior of excluding extension
512   *                                 source code).
513   */
514  public void setIncludeExtensionSource(final Boolean includeExtensionSource)
515  {
516    this.includeExtensionSource = includeExtensionSource;
517  }
518
519
520
521  /**
522   * Retrieves the value of a flag that indicates whether the server should
523   * collect items for the support data archive in sequential mode rather than
524   * in parallel.  Collecting data in sequential mode may reduce the amount of
525   * memory consumed during the collection process, but it will take longer to
526   * complete.
527   *
528   * @return  The value of a flag that indicates whether the server should
529   *          collect items for the support data archive in sequential mode
530   *          rather than in parallel, or {@code null} if the property should
531   *          not be specified when the task is created (in which case the
532   *          server will default to capturing data in parallel).
533   */
534  public Boolean getUseSequentialMode()
535  {
536    return useSequentialMode;
537  }
538
539
540
541  /**
542   * Specifies the value of a flag that indicates whether the server should
543   * collect items for the support data archive in sequential mode rather than
544   * in parallel.  Collecting data in sequential mode may reduce the amount of
545   * memory consumed during the collection process, but it will take longer to
546   * complete.
547   *
548   * @param  useSequentialMode  The value of a flag that indicates whether the
549   *                            server should collect items for the support data
550   *                            archive in sequential mode rather than in
551   *                            parallel.  It may be {@code null} if the
552   *                            property should not be specified when the task
553   *                            is created (in which case the server will
554   *                            default to capturing data in parallel).
555   */
556  public void setUseSequentialMode(final Boolean useSequentialMode)
557  {
558    this.useSequentialMode = useSequentialMode;
559  }
560
561
562
563  /**
564   * Retrieves the security level that should be used to indicate which data
565   * should be obscured, redacted, or omitted from the support data archive.
566   *
567   * @return  The security level that should be used when creating the support
568   *          data archive, or {@code null} if the property should not be
569   *          specified when the task is created (in which case the server will
570   *          use a default security level).
571   */
572  public CollectSupportDataSecurityLevel getSecurityLevel()
573  {
574    return securityLevel;
575  }
576
577
578
579  /**
580   * Specifies the security level that should be used to indicate which data
581   * should be obscured, redacted, or omitted from the support data archive.
582   *
583   * @param  securityLevel  The security level that should be used when creating
584   *                        the support data archive.  It may be {@code null} if
585   *                        the property should not be specified when the task
586   *                        is created (in which case the server will use a
587   *                        default security level).
588   */
589  public void setSecurityLevel(
590                   final CollectSupportDataSecurityLevel securityLevel)
591  {
592    this.securityLevel = securityLevel;
593  }
594
595
596
597  /**
598   * Retrieves the number of intervals that should be captured from tools that
599   * use interval-based sampling (e.g., vmstat, iostat, mpstat, etc.).
600   *
601   * @return  The number of intervals that should be captured from tools that
602   *          use interval-based sampling, or {@code null} if the property
603   *          should not be specified when the task is created (in which case
604   *          the server will use a default report count).
605   */
606  public Integer getReportCount()
607  {
608    return reportCount;
609  }
610
611
612
613  /**
614   * Specifies the number of intervals that should be captured form tools that
615   * use interval-based sampling (e.g., vmstat, iostat, mpstat, etc.).
616   *
617   * @param  reportCount  The number of intervals that should be captured from
618   *                      tools that use interval-based sampling.  The value
619   *                      must not be negative, but it may be zero to indicate
620   *                      that no intervals should be captured.  It may be
621   *                      {@code null} if the property should not be specified
622   *                      when the task is created (in which case the server
623   *                      will use a default report count).
624   */
625  public void setReportCount(final Integer reportCount)
626  {
627    this.reportCount = reportCount;
628  }
629
630
631
632  /**
633   * Retrieves the interval duration in seconds that should be used for tools
634   * that use interval-based sampling (e.g., vmstat, iostat, mpstat, etc.).
635   *
636   * @return  The interval duration in seconds that should be used for tools
637   *          that use interval-based sampling, or {@code null} if the property
638   *          should not be specified when the task is created (in which case
639   *          the server will use a default report interval).
640   */
641  public Integer getReportIntervalSeconds()
642  {
643    return reportIntervalSeconds;
644  }
645
646
647
648  /**
649   * Specifies the interval duration in seconds that should be used for tools
650   * that use interval-based sampling (e.g., vmstat, iostat, mpstat, etc.).
651   *
652   * @param  reportIntervalSeconds  The interval duration in seconds that should
653   *                                be used for tools that use interval-based
654   *                                sampling.  The value must be greater than or
655   *                                equal to one.  It may be {@code null} if the
656   *                                property should not be specified when the
657   *                                task is created (in which case the server
658   *                                will use a default report count).
659   */
660  public void setReportIntervalSeconds(final Integer reportIntervalSeconds)
661  {
662    this.reportIntervalSeconds = reportIntervalSeconds;
663  }
664
665
666
667  /**
668   * Retrieves the number of times that the jstack utility should be invoked to
669   * obtain stack traces from all threads in the server.
670   *
671   * @return  The number of times that the jstack utility should be invoked to
672   *          obtain stack traces from all threads in the server, or
673   *          {@code null} if the property should not be specified when the task
674   *          is created (in which case the server will use a default count).
675   */
676  public Integer getJStackCount()
677  {
678    return jstackCount;
679  }
680
681
682
683  /**
684   * Specifies the number of times that the jstack utility should be invoked to
685   * obtain stack traces from all threads in the server.
686   *
687   * @param  jstackCount  The number of times that the jstack utility should be
688   *                      invoked to obtain stack traces from all threads in the
689   *                      server.  The value must not be negative, but it may be
690   *                      zero to indicate that the jstack utility should not be
691   *                      invoked.  It may be {@code null} if the property
692   *                      should not be specified when the task is created (in
693   *                      which case the server will use a default count).
694   */
695  public void setJStackCount(final Integer jstackCount)
696  {
697    this.jstackCount = jstackCount;
698  }
699
700
701
702  /**
703   * Retrieves a string representation of the duration (up until the time that
704   * the collect support data task is invoked) of log content that should be
705   * included in the support data archive.
706   *
707   * @return  A string representation of the duration of log content that should
708   *          be included in the support data archive, or {@code null} if the
709   *          property should not be specified when the task is created (in
710   *          which case the server will use a default behavior for selecting
711   *          the amount of log content to include).
712   */
713  public String getLogDuration()
714  {
715    return logDuration;
716  }
717
718
719
720  /**
721   * Retrieves a parsed value of the log duration in milliseconds.
722   *
723   * @return  A parsed value of the log duration in milliseconds or {@code null}
724   *          if no log duration is set.
725   *
726   * @throws  TaskException  If the log duration value cannot be parsed as a
727   *                         valid duration.
728   */
729  public Long getLogDurationMillis()
730         throws TaskException
731  {
732    if (logDuration == null)
733    {
734      return null;
735    }
736
737    try
738    {
739      return DurationArgument.parseDuration(logDuration, TimeUnit.MILLISECONDS);
740    }
741    catch (final ArgumentException e)
742    {
743      Debug.debugException(e);
744      throw new TaskException(e.getMessage(), e);
745    }
746  }
747
748
749
750  /**
751   * Specifies the string representation of the duration (up until the time that
752   * the collect support data task is invoked) of log content that should be
753   * included in the support data archive.
754   * <BR><BR>
755   * The string representation of the duration should be specified as
756   * an integer followed by a time unit, where the unit may be one of
757   * millisecond, second, minute, hour, day, or week (or one of their plurals).
758   * For example, "5 minutes" or "1 hour".
759   *
760   * @param  logDuration  The string representation of the duration of log
761   *                      content that should be included in the support data
762   *                      archive.  It may be {@code null} if the property
763   *                      should not be specified when the task is created (in
764   *                      which case the server will determine an appropriate
765   *                      amount of log content to include).
766   *
767   * @throws  TaskException  If the provided string representation cannot be
768   *                         parsed as a valid duration.
769   */
770  public void setLogDuration(final String logDuration)
771         throws TaskException
772  {
773    if (logDuration == null)
774    {
775      this.logDuration = null;
776    }
777    else
778    {
779      try
780      {
781        DurationArgument.parseDuration(logDuration, TimeUnit.MILLISECONDS);
782        this.logDuration = logDuration;
783      }
784      catch (final ArgumentException e)
785      {
786        Debug.debugException(e);
787        throw new TaskException(e.getMessage(), e);
788      }
789    }
790  }
791
792
793
794  /**
795   * Specifies the duration in milliseconds (up until the time that the collect
796   * support data task is invoked) of log content that should be included in the
797   * support data archive.
798   *
799   * @param  logDurationMillis  The duration in milliseconds of log content that
800   *                            should be included in the support data archive.
801   *                            The value must be greater than zero.  It may be
802   *                            {@code null} if the property should not be
803   *                            specified when the task is created (in which
804   *                            case the server will determine an appropriate
805   *                            amount of log content to include).
806   */
807  public void setLogDurationMillis(final Long logDurationMillis)
808  {
809    if (logDurationMillis == null)
810    {
811      logDuration = null;
812    }
813    else
814    {
815      logDuration = DurationArgument.nanosToDuration(
816           TimeUnit.MILLISECONDS.toNanos(logDurationMillis));
817    }
818  }
819
820
821
822  /**
823   * Retrieves an additional comment that should be included in the support data
824   * archive.
825   *
826   * @return  An additional comment that should be included in the support data
827   *          archive, or {@code null} if no comment should be included.
828   */
829  public String getComment()
830  {
831    return comment;
832  }
833
834
835
836  /**
837   * Specifies an additional comment that should be included in the support data
838   * archive.
839   *
840   * @param  comment  An additional comment that should be included in the
841   *                  support data archive.  It may be {@code null} if no
842   *                  additional comment should be included.
843   */
844  public void setComment(final String comment)
845  {
846    this.comment = comment;
847  }
848
849
850
851  /**
852   * Retrieves the minimum number of existing support data archives that should
853   * be retained.
854   *
855   * @return  The minimum number of existing support data archives that should
856   *          be retained, or {@code null} if there is no minimum retain count.
857   */
858  public Integer getRetainPreviousSupportDataArchiveCount()
859  {
860    return retainPreviousSupportDataArchiveCount;
861  }
862
863
864
865  /**
866   * Specifies the minimum number of existing support data archives that should
867   * be retained.
868   * <BR><BR>
869   * Note that if an output path is specified, then a retain count or retain age
870   * may only be used if that output path specifies a directory rather than a
871   * file, so that the file name will be generated by the server, and only
872   * archive files in that directory with names that conform to the
873   * server-generated pattern may be removed.
874   * <BR><BR>
875   * If neither a retain count nor a retain age is specified, then no existing
876   * support data archives will be removed.  If both are specified, then any
877   * existing archive that is outside the criteria for either will be removed.
878   *
879   * @param  retainPreviousSupportDataArchiveCount
880   *              The minimum number of existing support data archives that
881   *              should be retained.  A value of zero indicates that only the
882   *              new support data archive should be retained, and any other
883   *              preexisting archives may be removed.  It may be {@code null}
884   *              if only the age of existing archives should be considered (if
885   *              a retain age is specified), or if no existing support data
886   *              archives should be removed (if no retain age is specified).
887   */
888  public void setRetainPreviousSupportDataArchiveCount(
889                   final Integer retainPreviousSupportDataArchiveCount)
890  {
891    this.retainPreviousSupportDataArchiveCount =
892         retainPreviousSupportDataArchiveCount;
893  }
894
895
896
897  /**
898   * Retrieves the minimum age of existing support data archives that should be
899   * retained.
900   *
901   * @return  The minimum age of existing support data archives that should
902   *          be retained, or {@code null} if there is no minimum retain age.
903   */
904  public String getRetainPreviousSupportDataArchiveAge()
905  {
906    return retainPreviousSupportDataArchiveAge;
907  }
908
909
910
911  /**
912   * Retrieves a parsed value of the retain previous support data archive age in
913   * milliseconds.
914   *
915   * @return  A parsed value of the retain previous support data archive age in
916   *          milliseconds or {@code null} if no retain age is set.
917   *
918   * @throws  TaskException  If the retain age value cannot be parsed as a valid
919   *                         duration.
920   */
921  public Long getRetainPreviousSupportDataArchiveAgeMillis()
922         throws TaskException
923  {
924    if (retainPreviousSupportDataArchiveAge == null)
925    {
926      return null;
927    }
928
929    try
930    {
931      return DurationArgument.parseDuration(
932           retainPreviousSupportDataArchiveAge, TimeUnit.MILLISECONDS);
933    }
934    catch (final ArgumentException e)
935    {
936      Debug.debugException(e);
937      throw new TaskException(e.getMessage(), e);
938    }
939  }
940
941
942
943  /**
944   * Specifies the minimum age of existing support data archives that should be
945   * retained.
946   * <BR><BR>
947   * The string representation of the duration should be specified as an integer
948   * followed by a time unit, where the unit may be one of millisecond, second,
949   * minute, hour, day, or week (or one of their plurals).  For example, "5
950   * minutes" or "1 hour".
951   * <BR><BR>
952   * Note that if an output path is specified, then a retain count or retain age
953   * may only be used if that output path specifies a directory rather than a
954   * file, so that the file name will be generated by the server, and only
955   * archive files in that directory with names that conform to the
956   * server-generated pattern may be removed.
957   * <BR><BR>
958   * If neither a retain count nor a retain age is specified, then no existing
959   * support data archives will be removed.  If both are specified, then any
960   * existing archive that is outside the criteria for either will be removed.
961   *
962   * @param  retainPreviousSupportDataArchiveAge
963   *              The minimum age of existing support data archives that
964   *              should be retained.  Any existing support data archives that
965   *              are older than this may be removed.  It may be {@code null}
966   *              if only the number of existing archives should be considered
967   *              (if a retain count is specified), or if no existing support
968   *              data archives should be removed (if no retain count is
969   *              specified).
970   *
971   * @throws  TaskException  If the provided string representation cannot be
972   *                         parsed as a valid duration.
973   */
974  public void setRetainPreviousSupportDataArchiveAge(
975                   final String retainPreviousSupportDataArchiveAge)
976         throws TaskException
977  {
978    if (retainPreviousSupportDataArchiveAge == null)
979    {
980      this.retainPreviousSupportDataArchiveAge = null;
981    }
982    else
983    {
984      try
985      {
986        DurationArgument.parseDuration(retainPreviousSupportDataArchiveAge,
987             TimeUnit.MILLISECONDS);
988        this.retainPreviousSupportDataArchiveAge =
989             retainPreviousSupportDataArchiveAge;
990      }
991      catch (final ArgumentException e)
992      {
993        Debug.debugException(e);
994        throw new TaskException(e.getMessage(), e);
995      }
996    }
997  }
998
999
1000
1001  /**
1002   * Specifies the minimum age in milliseconds of existing support data
1003   * archives that should be retained.
1004   * <BR><BR>
1005   * Note that if an output path is specified, then a retain count or retain age
1006   * may only be used if that output path specifies a directory rather than a
1007   * file, so that the file name will be generated by the server, and only
1008   * archive files in that directory with names that conform to the
1009   * server-generated pattern may be removed.
1010   * <BR><BR>
1011   * If neither a retain count nor a retain age is specified, then no existing
1012   * support data archives will be removed.  If both are specified, then any
1013   * existing archive that is outside the criteria for either will be removed.
1014   *
1015   * @param  retainPreviousSupportDataArchiveAgeMillis
1016   *              The minimum age in milliseconds of existing support data
1017   *              archives that should be retained.  Any existing support data
1018   *              archives that are older than this may be removed.  It may be
1019   *              {@code null} if only the number of existing archives should be
1020   *              considered (if a retain count is specified), or if no existing
1021   *              support data archives should be removed (if no retain count is
1022   *              specified).
1023   */
1024  public void setRetainPreviousSupportDataArchiveAgeMillis(
1025                   final Long retainPreviousSupportDataArchiveAgeMillis)
1026  {
1027    if (retainPreviousSupportDataArchiveAgeMillis == null)
1028    {
1029      retainPreviousSupportDataArchiveAge = null;
1030    }
1031    else
1032    {
1033      retainPreviousSupportDataArchiveAge = DurationArgument.nanosToDuration(
1034           TimeUnit.MILLISECONDS.toNanos(
1035                retainPreviousSupportDataArchiveAgeMillis));
1036    }
1037  }
1038
1039
1040
1041  /**
1042   * Retrieves the task ID that should be used for the task.
1043   *
1044   * @return  The task ID that should be used for the task, or {@code null} if a
1045   *          random UUID should be generated for use as the task ID.
1046   */
1047  public String getTaskID()
1048  {
1049    return taskID;
1050  }
1051
1052
1053
1054  /**
1055   *Specifies the task ID that should be used for the task.
1056   *
1057   * @param  taskID  The task ID that should be used for the task.  It may be
1058   *                 {@code null} if a random UUID should be generated for use
1059   *                 as the task ID.
1060   */
1061  public void setTaskID(final String taskID)
1062  {
1063    this.taskID = taskID;
1064  }
1065
1066
1067
1068  /**
1069   * Retrieves the earliest time that the task should be eligible to start
1070   * running.
1071   *
1072   * @return  The earliest time that the task should be eligible to start
1073   *          running, or {@code null} if the task should be eligible to start
1074   *          immediately (or as soon as all of its dependencies have been
1075   *          satisfied).
1076   */
1077  public Date getScheduledStartTime()
1078  {
1079    return scheduledStartTime;
1080  }
1081
1082
1083
1084  /**
1085   * Specifies the earliest time that the task should be eligible to start
1086   * running.
1087   *
1088   * @param  scheduledStartTime  The earliest time that the task should be
1089   *                             eligible to start running.  It may be
1090   *                             {@code null} if the task should be eligible to
1091   *                             start immediately (or as soon as all of its
1092   *                             dependencies have been satisfied).
1093   */
1094  public void setScheduledStartTime(final Date scheduledStartTime)
1095  {
1096    this.scheduledStartTime = scheduledStartTime;
1097  }
1098
1099
1100
1101  /**
1102   * Retrieves the task IDs for any tasks that must complete before the new
1103   * collect support data task will be eligible to start running.
1104   *
1105   * @return  The task IDs for any tasks that must complete before the new
1106   *          collect support data task will be eligible to start running, or
1107   *          an empty list if the new task should not depend on any other
1108   *          tasks.
1109   */
1110  public List<String> getDependencyIDs()
1111  {
1112    return new ArrayList<>(dependencyIDs);
1113  }
1114
1115
1116
1117  /**
1118   * Specifies the task IDs for any tasks that must complete before the new
1119   * collect support data task will be eligible to start running.
1120   *
1121   * @param  dependencyIDs  The task IDs for any tasks that must complete before
1122   *                        the new collect support data task will be eligible
1123   *                        to start running.  It may be {@code null} or empty
1124   *                        if the new task should not depend on any other
1125   *                        tasks.
1126   */
1127  public void setDependencyIDs(final List<String> dependencyIDs)
1128  {
1129    this.dependencyIDs.clear();
1130    if (dependencyIDs != null)
1131    {
1132      this.dependencyIDs.addAll(dependencyIDs);
1133    }
1134  }
1135
1136
1137
1138  /**
1139   * Retrieves the action that the server should take if any of the tasks on
1140   * which the new task depends did not complete successfully.
1141   *
1142   * @return  The action that the server should take if any of the tasks on
1143   *          which the new task depends did not complete successfully, or
1144   *          {@code null} if the property should not be specified when creating
1145   *          the task (and the server should choose an appropriate failed
1146   *          dependency action).
1147   */
1148  public FailedDependencyAction getFailedDependencyAction()
1149  {
1150    return failedDependencyAction;
1151  }
1152
1153
1154
1155  /**
1156   * Specifies the action that the server should take if any of the tasks on
1157   * which the new task depends did not complete successfully.
1158   *
1159   * @param  failedDependencyAction  The action that the server should take if
1160   *                                 any of the tasks on which the new task
1161   *                                 depends did not complete successfully.  It
1162   *                                 may be {@code null} if the property should
1163   *                                 not be specified when creating the task
1164   *                                 (and the server should choose an
1165   *                                 appropriate failed dependency action).
1166   */
1167  public void setFailedDependencyAction(
1168                   final FailedDependencyAction failedDependencyAction)
1169  {
1170    this.failedDependencyAction = failedDependencyAction;
1171  }
1172
1173
1174
1175  /**
1176   * Retrieves the addresses to email whenever the task starts running.
1177   *
1178   * @return  The addresses to email whenever the task starts running, or an
1179   *          empty list if no email notification should be sent when starting
1180   *          the task.
1181   */
1182  public List<String> getNotifyOnStart()
1183  {
1184    return new ArrayList<>(notifyOnStart);
1185  }
1186
1187
1188
1189  /**
1190   * Specifies the addresses to email whenever the task starts running.
1191   *
1192   * @param  notifyOnStart  The addresses to email whenever the task starts
1193   *                        running.  It amy be {@code null} or empty if no
1194   *                        email notification should be sent when starting the
1195   *                        task.
1196   */
1197  public void setNotifyOnStart(final List<String> notifyOnStart)
1198  {
1199    this.notifyOnStart.clear();
1200    if (notifyOnStart != null)
1201    {
1202      this.notifyOnStart.addAll(notifyOnStart);
1203    }
1204  }
1205
1206
1207
1208  /**
1209   * Retrieves the addresses to email whenever the task completes, regardless of
1210   * its success or failure.
1211   *
1212   * @return  The addresses to email whenever the task completes, or an
1213   *          empty list if no email notification should be sent when the task
1214   *          completes.
1215   */
1216  public List<String> getNotifyOnCompletion()
1217  {
1218    return new ArrayList<>(notifyOnCompletion);
1219  }
1220
1221
1222
1223  /**
1224   * Specifies the addresses to email whenever the task completes, regardless of
1225   * its success or failure.
1226   *
1227   * @param  notifyOnCompletion  The addresses to email whenever the task
1228   *                             completes.  It amy be {@code null} or empty if
1229   *                             no email notification should be sent when the
1230   *                             task completes.
1231   */
1232  public void setNotifyOnCompletion(final List<String> notifyOnCompletion)
1233  {
1234    this.notifyOnCompletion.clear();
1235    if (notifyOnCompletion != null)
1236    {
1237      this.notifyOnCompletion.addAll(notifyOnCompletion);
1238    }
1239  }
1240
1241
1242
1243  /**
1244   * Retrieves the addresses to email if the task completes successfully.
1245   *
1246   * @return  The addresses to email if the task completes successfully, or an
1247   *          empty list if no email notification should be sent on successful
1248   *          completion.
1249   */
1250  public List<String> getNotifyOnSuccess()
1251  {
1252    return new ArrayList<>(notifyOnSuccess);
1253  }
1254
1255
1256
1257  /**
1258   * Specifies the addresses to email if the task completes successfully.
1259   *
1260   * @param  notifyOnSuccess  The addresses to email if the task completes
1261   *                          successfully.  It amy be {@code null} or empty if
1262   *                          no email notification should be sent on
1263   *                          successful completion.
1264   */
1265  public void setNotifyOnSuccess(final List<String> notifyOnSuccess)
1266  {
1267    this.notifyOnSuccess.clear();
1268    if (notifyOnSuccess != null)
1269    {
1270      this.notifyOnSuccess.addAll(notifyOnSuccess);
1271    }
1272  }
1273
1274
1275
1276  /**
1277   * Retrieves the addresses to email if the task does not complete
1278   * successfully.
1279   *
1280   * @return  The addresses to email if the task does not complete successfully,
1281   *          or an empty list if no email notification should be sent on an
1282   *          unsuccessful completion.
1283   */
1284  public List<String> getNotifyOnError()
1285  {
1286    return new ArrayList<>(notifyOnError);
1287  }
1288
1289
1290
1291  /**
1292   * Specifies the addresses to email if the task does not complete
1293   * successfully.
1294   *
1295   * @param  notifyOnError  The addresses to email if the task does not complete
1296   *                        successfully.  It amy be {@code null} or empty if
1297   *                        no email notification should be sent on an
1298   *                        unsuccessful completion.
1299   */
1300  public void setNotifyOnError(final List<String> notifyOnError)
1301  {
1302    this.notifyOnError.clear();
1303    if (notifyOnError != null)
1304    {
1305      this.notifyOnError.addAll(notifyOnError);
1306    }
1307  }
1308
1309
1310
1311  /**
1312   * Retrieves the flag that indicates whether the server should send an
1313   * administrative alert notification when the task starts running.
1314   *
1315   * @return  The flag that indicates whether the server should send an
1316   *          administrative alert notification when the task starts running,
1317   *          or {@code null} if the property should not be specified when the
1318   *          task is created (and the server will default to not sending any
1319   *          alert).
1320   */
1321  public Boolean getAlertOnStart()
1322  {
1323    return alertOnStart;
1324  }
1325
1326
1327
1328  /**
1329   * Specifies the flag that indicates whether the server should send an
1330   * administrative alert notification when the task starts running.
1331   *
1332   * @param  alertOnStart  The flag that indicates whether the server should
1333   *                       send an administrative alert notification when the
1334   *                       task starts running,  It may be {@code null} if the
1335   *                       property should not be specified when the task is
1336   *                       created (and the server will default to not sending
1337   *                       any alert).
1338   */
1339  public void setAlertOnStart(final Boolean alertOnStart)
1340  {
1341    this.alertOnStart = alertOnStart;
1342  }
1343
1344
1345
1346  /**
1347   * Retrieves the flag that indicates whether the server should send an
1348   * administrative alert notification if the task completes successfully.
1349   *
1350   * @return  The flag that indicates whether the server should send an
1351   *          administrative alert notification if the task completes
1352   *          successfully, or {@code null} if the property should not be
1353   *          specified when the task is created (and the server will default to
1354   *          not sending any alert).
1355   */
1356  public Boolean getAlertOnSuccess()
1357  {
1358    return alertOnSuccess;
1359  }
1360
1361
1362
1363  /**
1364   * Specifies the flag that indicates whether the server should send an
1365   * administrative alert notification if the task completes successfully.
1366   *
1367   * @param  alertOnSuccess  The flag that indicates whether the server should
1368   *                         send an administrative alert notification if the
1369   *                         task completes successfully,  It may be
1370   *                         {@code null} if the property should not be
1371   *                         specified when the task is created (and the server
1372   *                         will default to not sending any alert).
1373   */
1374  public void setAlertOnSuccess(final Boolean alertOnSuccess)
1375  {
1376    this.alertOnSuccess = alertOnSuccess;
1377  }
1378
1379
1380
1381  /**
1382   * Retrieves the flag that indicates whether the server should send an
1383   * administrative alert notification if the task does not complete
1384   * successfully.
1385   *
1386   * @return  The flag that indicates whether the server should send an
1387   *          administrative alert notification if the task does not complete
1388   *          successfully, or {@code null} if the property should not be
1389   *          specified when the task is created (and the server will default to
1390   *          not sending any alert).
1391   */
1392  public Boolean getAlertOnError()
1393  {
1394    return alertOnError;
1395  }
1396
1397
1398
1399  /**
1400   * Specifies the flag that indicates whether the server should send an
1401   * administrative alert notification if the task does not complete
1402   * successfully.
1403   *
1404   * @param  alertOnError  The flag that indicates whether the server should
1405   *                       send an administrative alert notification if the task
1406   *                       does not complete successfully,  It may be
1407   *                       {@code null} if the property should not be specified
1408   *                       when the task is created (and the server will default
1409   *                       to not sending any alert).
1410   */
1411  public void setAlertOnError(final Boolean alertOnError)
1412  {
1413    this.alertOnError = alertOnError;
1414  }
1415
1416
1417
1418  /**
1419   * Retrieves a string representation of this collect support data task
1420   * properties object.
1421   *
1422   * @return  A string representation of this collect support data task
1423   *          properties object.
1424   */
1425  @Override()
1426  public String toString()
1427  {
1428    final StringBuilder buffer = new StringBuilder();
1429    toString(buffer);
1430    return buffer.toString();
1431  }
1432
1433
1434
1435  /**
1436   * Appends a string representation of this collect support data task
1437   * properties object to the provided buffer.
1438   *
1439   * @param  buffer  The buffer to which the string representation will be
1440   *                 appended.  It must not be {@code null}.
1441   */
1442  public void toString(final StringBuilder buffer)
1443  {
1444    buffer.append("CollectSupportDataArchiveProperties(");
1445
1446    appendNameValuePair(buffer, "taskID", taskID);
1447    appendNameValuePair(buffer, "outputPath", outputPath);
1448    appendNameValuePair(buffer, "encryptionPassphraseFile",
1449         encryptionPassphraseFile);
1450    appendNameValuePair(buffer, "includeExpensiveData", includeExpensiveData);
1451    appendNameValuePair(buffer, "includeReplicationStateDump",
1452         includeReplicationStateDump);
1453    appendNameValuePair(buffer, "includeBinaryFiles", includeBinaryFiles);
1454    appendNameValuePair(buffer, "includeExtensionSource",
1455         includeExtensionSource);
1456    appendNameValuePair(buffer, "securityLevel", securityLevel);
1457    appendNameValuePair(buffer, "useSequentialMode", useSequentialMode);
1458    appendNameValuePair(buffer, "reportCount", reportCount);
1459    appendNameValuePair(buffer, "reportIntervalSeconds", reportIntervalSeconds);
1460    appendNameValuePair(buffer, "jstackCount", jstackCount);
1461    appendNameValuePair(buffer, "logDuration", logDuration);
1462    appendNameValuePair(buffer, "comment", comment);
1463    appendNameValuePair(buffer, "retainPreviousSupportDataArchiveCount",
1464         retainPreviousSupportDataArchiveCount);
1465    appendNameValuePair(buffer, "retainPreviousSupportDataArchiveAge",
1466         retainPreviousSupportDataArchiveAge);
1467    appendNameValuePair(buffer, "scheduledStartTime", scheduledStartTime);
1468    appendNameValuePair(buffer, "dependencyIDs", dependencyIDs);
1469    appendNameValuePair(buffer, "failedDependencyAction",
1470         failedDependencyAction);
1471    appendNameValuePair(buffer, "notifyOnStart", notifyOnStart);
1472    appendNameValuePair(buffer, "notifyOnCompletion", notifyOnCompletion);
1473    appendNameValuePair(buffer, "notifyOnSuccess", notifyOnSuccess);
1474    appendNameValuePair(buffer, "notifyOnError", notifyOnError);
1475    appendNameValuePair(buffer, "alertOnStart", alertOnStart);
1476    appendNameValuePair(buffer, "alertOnSuccess", alertOnSuccess);
1477    appendNameValuePair(buffer, "alertOnError", alertOnError);
1478
1479    buffer.append(')');
1480  }
1481
1482
1483
1484  /**
1485   * Appends a name-value pair to the provided buffer, if the value is
1486   * non-{@code null}.
1487   *
1488   * @param  buffer  The buffer to which the name-value pair should be appended.
1489   * @param  name    The name to be used.  It must not be {@code null}.
1490   * @param  value   The value to be used.  It may be {@code null} if there is
1491   *                 no value for the property.
1492   */
1493  private static void appendNameValuePair(final StringBuilder buffer,
1494                                          final String name, final Object value)
1495  {
1496    if (value == null)
1497    {
1498      return;
1499    }
1500
1501    if ((buffer.length() > 0) &&
1502         (buffer.charAt(buffer.length() - 1) != '('))
1503    {
1504      buffer.append(", ");
1505    }
1506
1507    buffer.append(name);
1508    buffer.append("='");
1509    buffer.append(value);
1510    buffer.append('\'');
1511  }
1512
1513
1514
1515  /**
1516   * Appends a name-value pair to the provided buffer, if the value is
1517   * non-{@code null}.
1518   *
1519   * @param  buffer   The buffer to which the name-value pair should be
1520   *                  appended.
1521   * @param  name     The name to be used.  It must not be {@code null}.
1522   * @param  values   The list of values to be used.  It may be {@code null} or
1523   *                  empty if there are no values for the property.
1524   */
1525  private static void appendNameValuePair(final StringBuilder buffer,
1526                                          final String name,
1527                                          final List<String> values)
1528  {
1529    if ((values == null) || values.isEmpty())
1530    {
1531      return;
1532    }
1533
1534    if ((buffer.length() > 0) &&
1535         (buffer.charAt(buffer.length() - 1) != '('))
1536    {
1537      buffer.append(", ");
1538    }
1539
1540    buffer.append(name);
1541    buffer.append("={ ");
1542
1543    final Iterator<String> iterator = values.iterator();
1544    while (iterator.hasNext())
1545    {
1546      buffer.append('\'');
1547      buffer.append(iterator.next());
1548      buffer.append('\'');
1549
1550      if (iterator.hasNext())
1551      {
1552        buffer.append(", ");
1553      }
1554    }
1555
1556    buffer.append('}');
1557  }
1558}