001/* 002 * Copyright 2020-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020-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) 2020-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.tools; 037 038 039 040import java.io.OutputStream; 041import java.util.Arrays; 042import java.util.Collection; 043import java.util.LinkedHashMap; 044import java.util.List; 045import java.util.Map; 046import java.util.TreeMap; 047 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.ldap.sdk.Version; 050import com.unboundid.util.ColumnFormatter; 051import com.unboundid.util.CommandLineTool; 052import com.unboundid.util.FormattableColumn; 053import com.unboundid.util.HorizontalAlignment; 054import com.unboundid.util.NotNull; 055import com.unboundid.util.Nullable; 056import com.unboundid.util.OutputFormat; 057import com.unboundid.util.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.args.ArgumentException; 061import com.unboundid.util.args.ArgumentParser; 062import com.unboundid.util.args.BooleanArgument; 063import com.unboundid.util.args.IntegerArgument; 064import com.unboundid.util.args.StringArgument; 065import com.unboundid.util.json.JSONField; 066import com.unboundid.util.json.JSONObject; 067 068import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 069 070 071 072/** 073 * This class provides a command-line tool that can be used to list LDAP result 074 * code names and their numeric values, or to search for result codes that match 075 * a given name or value. 076 * <BR> 077 * <BLOCKQUOTE> 078 * <B>NOTE:</B> This class, and other classes within the 079 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 080 * supported for use against Ping Identity, UnboundID, and 081 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 082 * for proprietary functionality or for external specifications that are not 083 * considered stable or mature enough to be guaranteed to work in an 084 * interoperable way with other types of LDAP servers. 085 * </BLOCKQUOTE> 086 */ 087@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 088public final class LDAPResultCode 089 extends CommandLineTool 090{ 091 /** 092 * The column at which to wrap long lines. 093 */ 094 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 2; 095 096 097 098 /** 099 * The name of the JSON field that will hold the integer value for the result 100 * code. 101 */ 102 @NotNull private static final String JSON_FIELD_INT_VALUE = "int-value"; 103 104 105 106 /** 107 * The name of the JSON field that will hold the name for the result code. 108 */ 109 @NotNull private static final String JSON_FIELD_NAME = "name"; 110 111 112 113 /** 114 * The output format value that indicates that output should be generated as 115 * comma-separated values. 116 */ 117 @NotNull private static final String OUTPUT_FORMAT_CSV = "csv"; 118 119 120 121 /** 122 * The output format value that indicates that output should be generated as 123 * JSON objects. 124 */ 125 @NotNull private static final String OUTPUT_FORMAT_JSON = "json"; 126 127 128 129 /** 130 * The output format value that indicates that output should be generated as 131 * tab-delimited text. 132 */ 133 @NotNull private static final String OUTPUT_FORMAT_TAB_DELIMITED = 134 "tab-delimited"; 135 136 137 138 /** 139 * The output format value that indicates that output should be generated as 140 * a table. 141 */ 142 @NotNull private static final String OUTPUT_FORMAT_TABLE = "table"; 143 144 145 146 // The argument used to indicate that the tool should list result codes in 147 // alphabetic order rather than numeric order. 148 @Nullable private BooleanArgument alphabeticOrderArg; 149 150 // The argument used to indicate that the tool should list all defined result 151 // codes. 152 @Nullable private BooleanArgument listArg; 153 154 // The argument used to indicate that the tool should generate tab-delimited 155 // text output rather than as a table. 156 @Nullable private BooleanArgument scriptFriendlyArg; 157 158 // The argument used to indicate that the tool should search for the result 159 // code with the specified integer value. 160 @Nullable private IntegerArgument intValueArg; 161 162 // The argument used to specify the output format for the tool. 163 @Nullable private StringArgument outputFormatArg; 164 165 // The argument used to indicate that the tool should search for result codes 166 // that have the specified string in their name 167 @Nullable private StringArgument searchArg; 168 169 170 171 /** 172 * Runs this tool with the provided set of command-line arguments. 173 * 174 * @param args The command-line arguments provided to this program. It may 175 * be empty, but must not be {@code null}. 176 */ 177 public static void main(@NotNull final String... args) 178 { 179 final ResultCode resultCode = main(System.out, System.err, args); 180 if (resultCode != ResultCode.SUCCESS) 181 { 182 System.exit(resultCode.intValue()); 183 } 184 } 185 186 187 188 /** 189 * Runs this tool with the provided set of command-line arguments. 190 * 191 * @param out The output stream to use for standard output. It may be 192 * {@code null} if standard output should be suppressed. 193 * @param err The output stream to use for standard error. It may be 194 * {@code null} if standard error should be suppressed. 195 * @param args The command-line arguments provided to this program. It may 196 * be empty, but must not be {@code null}. 197 * 198 * @return A result code that indicates the result of tool processing. 199 */ 200 @NotNull() 201 public static ResultCode main(@Nullable final OutputStream out, 202 @Nullable final OutputStream err, 203 @NotNull final String... args) 204 { 205 final LDAPResultCode tool = new LDAPResultCode(out, err); 206 return tool.runTool(args); 207 } 208 209 210 211 /** 212 * Creates a new instance of this tool with the provided output and error 213 * streams. 214 * 215 * @param out The output stream to use for standard output. It may be 216 * {@code null} if standard output should be suppressed. 217 * @param err The output stream to use for standard error. It may be 218 * {@code null} if standard error should be suppressed. 219 */ 220 public LDAPResultCode(@Nullable final OutputStream out, 221 @Nullable final OutputStream err) 222 { 223 super(out, err); 224 225 alphabeticOrderArg = null; 226 listArg = null; 227 scriptFriendlyArg = null; 228 intValueArg = null; 229 outputFormatArg = null; 230 searchArg = null; 231 } 232 233 234 235 /** 236 * {@inheritDoc} 237 */ 238 @Override() 239 @NotNull() 240 public String getToolName() 241 { 242 return "ldap-result-code"; 243 } 244 245 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override() 251 @NotNull() 252 public String getToolDescription() 253 { 254 return INFO_LDAP_RC_TOOL_DESC_1.get(); 255 } 256 257 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override() 263 @NotNull() 264 public List<String> getAdditionalDescriptionParagraphs() 265 { 266 return Arrays.asList( 267 INFO_LDAP_RC_TOOL_DESC_2.get(), 268 INFO_LDAP_RC_TOOL_DESC_3.get()); 269 } 270 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override() 277 @NotNull() 278 public String getToolVersion() 279 { 280 return Version.NUMERIC_VERSION_STRING; 281 } 282 283 284 285 /** 286 * {@inheritDoc} 287 */ 288 @Override() 289 public boolean supportsInteractiveMode() 290 { 291 return true; 292 } 293 294 295 296 /** 297 * {@inheritDoc} 298 */ 299 @Override() 300 public boolean defaultsToInteractiveMode() 301 { 302 return false; 303 } 304 305 306 307 /** 308 * {@inheritDoc} 309 */ 310 @Override() 311 public boolean supportsPropertiesFile() 312 { 313 return true; 314 } 315 316 317 318 /** 319 * {@inheritDoc} 320 */ 321 @Override() 322 protected boolean supportsOutputFile() 323 { 324 return true; 325 } 326 327 328 329 /** 330 * {@inheritDoc} 331 */ 332 @Override() 333 protected boolean supportsDebugLogging() 334 { 335 return true; 336 } 337 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override() 344 protected boolean logToolInvocationByDefault() 345 { 346 return false; 347 } 348 349 350 351 /** 352 * {@inheritDoc} 353 */ 354 @Override() 355 public void addToolArguments(@NotNull final ArgumentParser parser) 356 throws ArgumentException 357 { 358 listArg = new BooleanArgument('l', "list", 1, 359 INFO_LDAP_RC_ARG_DESC_LIST.get()); 360 parser.addArgument(listArg); 361 362 intValueArg = new IntegerArgument('i', "int-value", false, 1, 363 INFO_LDAP_RC_ARG_PLACEHOLDER_INT_VALUE.get(), 364 INFO_LDAP_RC_ARG_DESC_INT_VALUE.get()); 365 intValueArg.addLongIdentifier("intValue", true); 366 parser.addArgument(intValueArg); 367 368 searchArg = new StringArgument('S', "search", false, 1, 369 INFO_LDAP_RC_ARG_PLACEHOLDER_SEARCH_STRING.get(), 370 INFO_LDAP_RC_ARG_DESC_SEARCH.get()); 371 parser.addArgument(searchArg); 372 373 alphabeticOrderArg = new BooleanArgument('a', "alphabetic-order", 1, 374 INFO_LDAP_RC_ARG_DESC_ALPHABETIC.get()); 375 alphabeticOrderArg.addLongIdentifier("alphabeticOrder", true); 376 alphabeticOrderArg.addLongIdentifier("alphabetical-order", true); 377 alphabeticOrderArg.addLongIdentifier("alphabeticalOrder", true); 378 alphabeticOrderArg.addLongIdentifier("alphabetic", true); 379 alphabeticOrderArg.addLongIdentifier("alphabetical", true); 380 parser.addArgument(alphabeticOrderArg); 381 382 outputFormatArg = new StringArgument(null, "output-format", false, 1, 383 INFO_LDAP_RC_ARG_PLACEHOLDER_OUTPUT_FORMAT.get(), 384 INFO_LDAP_RC_ARG_DESC_OUTPUT_FORMAT.get(), 385 StaticUtils.setOf( 386 OUTPUT_FORMAT_CSV, 387 OUTPUT_FORMAT_JSON, 388 OUTPUT_FORMAT_TAB_DELIMITED, 389 OUTPUT_FORMAT_TABLE)); 390 outputFormatArg.addLongIdentifier("outputFormat", true); 391 outputFormatArg.addLongIdentifier("format", true); 392 parser.addArgument(outputFormatArg); 393 394 scriptFriendlyArg = new BooleanArgument(null, "script-friendly", 1, 395 INFO_LDAP_RC_ARG_DESC_SCRIPT_FRIENDLY.get()); 396 scriptFriendlyArg.addLongIdentifier("scriptFriendly", true); 397 scriptFriendlyArg.setHidden(true); 398 parser.addArgument(scriptFriendlyArg); 399 400 parser.addExclusiveArgumentSet(listArg, intValueArg, searchArg); 401 parser.addExclusiveArgumentSet(outputFormatArg, scriptFriendlyArg); 402 } 403 404 405 406 /** 407 * {@inheritDoc} 408 */ 409 @Override() 410 @NotNull() 411 public ResultCode doToolProcessing() 412 { 413 // Get all result codes that should be included in the output. 414 final Map<Integer,ResultCode> resultCodesByIntValue = new TreeMap<>(); 415 final Map<String,ResultCode> resultCodesByName = new TreeMap<>(); 416 if ((intValueArg != null) && intValueArg.isPresent()) 417 { 418 final int intValue = intValueArg.getValue(); 419 final ResultCode rc = ResultCode.valueOf(intValue, null, false); 420 if (rc != null) 421 { 422 resultCodesByIntValue.put(intValue, rc); 423 resultCodesByName.put(StaticUtils.toLowerCase(rc.getName()), rc); 424 } 425 } 426 else 427 { 428 final String searchString; 429 if ((searchArg != null) && searchArg.isPresent()) 430 { 431 searchString = StaticUtils.toLowerCase(searchArg.getValue()); 432 } 433 else 434 { 435 searchString = null; 436 } 437 438 for (final ResultCode rc : ResultCode.values()) 439 { 440 final String name = rc.getName(); 441 final String lowerName = StaticUtils.toLowerCase(name); 442 if (searchString != null) 443 { 444 if (! lowerName.contains(searchString)) 445 { 446 continue; 447 } 448 } 449 450 resultCodesByIntValue.put(rc.intValue(), rc); 451 resultCodesByName.put(lowerName, rc); 452 } 453 } 454 455 456 // If there weren't any matching result codes, then inform the user and 457 // exit with an error. 458 if (resultCodesByIntValue.isEmpty()) 459 { 460 wrapErr(0, WRAP_COLUMN, ERR_LDAP_RC_NO_RESULTS.get()); 461 return ResultCode.NO_RESULTS_RETURNED; 462 } 463 464 465 // Iterate through the matching result codes and figure out how many 466 // characters are in the longest name and 467 final String nameLabel = INFO_LDAP_RC_NAME_LABEL.get(); 468 final String intValueLabel = INFO_LDAP_RC_INT_VALUE_LABEL.get(); 469 int numCharsInLongestName = nameLabel.length(); 470 int numCharsInLongestIntValue = intValueLabel.length(); 471 for (final Map.Entry<Integer,ResultCode> e : 472 resultCodesByIntValue.entrySet()) 473 { 474 final String intValueString = String.valueOf(e.getKey()); 475 numCharsInLongestIntValue = 476 Math.max(numCharsInLongestIntValue, intValueString.length()); 477 478 final String name = e.getValue().getName(); 479 numCharsInLongestName = Math.max(numCharsInLongestName, name.length()); 480 } 481 482 483 // Construct the column formatter that will be used to generate the output. 484 final boolean json; 485 final OutputFormat outputFormat; 486 final boolean scriptFriendly = 487 ((scriptFriendlyArg != null) && scriptFriendlyArg.isPresent()); 488 if (scriptFriendly) 489 { 490 json = false; 491 outputFormat = OutputFormat.TAB_DELIMITED_TEXT; 492 } 493 else if ((outputFormatArg != null) && outputFormatArg.isPresent()) 494 { 495 final String outputFormatValue = 496 StaticUtils.toLowerCase(outputFormatArg.getValue()); 497 if (outputFormatValue.equals(OUTPUT_FORMAT_CSV)) 498 { 499 json = false; 500 outputFormat = OutputFormat.CSV; 501 } 502 else if (outputFormatValue.equals(OUTPUT_FORMAT_JSON)) 503 { 504 json = true; 505 outputFormat = null; 506 } 507 else if (outputFormatValue.equals(OUTPUT_FORMAT_TAB_DELIMITED)) 508 { 509 json = false; 510 outputFormat = OutputFormat.TAB_DELIMITED_TEXT; 511 } 512 else 513 { 514 json = false; 515 outputFormat = OutputFormat.COLUMNS; 516 } 517 } 518 else 519 { 520 json = false; 521 outputFormat = OutputFormat.COLUMNS; 522 } 523 524 final ColumnFormatter formatter; 525 if (json) 526 { 527 formatter = null; 528 } 529 else 530 { 531 formatter = new ColumnFormatter(false, null, outputFormat, " | ", 532 new FormattableColumn(numCharsInLongestName, 533 HorizontalAlignment.LEFT, nameLabel), 534 new FormattableColumn(numCharsInLongestIntValue, 535 HorizontalAlignment.LEFT, intValueLabel)); 536 } 537 538 539 // Display the table header, if appropriate. 540 if ((formatter != null) && (outputFormat == OutputFormat.COLUMNS)) 541 { 542 for (final String line : formatter.getHeaderLines(true)) 543 { 544 out(line); 545 } 546 } 547 548 549 // Display the main output. 550 final Collection<ResultCode> resultCodes; 551 if ((alphabeticOrderArg != null) && alphabeticOrderArg.isPresent()) 552 { 553 resultCodes = resultCodesByName.values(); 554 } 555 else 556 { 557 resultCodes = resultCodesByIntValue.values(); 558 } 559 560 for (final ResultCode rc : resultCodes) 561 { 562 if (formatter == null) 563 { 564 final JSONObject jsonObject = new JSONObject( 565 new JSONField(JSON_FIELD_NAME, rc.getName()), 566 new JSONField(JSON_FIELD_INT_VALUE, rc.intValue())); 567 out(jsonObject.toSingleLineString()); 568 } 569 else 570 { 571 out(formatter.formatRow(rc.getName(), rc.intValue())); 572 } 573 } 574 575 return ResultCode.SUCCESS; 576 } 577 578 579 580 /** 581 * {@inheritDoc} 582 */ 583 @Override() 584 @NotNull() 585 public LinkedHashMap<String[],String> getExampleUsages() 586 { 587 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 588 589 examples.put( 590 StaticUtils.NO_STRINGS, 591 INFO_LDAP_RC_EXAMPLE_1.get()); 592 593 examples.put( 594 new String[] 595 { 596 "--int-value", "49", 597 "--output-format", "json" 598 }, 599 INFO_LDAP_RC_EXAMPLE_2.get()); 600 601 examples.put( 602 new String[] 603 { 604 "--search", "attribute" 605 }, 606 INFO_LDAP_RC_EXAMPLE_3.get()); 607 608 return examples; 609 } 610}