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.extensions;
037
038
039
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.Collections;
043import java.util.Iterator;
044import java.util.List;
045import java.util.TreeSet;
046
047import com.unboundid.asn1.ASN1Element;
048import com.unboundid.asn1.ASN1OctetString;
049import com.unboundid.asn1.ASN1Sequence;
050import com.unboundid.ldap.sdk.Control;
051import com.unboundid.ldap.sdk.ExtendedResult;
052import com.unboundid.ldap.sdk.LDAPException;
053import com.unboundid.ldap.sdk.ResultCode;
054import com.unboundid.util.Debug;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.Nullable;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.Validator;
061
062import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
063
064
065
066/**
067 * This class provides an implementation of an extended result that can be used
068 * to retrieve a list of all available versions of the configuration within a
069 * server.  This may include not only the currently-active configuration, but
070 * also former configurations that have been archived, and the baseline
071 * configuration for the current server version.
072 * <BR>
073 * <BLOCKQUOTE>
074 *   <B>NOTE:</B>  This class, and other classes within the
075 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
076 *   supported for use against Ping Identity, UnboundID, and
077 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
078 *   for proprietary functionality or for external specifications that are not
079 *   considered stable or mature enough to be guaranteed to work in an
080 *   interoperable way with other types of LDAP servers.
081 * </BLOCKQUOTE>
082 * <BR>
083 * The OID for this extended result is 1.3.6.1.4.1.30221.2.6.27.  If the request
084 * was processed successfully, then the response will have a value with the
085 * following encoding:
086 * <PRE>
087 *   ListConfigurationsResult ::= SEQUENCE {
088 *        activeConfigFileName        [0] OCTET STRING,
089 *        baselineConfigFileNames     [1] OCTET STRING OPTIONAL,
090 *        archivedConfigFileNames     [2] SEQUENCE OF OCTET STRING OPTIONAL,
091 *        ... }
092 * </PRE>
093 */
094@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
095public final class ListConfigurationsExtendedResult
096       extends ExtendedResult
097{
098  /**
099   * The OID (1.3.6.1.4.1.30221.2.6.27) for the list configurations extended
100   * result.
101   */
102  @NotNull public static final String LIST_CONFIGS_RESULT_OID =
103       "1.3.6.1.4.1.30221.2.6.27";
104
105
106
107  /**
108   * The BER type for the element holding the filename used for the active
109   * configuration.
110   */
111  private static final byte TYPE_ACTIVE_CONFIG_FILE_NAME = (byte) 0x80;
112
113
114
115  /**
116   * The BER type for the element holding the filename used for the baseline
117   * configuration.
118   */
119  private static final byte TYPE_BASELINE_CONFIG_FILE_NAMES = (byte) 0xA1;
120
121
122
123  /**
124   * The BER type for the element holding the filenames used for the archived
125   * configurations.
126   */
127  private static final byte TYPE_ARCHIVED_CONFIG_FILE_NAMES = (byte) 0xA2;
128
129
130
131  /**
132   * The serial version UID for this serializable class.
133   */
134  private static final long serialVersionUID = -466738484294922561L;
135
136
137
138  // The names of the archived configuration files.
139  @Nullable private final List<String> archivedFileNames;
140
141  // The name of the baseline configuration file.
142  @Nullable private final List<String> baselineFileNames;
143
144  // The name of the active configuration file.
145  @Nullable private final String activeFileName;
146
147
148
149  /**
150   * Creates a new list configurations extended result from the provided generic
151   * extended result.
152   *
153   * @param  result  The generic extended result to be decoded as a list
154   *                 configurations extended result.
155   *
156   * @throws LDAPException  If the provided extended result cannot be parsed as
157   *                         a valid list configurations extended result.
158   */
159  public ListConfigurationsExtendedResult(@NotNull final ExtendedResult result)
160       throws LDAPException
161  {
162    super(result);
163
164    final ASN1OctetString value = result.getValue();
165    if (value == null)
166    {
167      activeFileName = null;
168      baselineFileNames = Collections.emptyList();
169      archivedFileNames = Collections.emptyList();
170      return;
171    }
172
173    try
174    {
175      String activeName = null;
176      List<String> archivedNames = Collections.emptyList();
177      List<String> baselineNames = null;
178      final ASN1Element[] elements =
179           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
180      for (final ASN1Element e : elements)
181      {
182        switch (e.getType())
183        {
184          case TYPE_ACTIVE_CONFIG_FILE_NAME:
185            activeName = ASN1OctetString.decodeAsOctetString(e).stringValue();
186            break;
187          case TYPE_BASELINE_CONFIG_FILE_NAMES:
188            final ASN1Element[] baselineNameElements =
189                 ASN1Sequence.decodeAsSequence(e).elements();
190            baselineNames = new ArrayList<>(baselineNameElements.length);
191            for (final ASN1Element el : baselineNameElements)
192            {
193              baselineNames.add(
194                   ASN1OctetString.decodeAsOctetString(el).stringValue());
195            }
196            archivedNames = Collections.unmodifiableList(baselineNames);
197            break;
198          case TYPE_ARCHIVED_CONFIG_FILE_NAMES:
199            final ASN1Element[] archivedNameElements =
200                 ASN1Sequence.decodeAsSequence(e).elements();
201            archivedNames = new ArrayList<>(archivedNameElements.length);
202            for (final ASN1Element el : archivedNameElements)
203            {
204              archivedNames.add(
205                   ASN1OctetString.decodeAsOctetString(el).stringValue());
206            }
207            archivedNames = Collections.unmodifiableList(archivedNames);
208            break;
209          default:
210            throw new LDAPException(ResultCode.DECODING_ERROR,
211                 ERR_LIST_CONFIGS_RESULT_UNEXPECTED_ELEMENT_TYPE.get(
212                      StaticUtils.toHex(e.getType())));
213        }
214      }
215
216      activeFileName    = activeName;
217      archivedFileNames = archivedNames;
218      baselineFileNames = baselineNames;
219
220      if (activeFileName == null)
221      {
222        throw new LDAPException(ResultCode.DECODING_ERROR,
223             ERR_LIST_CONFIGS_RESULT_NO_ACTIVE_CONFIG.get());
224      }
225    }
226    catch (final LDAPException le)
227    {
228      Debug.debugException(le);
229      throw le;
230    }
231    catch (final Exception e)
232    {
233      Debug.debugException(e);
234      throw new LDAPException(ResultCode.DECODING_ERROR,
235           ERR_LIST_CONFIGS_RESULT_ERROR_PARSING_VALUE.get(
236                StaticUtils.getExceptionMessage(e)),
237           e);
238    }
239  }
240
241
242
243  /**
244   * Creates a new list configurations extended result with the provided
245   * information.
246   *
247   * @param  messageID          The message ID for the LDAP message that is
248   *                            associated with this LDAP result.
249   * @param  resultCode         The result code from the response.
250   * @param  diagnosticMessage  The diagnostic message from the response, if
251   *                            available.
252   * @param  matchedDN          The matched DN from the response, if available.
253   * @param  referralURLs       The set of referral URLs from the response, if
254   *                            available.
255   * @param  activeFileName     The name of the active configuration file, if
256   *                            available.
257   * @param  baselineFileNames  The names of the baseline configuration files
258   *                            for current and former server versions, if
259   *                            available.  It must be {@code null} or empty if
260   *                            the active file name is {@code null}.
261   * @param  archivedFileNames  The names of the archived configuration files,
262   *                            if available.  It must be {@code null} or empty
263   *                            if the active file name is {@code null}.
264   * @param  responseControls   The set of controls from the response, if
265   *                            available.
266   */
267  public ListConfigurationsExtendedResult(final int messageID,
268              @NotNull final ResultCode resultCode,
269              @Nullable final String diagnosticMessage,
270              @Nullable final String matchedDN,
271              @Nullable final String[] referralURLs,
272              @Nullable final String activeFileName,
273              @Nullable final Collection<String> baselineFileNames,
274              @Nullable final Collection<String> archivedFileNames,
275              @Nullable final Control... responseControls)
276  {
277    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
278         ((activeFileName == null) ? null : LIST_CONFIGS_RESULT_OID),
279         encodeValue(activeFileName, baselineFileNames, archivedFileNames),
280         responseControls);
281
282    this.activeFileName   = activeFileName;
283
284    if (baselineFileNames == null)
285    {
286      this.baselineFileNames = Collections.emptyList();
287    }
288    else
289    {
290      this.baselineFileNames =
291           Collections.unmodifiableList(new ArrayList<>(baselineFileNames));
292    }
293
294    if (archivedFileNames == null)
295    {
296      this.archivedFileNames = Collections.emptyList();
297    }
298    else
299    {
300      this.archivedFileNames =
301           Collections.unmodifiableList(new ArrayList<>(archivedFileNames));
302    }
303  }
304
305
306
307  /**
308   * Creates an ASN.1 octet string containing an encoded representation of the
309   * value for a list configurations extended result with the provided
310   * information.
311   *
312   * @param  activeFileName     The name of the active configuration file, if
313   *                            available.
314   * @param  baselineFileNames  The names of the baseline configuration files
315   *                            for current and former server versions, if
316   *                            available.  It must be {@code null} or empty if
317   *                            the active file name is {@code null}.
318   * @param  archivedFileNames  The names of the archived configuration files,
319   *                            if available.  It must be {@code null} or empty
320   *                            if the active file name is {@code null}.
321   *
322   * @return  An ASN.1 octet string containing an encoded representation of the
323   *          value for a list configurations extended result, or {@code null}
324   *          if a result with the provided information should not have a value.
325   */
326  @Nullable()
327  public static ASN1OctetString encodeValue(
328              @Nullable final String activeFileName,
329              @Nullable final Collection<String> baselineFileNames,
330              @Nullable final Collection<String> archivedFileNames)
331  {
332    if (activeFileName == null)
333    {
334      Validator.ensureTrue(
335           ((baselineFileNames == null) || baselineFileNames.isEmpty()),
336           "The baseline filename must be null if the active filename is null");
337      Validator.ensureTrue(
338           ((archivedFileNames == null) || archivedFileNames.isEmpty()),
339           "The archived filenames must be null or empty if the active " +
340                "filename is null");
341      return null;
342    }
343
344    final ArrayList<ASN1Element> elements = new ArrayList<>(3);
345    elements.add(
346         new ASN1OctetString(TYPE_ACTIVE_CONFIG_FILE_NAME, activeFileName));
347
348    if ((baselineFileNames != null) && (! baselineFileNames.isEmpty()))
349    {
350      final TreeSet<String> sortedBaselineNames =
351           new TreeSet<>(baselineFileNames);
352      final ArrayList<ASN1Element> baselineNameElements =
353           new ArrayList<>(sortedBaselineNames.size());
354      for (final String s : sortedBaselineNames)
355      {
356        baselineNameElements.add(new ASN1OctetString(s));
357      }
358      elements.add(new ASN1Sequence(TYPE_BASELINE_CONFIG_FILE_NAMES,
359           baselineNameElements));
360    }
361
362    if ((archivedFileNames != null) && (! archivedFileNames.isEmpty()))
363    {
364      final TreeSet<String> sortedArchivedNames =
365           new TreeSet<>(archivedFileNames);
366      final ArrayList<ASN1Element> archivedNameElements =
367           new ArrayList<>(sortedArchivedNames.size());
368      for (final String s : sortedArchivedNames)
369      {
370        archivedNameElements.add(new ASN1OctetString(s));
371      }
372      elements.add(new ASN1Sequence(TYPE_ARCHIVED_CONFIG_FILE_NAMES,
373           archivedNameElements));
374    }
375
376    return new ASN1OctetString(new ASN1Sequence(elements).encode());
377  }
378
379
380
381  /**
382   * Retrieves the name of the active configuration file the server is
383   * currently using, if available.
384   *
385   * @return  The name of the active configuration file the server is
386   *          currently using, or {@code null} this is not available.
387   */
388  @Nullable()
389  public String getActiveFileName()
390  {
391    return activeFileName;
392  }
393
394
395
396  /**
397   * Retrieves a list containing the names of the baseline configuration files
398   * (i.e., the files containing the initial "out-of-the-box" configuration for
399   * various server versions), if available.
400   *
401   * @return  A list containing the names of the baseline configuration files,
402   *          or an empty list if this is not available.
403   */
404  @Nullable()
405  public List<String> getBaselineFileNames()
406  {
407    return baselineFileNames;
408  }
409
410
411
412  /**
413   * Retrieves a list containing the names of the archived configuration files,
414   * if available.
415   *
416   * @return  A list containing the names of the archived configuration files,
417   *          or an empty list if this is not available.
418   */
419  @Nullable()
420  public List<String> getArchivedFileNames()
421  {
422    return archivedFileNames;
423  }
424
425
426
427  /**
428   * {@inheritDoc}
429   */
430  @Override()
431  @NotNull()
432  public String getExtendedResultName()
433  {
434    return INFO_EXTENDED_RESULT_NAME_LIST_CONFIGS.get();
435  }
436
437
438
439  /**
440   * {@inheritDoc}
441   */
442  @Override()
443  public void toString(@NotNull final StringBuilder buffer)
444  {
445    buffer.append("ListConfigurationsExtendedResult(resultCode=");
446    buffer.append(getResultCode());
447
448    final int messageID = getMessageID();
449    if (messageID >= 0)
450    {
451      buffer.append(", messageID=");
452      buffer.append(messageID);
453    }
454
455    if (activeFileName != null)
456    {
457      buffer.append(", activeFileName='");
458      buffer.append(activeFileName);
459      buffer.append('\'');
460    }
461
462    if (! baselineFileNames.isEmpty())
463    {
464      buffer.append(", baselineFileNames={");
465
466      final Iterator<String> iterator = baselineFileNames.iterator();
467      while (iterator.hasNext())
468      {
469        buffer.append('\'');
470        buffer.append(iterator.next());
471        buffer.append('\'');
472        if (iterator.hasNext())
473        {
474          buffer.append(',');
475        }
476      }
477
478      buffer.append('}');
479    }
480
481    if (! archivedFileNames.isEmpty())
482    {
483      buffer.append(", archivedFileNames={");
484
485      final Iterator<String> iterator = archivedFileNames.iterator();
486      while (iterator.hasNext())
487      {
488        buffer.append('\'');
489        buffer.append(iterator.next());
490        buffer.append('\'');
491        if (iterator.hasNext())
492        {
493          buffer.append(',');
494        }
495      }
496
497      buffer.append('}');
498    }
499
500    final String diagnosticMessage = getDiagnosticMessage();
501    if (diagnosticMessage != null)
502    {
503      buffer.append(", diagnosticMessage='");
504      buffer.append(diagnosticMessage);
505      buffer.append('\'');
506    }
507
508    final String matchedDN = getMatchedDN();
509    if (matchedDN != null)
510    {
511      buffer.append(", matchedDN='");
512      buffer.append(matchedDN);
513      buffer.append('\'');
514    }
515
516    final String[] referralURLs = getReferralURLs();
517    if (referralURLs.length > 0)
518    {
519      buffer.append(", referralURLs={");
520      for (int i=0; i < referralURLs.length; i++)
521      {
522        if (i > 0)
523        {
524          buffer.append(", ");
525        }
526
527        buffer.append('\'');
528        buffer.append(referralURLs[i]);
529        buffer.append('\'');
530      }
531      buffer.append('}');
532    }
533
534    final Control[] responseControls = getResponseControls();
535    if (responseControls.length > 0)
536    {
537      buffer.append(", responseControls={");
538      for (int i=0; i < responseControls.length; i++)
539      {
540        if (i > 0)
541        {
542          buffer.append(", ");
543        }
544
545        buffer.append(responseControls[i]);
546      }
547      buffer.append('}');
548    }
549
550    buffer.append(')');
551  }
552}