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}