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 logToolInvocationByDefault() 334 { 335 return false; 336 } 337 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override() 344 public void addToolArguments(@NotNull final ArgumentParser parser) 345 throws ArgumentException 346 { 347 listArg = new BooleanArgument('l', "list", 1, 348 INFO_LDAP_RC_ARG_DESC_LIST.get()); 349 parser.addArgument(listArg); 350 351 intValueArg = new IntegerArgument('i', "int-value", false, 1, 352 INFO_LDAP_RC_ARG_PLACEHOLDER_INT_VALUE.get(), 353 INFO_LDAP_RC_ARG_DESC_INT_VALUE.get()); 354 intValueArg.addLongIdentifier("intValue", true); 355 parser.addArgument(intValueArg); 356 357 searchArg = new StringArgument('S', "search", false, 1, 358 INFO_LDAP_RC_ARG_PLACEHOLDER_SEARCH_STRING.get(), 359 INFO_LDAP_RC_ARG_DESC_SEARCH.get()); 360 parser.addArgument(searchArg); 361 362 alphabeticOrderArg = new BooleanArgument('a', "alphabetic-order", 1, 363 INFO_LDAP_RC_ARG_DESC_ALPHABETIC.get()); 364 alphabeticOrderArg.addLongIdentifier("alphabeticOrder", true); 365 alphabeticOrderArg.addLongIdentifier("alphabetical-order", true); 366 alphabeticOrderArg.addLongIdentifier("alphabeticalOrder", true); 367 alphabeticOrderArg.addLongIdentifier("alphabetic", true); 368 alphabeticOrderArg.addLongIdentifier("alphabetical", true); 369 parser.addArgument(alphabeticOrderArg); 370 371 outputFormatArg = new StringArgument(null, "output-format", false, 1, 372 INFO_LDAP_RC_ARG_PLACEHOLDER_OUTPUT_FORMAT.get(), 373 INFO_LDAP_RC_ARG_DESC_OUTPUT_FORMAT.get(), 374 StaticUtils.setOf( 375 OUTPUT_FORMAT_CSV, 376 OUTPUT_FORMAT_JSON, 377 OUTPUT_FORMAT_TAB_DELIMITED, 378 OUTPUT_FORMAT_TABLE)); 379 outputFormatArg.addLongIdentifier("outputFormat", true); 380 outputFormatArg.addLongIdentifier("format", true); 381 parser.addArgument(outputFormatArg); 382 383 scriptFriendlyArg = new BooleanArgument(null, "script-friendly", 1, 384 INFO_LDAP_RC_ARG_DESC_SCRIPT_FRIENDLY.get()); 385 scriptFriendlyArg.addLongIdentifier("scriptFriendly", true); 386 scriptFriendlyArg.setHidden(true); 387 parser.addArgument(scriptFriendlyArg); 388 389 parser.addExclusiveArgumentSet(listArg, intValueArg, searchArg); 390 parser.addExclusiveArgumentSet(outputFormatArg, scriptFriendlyArg); 391 } 392 393 394 395 /** 396 * {@inheritDoc} 397 */ 398 @Override() 399 @NotNull() 400 public ResultCode doToolProcessing() 401 { 402 // Get all result codes that should be included in the output. 403 final Map<Integer,ResultCode> resultCodesByIntValue = new TreeMap<>(); 404 final Map<String,ResultCode> resultCodesByName = new TreeMap<>(); 405 if ((intValueArg != null) && intValueArg.isPresent()) 406 { 407 final int intValue = intValueArg.getValue(); 408 final ResultCode rc = ResultCode.valueOf(intValue, null, false); 409 if (rc != null) 410 { 411 resultCodesByIntValue.put(intValue, rc); 412 resultCodesByName.put(StaticUtils.toLowerCase(rc.getName()), rc); 413 } 414 } 415 else 416 { 417 final String searchString; 418 if ((searchArg != null) && searchArg.isPresent()) 419 { 420 searchString = StaticUtils.toLowerCase(searchArg.getValue()); 421 } 422 else 423 { 424 searchString = null; 425 } 426 427 for (final ResultCode rc : ResultCode.values()) 428 { 429 final String name = rc.getName(); 430 final String lowerName = StaticUtils.toLowerCase(name); 431 if (searchString != null) 432 { 433 if (! lowerName.contains(searchString)) 434 { 435 continue; 436 } 437 } 438 439 resultCodesByIntValue.put(rc.intValue(), rc); 440 resultCodesByName.put(lowerName, rc); 441 } 442 } 443 444 445 // If there weren't any matching result codes, then inform the user and 446 // exit with an error. 447 if (resultCodesByIntValue.isEmpty()) 448 { 449 wrapErr(0, WRAP_COLUMN, ERR_LDAP_RC_NO_RESULTS.get()); 450 return ResultCode.NO_RESULTS_RETURNED; 451 } 452 453 454 // Iterate through the matching result codes and figure out how many 455 // characters are in the longest name and 456 final String nameLabel = INFO_LDAP_RC_NAME_LABEL.get(); 457 final String intValueLabel = INFO_LDAP_RC_INT_VALUE_LABEL.get(); 458 int numCharsInLongestName = nameLabel.length(); 459 int numCharsInLongestIntValue = intValueLabel.length(); 460 for (final Map.Entry<Integer,ResultCode> e : 461 resultCodesByIntValue.entrySet()) 462 { 463 final String intValueString = String.valueOf(e.getKey()); 464 numCharsInLongestIntValue = 465 Math.max(numCharsInLongestIntValue, intValueString.length()); 466 467 final String name = e.getValue().getName(); 468 numCharsInLongestName = Math.max(numCharsInLongestName, name.length()); 469 } 470 471 472 // Construct the column formatter that will be used to generate the output. 473 final boolean json; 474 final OutputFormat outputFormat; 475 final boolean scriptFriendly = 476 ((scriptFriendlyArg != null) && scriptFriendlyArg.isPresent()); 477 if (scriptFriendly) 478 { 479 json = false; 480 outputFormat = OutputFormat.TAB_DELIMITED_TEXT; 481 } 482 else if ((outputFormatArg != null) && outputFormatArg.isPresent()) 483 { 484 final String outputFormatValue = 485 StaticUtils.toLowerCase(outputFormatArg.getValue()); 486 if (outputFormatValue.equals(OUTPUT_FORMAT_CSV)) 487 { 488 json = false; 489 outputFormat = OutputFormat.CSV; 490 } 491 else if (outputFormatValue.equals(OUTPUT_FORMAT_JSON)) 492 { 493 json = true; 494 outputFormat = null; 495 } 496 else if (outputFormatValue.equals(OUTPUT_FORMAT_TAB_DELIMITED)) 497 { 498 json = false; 499 outputFormat = OutputFormat.TAB_DELIMITED_TEXT; 500 } 501 else 502 { 503 json = false; 504 outputFormat = OutputFormat.COLUMNS; 505 } 506 } 507 else 508 { 509 json = false; 510 outputFormat = OutputFormat.COLUMNS; 511 } 512 513 final ColumnFormatter formatter; 514 if (json) 515 { 516 formatter = null; 517 } 518 else 519 { 520 formatter = new ColumnFormatter(false, null, outputFormat, " | ", 521 new FormattableColumn(numCharsInLongestName, 522 HorizontalAlignment.LEFT, nameLabel), 523 new FormattableColumn(numCharsInLongestIntValue, 524 HorizontalAlignment.LEFT, intValueLabel)); 525 } 526 527 528 // Display the table header, if appropriate. 529 if ((formatter != null) && (outputFormat == OutputFormat.COLUMNS)) 530 { 531 for (final String line : formatter.getHeaderLines(true)) 532 { 533 out(line); 534 } 535 } 536 537 538 // Display the main output. 539 final Collection<ResultCode> resultCodes; 540 if ((alphabeticOrderArg != null) && alphabeticOrderArg.isPresent()) 541 { 542 resultCodes = resultCodesByName.values(); 543 } 544 else 545 { 546 resultCodes = resultCodesByIntValue.values(); 547 } 548 549 for (final ResultCode rc : resultCodes) 550 { 551 if (formatter == null) 552 { 553 final JSONObject jsonObject = new JSONObject( 554 new JSONField(JSON_FIELD_NAME, rc.getName()), 555 new JSONField(JSON_FIELD_INT_VALUE, rc.intValue())); 556 out(jsonObject.toSingleLineString()); 557 } 558 else 559 { 560 out(formatter.formatRow(rc.getName(), rc.intValue())); 561 } 562 } 563 564 return ResultCode.SUCCESS; 565 } 566 567 568 569 /** 570 * {@inheritDoc} 571 */ 572 @Override() 573 @NotNull() 574 public LinkedHashMap<String[],String> getExampleUsages() 575 { 576 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 577 578 examples.put( 579 StaticUtils.NO_STRINGS, 580 INFO_LDAP_RC_EXAMPLE_1.get()); 581 582 examples.put( 583 new String[] 584 { 585 "--int-value", "49", 586 "--output-format", "json" 587 }, 588 INFO_LDAP_RC_EXAMPLE_2.get()); 589 590 examples.put( 591 new String[] 592 { 593 "--search", "attribute" 594 }, 595 INFO_LDAP_RC_EXAMPLE_3.get()); 596 597 return examples; 598 } 599}