001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS. 026 */ 027package org.opends.server.tools; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.opendj.ldap.DecodeException; 031import org.forgerock.opendj.config.server.ConfigException; 032import org.opends.server.core.DirectoryServer; 033import org.opends.server.loggers.JDKLogging; 034import org.opends.server.tools.tasks.TaskClient; 035import org.opends.server.tools.tasks.TaskEntry; 036import org.opends.server.types.InitializationException; 037import org.opends.server.types.LDAPException; 038import org.opends.server.util.BuildVersion; 039import org.opends.server.util.StaticUtils; 040 041import com.forgerock.opendj.cli.ArgumentException; 042import com.forgerock.opendj.cli.BooleanArgument; 043import com.forgerock.opendj.cli.ClientException; 044import com.forgerock.opendj.cli.CommonArguments; 045import com.forgerock.opendj.cli.StringArgument; 046 047import org.opends.server.backends.task.TaskState; 048import org.opends.server.util.args.LDAPConnectionArgumentParser; 049import org.opends.server.util.cli.LDAPConnectionConsoleInteraction; 050 051import com.forgerock.opendj.cli.ConsoleApplication; 052import com.forgerock.opendj.cli.Menu; 053import com.forgerock.opendj.cli.MenuBuilder; 054import com.forgerock.opendj.cli.MenuCallback; 055import com.forgerock.opendj.cli.MenuResult; 056import com.forgerock.opendj.cli.TableBuilder; 057import com.forgerock.opendj.cli.TextTablePrinter; 058 059import java.io.IOException; 060import java.io.InputStream; 061import java.io.OutputStream; 062import java.io.PrintStream; 063import java.io.StringWriter; 064import java.util.ArrayList; 065import java.util.List; 066import java.util.Map; 067import java.util.TreeMap; 068 069import static org.opends.messages.ToolMessages.*; 070 071import static com.forgerock.opendj.cli.ArgumentConstants.*; 072import static com.forgerock.opendj.cli.Utils.filterExitCode; 073 074/** 075 * Tool for getting information and managing tasks in the Directory Server. 076 */ 077public class ManageTasks extends ConsoleApplication { 078 079 /** This CLI is always using the administration connector with SSL. */ 080 private static final boolean alwaysSSL = true; 081 082 /** 083 * The main method for TaskInfo tool. 084 * 085 * @param args The command-line arguments provided to this program. 086 */ 087 public static void main(String[] args) { 088 int retCode = mainTaskInfo(args, System.in, System.out, System.err); 089 090 if (retCode != 0) { 091 System.exit(filterExitCode(retCode)); 092 } 093 } 094 095 /** 096 * Processes the command-line arguments and invokes the process for 097 * displaying task information. 098 * 099 * @param args The command-line arguments provided to this program. 100 * @return int return code 101 */ 102 public static int mainTaskInfo(String[] args) { 103 return mainTaskInfo(args, System.in, System.out, System.err); 104 } 105 106 /** 107 * Processes the command-line arguments and invokes the export process. 108 * 109 * @param args The command-line arguments provided to this 110 * @param in Input stream from which to solicit user input. 111 * @param out The output stream to use for standard output, or 112 * {@code null} if standard output is not needed. 113 * @param err The output stream to use for standard error, or 114 * {@code null} if standard error is not needed. 115 * @param initializeServer Indicates whether to initialize the server. 116 * @return int return code 117 */ 118 public static int mainTaskInfo(String[] args, 119 InputStream in, 120 OutputStream out, 121 OutputStream err, 122 boolean initializeServer) { 123 ManageTasks tool = new ManageTasks(in, out, err); 124 return tool.process(args, initializeServer); 125 } 126 127 /** 128 * Processes the command-line arguments and invokes the export process. 129 * 130 * @param args The command-line arguments provided to this 131 * @param in Input stream from which to solicit user input. 132 * @param out The output stream to use for standard output, or 133 * {@code null} if standard output is not needed. 134 * @param err The output stream to use for standard error, or 135 * {@code null} if standard error is not needed. 136 * @return int return code 137 */ 138 public static int mainTaskInfo(String[] args, 139 InputStream in, 140 OutputStream out, 141 OutputStream err) { 142 return mainTaskInfo(args, in, out, err, true); 143 } 144 145 private static final int INDENT = 2; 146 147 /** 148 * ID of task for which to display details and exit. 149 */ 150 private StringArgument task; 151 152 /** 153 * Indicates print summary and exit. 154 */ 155 private BooleanArgument summary; 156 157 /** 158 * ID of task to cancel. 159 */ 160 private StringArgument cancel; 161 162 /** 163 * Argument used to request non-interactive behavior. 164 */ 165 private BooleanArgument noPrompt; 166 167 /** 168 * Accesses the directory's task backend. 169 */ 170 private TaskClient taskClient; 171 172 /** 173 * Constructs a parameterized instance. 174 * 175 * @param in Input stream from which to solicit user input. 176 * @param out The output stream to use for standard output, or 177 * {@code null} if standard output is not needed. 178 * @param err The output stream to use for standard error, or 179 * {@code null} if standard error is not needed. 180 */ 181 public ManageTasks(InputStream in, OutputStream out, OutputStream err) 182 { 183 super(new PrintStream(out), new PrintStream(err)); 184 } 185 186 /** 187 * Processes the command-line arguments and invokes the export process. 188 * 189 * @param args The command-line arguments provided to this 190 * program. 191 * @return The error code. 192 */ 193 public int process(String[] args) 194 { 195 return process(args, true); 196 } 197 198 /** 199 * Processes the command-line arguments and invokes the export process. 200 * 201 * @param args The command-line arguments provided to this 202 * program. 203 * @param initializeServer Indicates whether to initialize the server. 204 * @return The error code. 205 */ 206 public int process(String[] args, boolean initializeServer) 207 { 208 if (initializeServer) 209 { 210 DirectoryServer.bootstrapClient(); 211 } 212 JDKLogging.disableLogging(); 213 214 215 // Create the command-line argument parser for use with this program. 216 LDAPConnectionArgumentParser argParser = new LDAPConnectionArgumentParser( 217 "org.opends.server.tools.TaskInfo", 218 INFO_TASKINFO_TOOL_DESCRIPTION.get(), 219 false, null, alwaysSSL); 220 argParser.setShortToolDescription(REF_SHORT_DESC_MANAGE_TASKS.get()); 221 222 // Initialize all the command-line argument types and register them with the 223 // parser. 224 try { 225 226 StringArgument propertiesFileArgument = new StringArgument( 227 "propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH, false, false, 228 true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null, 229 INFO_DESCRIPTION_PROP_FILE_PATH.get()); 230 argParser.addArgument(propertiesFileArgument); 231 argParser.setFilePropertiesArgument(propertiesFileArgument); 232 233 BooleanArgument noPropertiesFileArgument = new BooleanArgument( 234 "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE, 235 INFO_DESCRIPTION_NO_PROP_FILE.get()); 236 argParser.addArgument(noPropertiesFileArgument); 237 argParser.setNoPropertiesFileArgument(noPropertiesFileArgument); 238 239 task = new StringArgument( 240 "info", 'i', "info", 241 false, true, INFO_TASK_ID_PLACEHOLDER.get(), 242 INFO_TASKINFO_TASK_ARG_DESCRIPTION.get()); 243 argParser.addArgument(task); 244 245 cancel = new StringArgument( 246 "cancel", 'c', "cancel", 247 false, true, INFO_TASK_ID_PLACEHOLDER.get(), 248 INFO_TASKINFO_TASK_ARG_CANCEL.get()); 249 argParser.addArgument(cancel); 250 251 summary = new BooleanArgument( 252 "summary", 's', "summary", 253 INFO_TASKINFO_SUMMARY_ARG_DESCRIPTION.get()); 254 argParser.addArgument(summary); 255 256 noPrompt = CommonArguments.getNoPrompt(); 257 argParser.addArgument(noPrompt); 258 259 BooleanArgument displayUsage = CommonArguments.getShowUsage(); 260 argParser.addArgument(displayUsage); 261 argParser.setUsageArgument(displayUsage); 262 } 263 catch (ArgumentException ae) { 264 LocalizableMessage message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); 265 println(message); 266 return 1; 267 } 268 269 try 270 { 271 argParser.getArguments().initArgumentsWithConfiguration(); 272 } 273 catch (ConfigException ce) 274 { 275 // Ignore. 276 } 277 278 // Parse the command-line arguments provided to this program. 279 try { 280 argParser.parseArguments(args); 281 StaticUtils.checkOnlyOneArgPresent(task, summary, cancel); 282 } 283 catch (ArgumentException ae) { 284 argParser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 285 return 1; 286 } 287 288 if (!argParser.usageOrVersionDisplayed()) { 289 // Checks the version - if upgrade required, the tool is unusable 290 try 291 { 292 BuildVersion.checkVersionMismatch(); 293 } 294 catch (InitializationException e) 295 { 296 println(e.getMessageObject()); 297 return 1; 298 } 299 300 try { 301 LDAPConnectionConsoleInteraction ui = 302 new LDAPConnectionConsoleInteraction( 303 this, argParser.getArguments()); 304 305 taskClient = new TaskClient(argParser.connect(ui, 306 getOutputStream(), getErrorStream())); 307 308 if (isMenuDrivenMode()) { 309 310 // Keep prompting the user until they specify quit of 311 // there is a fatal exception 312 while (true) { 313 getOutputStream().println(); 314 Menu<Void> menu = getSummaryMenu(); 315 MenuResult<Void> result = menu.run(); 316 if (result.isQuit()) { 317 return 0; 318 } 319 } 320 321 } else if (task.isPresent()) { 322 getOutputStream().println(); 323 MenuResult<TaskEntry> r = 324 new PrintTaskInfo(task.getValue()).invoke(this); 325 if (r.isAgain()) 326 { 327 return 1; 328 } 329 } else if (summary.isPresent()) { 330 getOutputStream().println(); 331 printSummaryTable(); 332 } else if (cancel.isPresent()) { 333 MenuResult<TaskEntry> r = 334 new CancelTask(cancel.getValue()).invoke(this); 335 if (r.isAgain()) 336 { 337 return 1; 338 } 339 } else if (!isInteractive()) { 340 // no-prompt option 341 getOutputStream().println(); 342 printSummaryTable(); 343 return 0; 344 } 345 346 } catch (LDAPConnectionException lce) { 347 println(INFO_TASKINFO_LDAP_EXCEPTION.get(lce.getMessageObject())); 348 return 1; 349 } catch (Exception e) { 350 println(LocalizableMessage.raw(StaticUtils.getExceptionMessage(e))); 351 return 1; 352 } 353 } 354 return 0; 355 } 356 357 /** {@inheritDoc} */ 358 @Override 359 public boolean isAdvancedMode() { 360 return false; 361 } 362 363 /** {@inheritDoc} */ 364 @Override 365 public boolean isInteractive() { 366 return !noPrompt.isPresent(); 367 } 368 369 /** {@inheritDoc} */ 370 @Override 371 public boolean isMenuDrivenMode() { 372 return !task.isPresent() && !cancel.isPresent() && !summary.isPresent() && 373 !noPrompt.isPresent(); 374 } 375 376 /** {@inheritDoc} */ 377 @Override 378 public boolean isQuiet() { 379 return false; 380 } 381 382 /** {@inheritDoc} */ 383 @Override 384 public boolean isScriptFriendly() { 385 return false; 386 } 387 388 /** {@inheritDoc} */ 389 @Override 390 public boolean isVerbose() { 391 return false; 392 } 393 394 /** 395 * Creates the summary table. 396 * 397 * @throws IOException if there is a problem with screen I/O 398 * @throws LDAPException if there is a problem getting information 399 * out to the directory 400 * @throws DecodeException if there is a problem with the encoding 401 */ 402 private void printSummaryTable() 403 throws LDAPException, IOException, DecodeException { 404 List<TaskEntry> entries = taskClient.getTaskEntries(); 405 if (!entries.isEmpty()) { 406 TableBuilder table = new TableBuilder(); 407 Map<String, TaskEntry> mapIdToEntry = new TreeMap<>(); 408 for (TaskEntry entry : entries) { 409 String taskId = entry.getId(); 410 if (taskId != null) { 411 mapIdToEntry.put(taskId, entry); 412 } 413 } 414 415 table.appendHeading(INFO_TASKINFO_FIELD_ID.get()); 416 table.appendHeading(INFO_TASKINFO_FIELD_TYPE.get()); 417 table.appendHeading(INFO_TASKINFO_FIELD_STATUS.get()); 418 for (String taskId : mapIdToEntry.keySet()) { 419 TaskEntry entryWrapper = mapIdToEntry.get(taskId); 420 table.startRow(); 421 table.appendCell(taskId); 422 table.appendCell(entryWrapper.getType()); 423 table.appendCell(entryWrapper.getState()); 424 } 425 StringWriter sw = new StringWriter(); 426 TextTablePrinter tablePrinter = new TextTablePrinter(sw); 427 tablePrinter.setIndentWidth(INDENT); 428 tablePrinter.setTotalWidth(80); 429 table.print(tablePrinter); 430 getOutputStream().println(LocalizableMessage.raw(sw.getBuffer())); 431 } else { 432 getOutputStream().println(INFO_TASKINFO_NO_TASKS.get()); 433 getOutputStream().println(); 434 } 435 } 436 437 /** 438 * Creates the summary table. 439 * 440 * @return list of strings of IDs of all the tasks in the table in order 441 * of the indexes printed in the table 442 * @throws IOException if there is a problem with screen I/O 443 * @throws LDAPException if there is a problem getting information 444 * out to the directory 445 * @throws DecodeException if there is a problem with the encoding 446 */ 447 private Menu<Void> getSummaryMenu() 448 throws LDAPException, IOException, DecodeException { 449 List<String> taskIds = new ArrayList<>(); 450 List<Integer> cancelableIndices = new ArrayList<>(); 451 List<TaskEntry> entries = taskClient.getTaskEntries(); 452 MenuBuilder<Void> menuBuilder = new MenuBuilder<>(this); 453 if (!entries.isEmpty()) { 454 Map<String, TaskEntry> mapIdToEntry = new TreeMap<>(); 455 for (TaskEntry entry : entries) { 456 String taskId = entry.getId(); 457 if (taskId != null) { 458 mapIdToEntry.put(taskId, entry); 459 } 460 } 461 462 menuBuilder.setColumnHeadings( 463 INFO_TASKINFO_FIELD_ID.get(), 464 INFO_TASKINFO_FIELD_TYPE.get(), 465 INFO_TASKINFO_FIELD_STATUS.get()); 466 menuBuilder.setColumnWidths(null, null, 0); 467 int index = 0; 468 for (final String taskId : mapIdToEntry.keySet()) { 469 taskIds.add(taskId); 470 final TaskEntry taskEntry = mapIdToEntry.get(taskId); 471 menuBuilder.addNumberedOption( 472 LocalizableMessage.raw(taskEntry.getId()), 473 new TaskDrilldownMenu(taskId), 474 taskEntry.getType(), taskEntry.getState()); 475 index++; 476 if (taskEntry.isCancelable()) { 477 cancelableIndices.add(index); 478 } 479 } 480 } else { 481 getOutputStream().println(INFO_TASKINFO_NO_TASKS.get()); 482 getOutputStream().println(); 483 } 484 485 menuBuilder.addCharOption( 486 INFO_TASKINFO_CMD_REFRESH_CHAR.get(), 487 INFO_TASKINFO_CMD_REFRESH.get(), 488 new PrintSummaryTop()); 489 490 if (!cancelableIndices.isEmpty()) { 491 menuBuilder.addCharOption( 492 INFO_TASKINFO_CMD_CANCEL_CHAR.get(), 493 INFO_TASKINFO_CMD_CANCEL.get(), 494 new CancelTaskTop(taskIds, cancelableIndices)); 495 } 496 menuBuilder.addQuitOption(); 497 498 return menuBuilder.toMenu(); 499 } 500 501 /** 502 * Gets the client that can be used to interact with the task backend. 503 * 504 * @return TaskClient for interacting with the task backend. 505 */ 506 public TaskClient getTaskClient() { 507 return this.taskClient; 508 } 509 510 /** 511 * Base for callbacks that implement top level menu items. 512 */ 513 private static abstract class TopMenuCallback 514 implements MenuCallback<Void> { 515 516 /** {@inheritDoc} */ 517 @Override 518 public MenuResult<Void> invoke(ConsoleApplication app) throws ClientException { 519 return invoke((ManageTasks)app); 520 } 521 522 /** 523 * Called upon task invocation. 524 * 525 * @param app this console application 526 * @return MessageResult result of task 527 * @throws ClientException if there is a problem 528 */ 529 protected abstract MenuResult<Void> invoke(ManageTasks app) 530 throws ClientException; 531 532 } 533 534 /** 535 * Base for callbacks that manage task entries. 536 */ 537 private static abstract class TaskOperationCallback 538 implements MenuCallback<TaskEntry> { 539 540 /** ID of the task to manage. */ 541 protected String taskId; 542 543 /** 544 * Constructs a parameterized instance. 545 * 546 * @param taskId if the task to examine 547 */ 548 public TaskOperationCallback(String taskId) { 549 this.taskId = taskId; 550 } 551 552 /** {@inheritDoc} */ 553 @Override 554 public MenuResult<TaskEntry> invoke(ConsoleApplication app) 555 throws ClientException 556 { 557 return invoke((ManageTasks)app); 558 } 559 560 /** 561 * Invokes the task. 562 * 563 * @param app 564 * the current application running 565 * @return how the application should proceed next 566 * @throws ClientException 567 * if any problem occurred 568 */ 569 protected abstract MenuResult<TaskEntry> invoke(ManageTasks app) 570 throws ClientException; 571 572 } 573 574 /** 575 * Executable for printing a task summary table. 576 */ 577 private static class PrintSummaryTop extends TopMenuCallback { 578 579 @Override 580 public MenuResult<Void> invoke(ManageTasks app) 581 throws ClientException 582 { 583 // Since the summary table is reprinted every time the 584 // user enters the top level this task just returns 585 // 'success' 586 return MenuResult.success(); 587 } 588 } 589 590 /** 591 * Executable for printing a particular task's details. 592 */ 593 private static class TaskDrilldownMenu extends TopMenuCallback { 594 595 private String taskId; 596 597 /** 598 * Constructs a parameterized instance. 599 * 600 * @param taskId of the task for which information will be displayed 601 */ 602 public TaskDrilldownMenu(String taskId) { 603 this.taskId = taskId; 604 } 605 606 /** {@inheritDoc} */ 607 @Override 608 public MenuResult<Void> invoke(ManageTasks app) throws ClientException { 609 MenuResult<TaskEntry> res = new PrintTaskInfo(taskId).invoke(app); 610 TaskEntry taskEntry = res.getValue(); 611 if (taskEntry != null) { 612 while (true) { 613 try { 614 taskEntry = app.getTaskClient().getTaskEntry(taskId); 615 616 // Show the menu 617 MenuBuilder<TaskEntry> menuBuilder = new MenuBuilder<>(app); 618 menuBuilder.addBackOption(true); 619 menuBuilder.addCharOption( 620 INFO_TASKINFO_CMD_REFRESH_CHAR.get(), 621 INFO_TASKINFO_CMD_REFRESH.get(), 622 new PrintTaskInfo(taskId)); 623 List<LocalizableMessage> logs = taskEntry.getLogMessages(); 624 if (logs != null && !logs.isEmpty()) { 625 menuBuilder.addCharOption( 626 INFO_TASKINFO_CMD_VIEW_LOGS_CHAR.get(), 627 INFO_TASKINFO_CMD_VIEW_LOGS.get(), 628 new ViewTaskLogs(taskId)); 629 } 630 if (taskEntry.isCancelable() && !taskEntry.isDone()) { 631 menuBuilder.addCharOption( 632 INFO_TASKINFO_CMD_CANCEL_CHAR.get(), 633 INFO_TASKINFO_CMD_CANCEL.get(), 634 new CancelTask(taskId)); 635 } 636 menuBuilder.addQuitOption(); 637 Menu<TaskEntry> menu = menuBuilder.toMenu(); 638 MenuResult<TaskEntry> result = menu.run(); 639 if (result.isCancel()) { 640 break; 641 } else if (result.isQuit()) { 642 System.exit(0); 643 } 644 } catch (Exception e) { 645 app.println(LocalizableMessage.raw(StaticUtils.getExceptionMessage(e))); 646 } 647 } 648 } else { 649 app.println(ERR_TASKINFO_UNKNOWN_TASK_ENTRY.get(taskId)); 650 } 651 return MenuResult.success(); 652 } 653 654 } 655 656 /** 657 * Executable for printing a particular task's details. 658 */ 659 private static class PrintTaskInfo extends TaskOperationCallback { 660 661 /** 662 * Constructs a parameterized instance. 663 * 664 * @param taskId of the task for which information will be printed 665 */ 666 public PrintTaskInfo(String taskId) { 667 super(taskId); 668 } 669 670 /** {@inheritDoc} */ 671 @Override 672 public MenuResult<TaskEntry> invoke(ManageTasks app) 673 throws ClientException 674 { 675 LocalizableMessage m; 676 TaskEntry taskEntry; 677 try { 678 taskEntry = app.getTaskClient().getTaskEntry(taskId); 679 680 TableBuilder table = new TableBuilder(); 681 table.appendHeading(INFO_TASKINFO_DETAILS.get()); 682 683 table.startRow(); 684 table.appendCell(INFO_TASKINFO_FIELD_ID.get()); 685 table.appendCell(taskEntry.getId()); 686 687 table.startRow(); 688 table.appendCell(INFO_TASKINFO_FIELD_TYPE.get()); 689 table.appendCell(taskEntry.getType()); 690 691 table.startRow(); 692 table.appendCell(INFO_TASKINFO_FIELD_STATUS.get()); 693 table.appendCell(taskEntry.getState()); 694 695 table.startRow(); 696 table.appendCell(INFO_TASKINFO_FIELD_SCHEDULED_START.get()); 697 698 if (TaskState.isRecurring(taskEntry.getTaskState())) { 699 m = taskEntry.getScheduleTab(); 700 table.appendCell(m); 701 } else { 702 m = taskEntry.getScheduledStartTime(); 703 if (m == null || m.equals(LocalizableMessage.EMPTY)) { 704 table.appendCell(INFO_TASKINFO_IMMEDIATE_EXECUTION.get()); 705 } else { 706 table.appendCell(m); 707 } 708 709 table.startRow(); 710 table.appendCell(INFO_TASKINFO_FIELD_ACTUAL_START.get()); 711 table.appendCell(taskEntry.getActualStartTime()); 712 713 table.startRow(); 714 table.appendCell(INFO_TASKINFO_FIELD_COMPLETION_TIME.get()); 715 table.appendCell(taskEntry.getCompletionTime()); 716 } 717 718 writeMultiValueCells( 719 table, 720 INFO_TASKINFO_FIELD_DEPENDENCY.get(), 721 taskEntry.getDependencyIds()); 722 723 table.startRow(); 724 table.appendCell(INFO_TASKINFO_FIELD_FAILED_DEPENDENCY_ACTION.get()); 725 m = taskEntry.getFailedDependencyAction(); 726 table.appendCell(m != null ? m : INFO_TASKINFO_NONE.get()); 727 728 writeMultiValueCells( 729 table, 730 INFO_TASKINFO_FIELD_NOTIFY_ON_COMPLETION.get(), 731 taskEntry.getCompletionNotificationEmailAddresses(), 732 INFO_TASKINFO_NONE_SPECIFIED.get()); 733 734 writeMultiValueCells( 735 table, 736 INFO_TASKINFO_FIELD_NOTIFY_ON_ERROR.get(), 737 taskEntry.getErrorNotificationEmailAddresses(), 738 INFO_TASKINFO_NONE_SPECIFIED.get()); 739 740 StringWriter sw = new StringWriter(); 741 TextTablePrinter tablePrinter = new TextTablePrinter(sw); 742 tablePrinter.setTotalWidth(80); 743 tablePrinter.setIndentWidth(INDENT); 744 tablePrinter.setColumnWidth(1, 0); 745 table.print(tablePrinter); 746 app.getOutputStream().println(); 747 app.getOutputStream().println(LocalizableMessage.raw(sw.getBuffer().toString())); 748 749 // Create a table for the task options 750 table = new TableBuilder(); 751 table.appendHeading(INFO_TASKINFO_OPTIONS.get(taskEntry.getType())); 752 Map<LocalizableMessage,List<String>> taskSpecificAttrs = 753 taskEntry.getTaskSpecificAttributeValuePairs(); 754 for (LocalizableMessage attrName : taskSpecificAttrs.keySet()) { 755 table.startRow(); 756 table.appendCell(attrName); 757 List<String> values = taskSpecificAttrs.get(attrName); 758 if (!values.isEmpty()) { 759 table.appendCell(values.get(0)); 760 } 761 if (values.size() > 1) { 762 for (int i = 1; i < values.size(); i++) { 763 table.startRow(); 764 table.appendCell(); 765 table.appendCell(values.get(i)); 766 } 767 } 768 } 769 sw = new StringWriter(); 770 tablePrinter = new TextTablePrinter(sw); 771 tablePrinter.setTotalWidth(80); 772 tablePrinter.setIndentWidth(INDENT); 773 tablePrinter.setColumnWidth(1, 0); 774 table.print(tablePrinter); 775 app.getOutputStream().println(LocalizableMessage.raw(sw.getBuffer().toString())); 776 777 // Print the last log message if any 778 List<LocalizableMessage> logs = taskEntry.getLogMessages(); 779 if (logs != null && !logs.isEmpty()) { 780 // Create a table for the last log entry 781 table = new TableBuilder(); 782 table.appendHeading(INFO_TASKINFO_FIELD_LAST_LOG.get()); 783 table.startRow(); 784 table.appendCell(logs.get(logs.size() - 1)); 785 786 sw = new StringWriter(); 787 tablePrinter = new TextTablePrinter(sw); 788 tablePrinter.setTotalWidth(80); 789 tablePrinter.setIndentWidth(INDENT); 790 tablePrinter.setColumnWidth(0, 0); 791 table.print(tablePrinter); 792 app.getOutputStream().println(LocalizableMessage.raw(sw.getBuffer().toString())); 793 } 794 795 app.getOutputStream().println(); 796 } catch (Exception e) { 797 app.println(ERR_TASKINFO_RETRIEVING_TASK_ENTRY.get(taskId, e.getMessage())); 798 return MenuResult.again(); 799 } 800 return MenuResult.success(taskEntry); 801 } 802 803 /** 804 * Writes an attribute and associated values to the table. 805 * @param table of task details 806 * @param fieldLabel of attribute 807 * @param values of the attribute 808 */ 809 private void writeMultiValueCells(TableBuilder table, 810 LocalizableMessage fieldLabel, 811 List<?> values) { 812 writeMultiValueCells(table, fieldLabel, values, INFO_TASKINFO_NONE.get()); 813 } 814 815 /** 816 * Writes an attribute and associated values to the table. 817 * 818 * @param table of task details 819 * @param fieldLabel of attribute 820 * @param values of the attribute 821 * @param noneLabel label for the value column when there are no values 822 */ 823 private void writeMultiValueCells(TableBuilder table, 824 LocalizableMessage fieldLabel, 825 List<?> values, 826 LocalizableMessage noneLabel) { 827 table.startRow(); 828 table.appendCell(fieldLabel); 829 if (values.isEmpty()) { 830 table.appendCell(noneLabel); 831 } else { 832 table.appendCell(values.get(0)); 833 } 834 if (values.size() > 1) { 835 for (int i = 1; i < values.size(); i++) { 836 table.startRow(); 837 table.appendCell(); 838 table.appendCell(values.get(i)); 839 } 840 } 841 } 842 } 843 844 /** 845 * Executable for printing a particular task's details. 846 */ 847 private static class ViewTaskLogs extends TaskOperationCallback { 848 849 /** 850 * Constructs a parameterized instance. 851 * 852 * @param taskId of the task for which log records will be printed 853 */ 854 public ViewTaskLogs(String taskId) { 855 super(taskId); 856 } 857 858 /** {@inheritDoc} */ 859 @Override 860 protected MenuResult<TaskEntry> invoke(ManageTasks app) 861 throws ClientException 862 { 863 TaskEntry taskEntry = null; 864 try { 865 taskEntry = app.getTaskClient().getTaskEntry(taskId); 866 List<LocalizableMessage> logs = taskEntry.getLogMessages(); 867 app.getOutputStream().println(); 868 869 // Create a table for the last log entry 870 TableBuilder table = new TableBuilder(); 871 table.appendHeading(INFO_TASKINFO_FIELD_LOG.get()); 872 if (logs != null && !logs.isEmpty()) { 873 for (LocalizableMessage log : logs) { 874 table.startRow(); 875 table.appendCell(log); 876 } 877 } else { 878 table.startRow(); 879 table.appendCell(INFO_TASKINFO_NONE.get()); 880 } 881 StringWriter sw = new StringWriter(); 882 TextTablePrinter tablePrinter = new TextTablePrinter(sw); 883 tablePrinter.setTotalWidth(80); 884 tablePrinter.setIndentWidth(INDENT); 885 tablePrinter.setColumnWidth(0, 0); 886 table.print(tablePrinter); 887 app.getOutputStream().println(LocalizableMessage.raw(sw.getBuffer().toString())); 888 app.getOutputStream().println(); 889 } catch (Exception e) { 890 app.println(ERR_TASKINFO_ACCESSING_LOGS.get(taskId, e.getMessage())); 891 } 892 return MenuResult.success(taskEntry); 893 } 894 } 895 896 /** 897 * Executable for canceling a particular task. 898 */ 899 private static class CancelTaskTop extends TopMenuCallback { 900 901 private List<String> taskIds; 902 private List<Integer> cancelableIndices; 903 904 /** 905 * Constructs a parameterized instance. 906 * 907 * @param taskIds of all known tasks 908 * @param cancelableIndices list of integers whose elements represent 909 * the indices of <code>taskIds</code> that are cancelable 910 */ 911 public CancelTaskTop(List<String> taskIds, 912 List<Integer> cancelableIndices) { 913 this.taskIds = taskIds; 914 this.cancelableIndices = cancelableIndices; 915 } 916 917 /** {@inheritDoc} */ 918 @Override 919 public MenuResult<Void> invoke(ManageTasks app) 920 throws ClientException 921 { 922 if (taskIds != null && !taskIds.isEmpty()) { 923 if (cancelableIndices != null && !cancelableIndices.isEmpty()) { 924 925 // Prompt for the task number 926 Integer index = null; 927 String line = app.readLineOfInput( 928 INFO_TASKINFO_CMD_CANCEL_NUMBER_PROMPT.get( 929 cancelableIndices.get(0))); 930 if (line.length() == 0) { 931 line = String.valueOf(cancelableIndices.get(0)); 932 } 933 try { 934 int i = Integer.parseInt(line); 935 if (!cancelableIndices.contains(i)) { 936 app.println(ERR_TASKINFO_NOT_CANCELABLE_TASK_INDEX.get(i)); 937 } else { 938 index = i - 1; 939 } 940 } catch (NumberFormatException nfe) { 941 // ignore; 942 } 943 if (index != null) { 944 String taskId = taskIds.get(index); 945 try { 946 CancelTask ct = new CancelTask(taskId); 947 MenuResult<TaskEntry> result = ct.invoke(app); 948 if (result.isSuccess()) { 949 return MenuResult.success(); 950 } else { 951 return MenuResult.again(); 952 } 953 } catch (Exception e) { 954 app.println(ERR_TASKINFO_CANCELING_TASK.get( 955 taskId, e.getMessage())); 956 return MenuResult.again(); 957 } 958 } else { 959 app.println(ERR_TASKINFO_INVALID_MENU_KEY.get(line)); 960 return MenuResult.again(); 961 } 962 } else { 963 app.println(INFO_TASKINFO_NO_CANCELABLE_TASKS.get()); 964 return MenuResult.cancel(); 965 } 966 } else { 967 app.println(INFO_TASKINFO_NO_TASKS.get()); 968 return MenuResult.cancel(); 969 } 970 } 971 972 } 973 974 /** 975 * Executable for canceling a particular task. 976 */ 977 private static class CancelTask extends TaskOperationCallback { 978 979 /** 980 * Constructs a parameterized instance. 981 * 982 * @param taskId of the task to cancel 983 */ 984 public CancelTask(String taskId) { 985 super(taskId); 986 } 987 988 /** {@inheritDoc} */ 989 @Override 990 public MenuResult<TaskEntry> invoke(ManageTasks app) 991 throws ClientException 992 { 993 try { 994 TaskEntry entry = app.getTaskClient().getTaskEntry(taskId); 995 if (entry.isCancelable()) { 996 app.getTaskClient().cancelTask(taskId); 997 app.println(INFO_TASKINFO_CMD_CANCEL_SUCCESS.get(taskId)); 998 return MenuResult.success(entry); 999 } else { 1000 app.println(ERR_TASKINFO_TASK_NOT_CANCELABLE_TASK.get(taskId)); 1001 return MenuResult.again(); 1002 } 1003 } catch (Exception e) { 1004 app.println(ERR_TASKINFO_CANCELING_TASK.get( 1005 taskId, e.getMessage())); 1006 return MenuResult.again(); 1007 } 1008 } 1009 1010 } 1011 1012}