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