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 2007-2008 Sun Microsystems, Inc.
026     */
027    
028    package org.opends.server.tools.tasks;
029    
030    import org.opends.server.util.args.BooleanArgument;
031    import org.opends.server.util.args.LDAPConnectionArgumentParser;
032    import org.opends.server.util.args.ArgumentException;
033    import org.opends.server.util.args.StringArgument;
034    import org.opends.server.util.args.ArgumentGroup;
035    import static org.opends.server.util.StaticUtils.wrapText;
036    import static org.opends.server.util.StaticUtils.getExceptionMessage;
037    import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH;
038    import org.opends.server.util.StaticUtils;
039    import org.opends.server.protocols.asn1.ASN1Exception;
040    import org.opends.server.tools.LDAPConnection;
041    import org.opends.server.tools.LDAPConnectionException;
042    import static org.opends.server.tools.ToolConstants.*;
043    
044    import org.opends.server.types.LDAPException;
045    import org.opends.server.types.OpenDsException;
046    import org.opends.server.core.DirectoryServer;
047    import org.opends.server.backends.task.TaskState;
048    import org.opends.server.backends.task.FailedDependencyAction;
049    import org.opends.messages.Message;
050    import static org.opends.messages.ToolMessages.*;
051    
052    import java.io.PrintStream;
053    import java.text.ParseException;
054    import java.util.Date;
055    import java.util.Set;
056    import java.util.HashSet;
057    import java.util.List;
058    import java.util.LinkedList;
059    import java.util.EnumSet;
060    import java.util.Collections;
061    import java.io.IOException;
062    
063    /**
064     * Base class for tools that are capable of operating either by running
065     * local within this JVM or by scheduling a task to perform the same
066     * action running within the directory server through the tasks interface.
067     */
068    public abstract class TaskTool implements TaskScheduleInformation {
069    
070      /**
071       * Magic value used to indicate that the user would like to schedule
072       * this operation to run immediately as a task as opposed to running
073       * the operation in the local VM.
074       */
075      public static final String NOW = "0";
076    
077      private static final int RUN_OFFLINE = 51;
078      private static final int RUN_ONLINE = 52;
079    
080      // Number of milliseconds this utility will wait before reloading
081      // this task's entry in the directory while it is polling for status
082      private static final int SYNCHRONOUS_TASK_POLL_INTERVAL = 1000;
083    
084      LDAPConnectionArgumentParser argParser;
085    
086      // Argument for describing the task's start time
087      StringArgument startArg;
088    
089      // Argument for specifying completion notifications
090      StringArgument completionNotificationArg;
091    
092      // Argument for specifying error notifications
093      StringArgument errorNotificationArg;
094    
095      // Argument for specifying dependency
096      StringArgument dependencyArg;
097    
098      // Argument for specifying a failed dependency action
099      StringArgument failedDependencyActionArg;
100    
101      // Client for interacting with the task backend
102      TaskClient taskClient;
103    
104      // Argument used to know whether we must test if we must run in offline
105      // mode.
106      BooleanArgument testIfOfflineArg;
107    
108      /**
109       * Called when this utility should perform its actions locally in this
110       * JVM.
111       *
112       * @param initializeServer indicates whether or not to initialize the
113       *        directory server in the case of a local action
114       * @param out stream to write messages; may be null
115       * @param err stream to write messages; may be null
116       * @return int indicating the result of this action
117       */
118      abstract protected int processLocal(boolean initializeServer,
119                                          PrintStream out,
120                                          PrintStream err);
121    
122      /**
123       * Creates an argument parser prepopulated with arguments for processing
124       * input for scheduling tasks with the task backend.
125       *
126       * @param className of this tool
127       * @param toolDescription of this tool
128       * @return LDAPConnectionArgumentParser for processing CLI input
129       */
130      protected LDAPConnectionArgumentParser createArgParser(String className,
131          Message toolDescription)
132        {
133        ArgumentGroup ldapGroup = new ArgumentGroup(
134                INFO_DESCRIPTION_TASK_LDAP_ARGS.get(), 1001);
135    
136        argParser = new LDAPConnectionArgumentParser(className,
137                toolDescription, false, ldapGroup);
138    
139        ArgumentGroup taskGroup = new ArgumentGroup(
140                INFO_DESCRIPTION_TASK_TASK_ARGS.get(), 1000);
141    
142        try {
143          StringArgument propertiesFileArgument = new StringArgument(
144              "propertiesFilePath",
145              null, OPTION_LONG_PROP_FILE_PATH,
146              false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
147              INFO_DESCRIPTION_PROP_FILE_PATH.get());
148          argParser.addArgument(propertiesFileArgument);
149          argParser.setFilePropertiesArgument(propertiesFileArgument);
150    
151         BooleanArgument noPropertiesFileArgument = new BooleanArgument(
152              "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
153              INFO_DESCRIPTION_NO_PROP_FILE.get());
154         argParser.addArgument(noPropertiesFileArgument);
155         argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
156    
157          startArg = new StringArgument(
158                  OPTION_LONG_START_DATETIME,
159                  OPTION_SHORT_START_DATETIME,
160                  OPTION_LONG_START_DATETIME, false, false,
161                  true, INFO_START_DATETIME_PLACEHOLDER.get(),
162                  null, null,
163                  INFO_DESCRIPTION_START_DATETIME.get());
164          argParser.addArgument(startArg, taskGroup);
165    
166          completionNotificationArg = new StringArgument(
167                  OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
168                  OPTION_SHORT_COMPLETION_NOTIFICATION_EMAIL,
169                  OPTION_LONG_COMPLETION_NOTIFICATION_EMAIL,
170                  false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
171                  null, null, INFO_DESCRIPTION_TASK_COMPLETION_NOTIFICATION.get());
172          argParser.addArgument(completionNotificationArg, taskGroup);
173    
174          errorNotificationArg = new StringArgument(
175                  OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
176                  OPTION_SHORT_ERROR_NOTIFICATION_EMAIL,
177                  OPTION_LONG_ERROR_NOTIFICATION_EMAIL,
178                  false, true, true, INFO_EMAIL_ADDRESS_PLACEHOLDER.get(),
179                  null, null, INFO_DESCRIPTION_TASK_ERROR_NOTIFICATION.get());
180          argParser.addArgument(errorNotificationArg, taskGroup);
181    
182          dependencyArg = new StringArgument(
183                  OPTION_LONG_DEPENDENCY,
184                  OPTION_SHORT_DEPENDENCY,
185                  OPTION_LONG_DEPENDENCY,
186                  false, true, true, INFO_TASK_ID_PLACEHOLDER.get(),
187                  null, null, INFO_DESCRIPTION_TASK_DEPENDENCY_ID.get());
188          argParser.addArgument(dependencyArg, taskGroup);
189    
190          Set fdaValSet = EnumSet.allOf(FailedDependencyAction.class);
191          failedDependencyActionArg = new StringArgument(
192                  OPTION_LONG_FAILED_DEPENDENCY_ACTION,
193                  OPTION_SHORT_FAILED_DEPENDENCY_ACTION,
194                  OPTION_LONG_FAILED_DEPENDENCY_ACTION,
195                  false, true, true, INFO_ACTION_PLACEHOLDER.get(),
196                  null, null, INFO_DESCRIPTION_TASK_FAILED_DEPENDENCY_ACTION.get(
197                    StaticUtils.collectionToString(fdaValSet, ","),
198                    FailedDependencyAction.defaultValue().name()));
199          argParser.addArgument(failedDependencyActionArg, taskGroup);
200    
201          testIfOfflineArg = new BooleanArgument(
202              "testIfOffline", null, "testIfOffline",
203              INFO_DESCRIPTION_TEST_IF_OFFLINE.get());
204          testIfOfflineArg.setHidden(true);
205          argParser.addArgument(testIfOfflineArg);
206    
207        } catch (ArgumentException e) {
208          // should never happen
209        }
210    
211        return argParser;
212      }
213    
214      /**
215       * Validates arguments related to task scheduling.  This should be
216       * called after the <code>ArgumentParser.parseArguments</code> has
217       * been called.
218       *
219       * @throws ArgumentException if there is a problem with the arguments
220       */
221      protected void validateTaskArgs() throws ArgumentException {
222        if (startArg.isPresent() && !NOW.equals(startArg.getValue())) {
223          try {
224            StaticUtils.parseDateTimeString(startArg.getValue());
225          } catch (ParseException pe) {
226            throw new ArgumentException(ERR_START_DATETIME_FORMAT.get());
227          }
228        }
229    
230        if (!processAsTask() && completionNotificationArg.isPresent()) {
231          throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
232                  completionNotificationArg.getLongIdentifier()));
233        }
234    
235        if (!processAsTask() && errorNotificationArg.isPresent()) {
236          throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
237                  errorNotificationArg.getLongIdentifier()));
238        }
239    
240        if (!processAsTask() && dependencyArg.isPresent()) {
241          throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
242                  dependencyArg.getLongIdentifier()));
243        }
244    
245        if (!processAsTask() && failedDependencyActionArg.isPresent()) {
246          throw new ArgumentException(ERR_TASKTOOL_OPTIONS_FOR_TASK_ONLY.get(
247                  failedDependencyActionArg.getLongIdentifier()));
248        }
249    
250        if (completionNotificationArg.isPresent()) {
251          LinkedList<String> addrs = completionNotificationArg.getValues();
252          for (String addr : addrs) {
253            if (!StaticUtils.isEmailAddress(addr)) {
254              throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
255                      addr, completionNotificationArg.getLongIdentifier()));
256            }
257          }
258        }
259    
260        if (errorNotificationArg.isPresent()) {
261          LinkedList<String> addrs = errorNotificationArg.getValues();
262          for (String addr : addrs) {
263            if (!StaticUtils.isEmailAddress(addr)) {
264              throw new ArgumentException(ERR_TASKTOOL_INVALID_EMAIL_ADDRESS.get(
265                      addr, errorNotificationArg.getLongIdentifier()));
266            }
267          }
268        }
269    
270        if (failedDependencyActionArg.isPresent()) {
271    
272          if (!dependencyArg.isPresent()) {
273            throw new ArgumentException(ERR_TASKTOOL_FDA_WITH_NO_DEPENDENCY.get());
274          }
275    
276          String fda = failedDependencyActionArg.getValue();
277          if (null == FailedDependencyAction.fromString(fda)) {
278            Set fdaValSet = EnumSet.allOf(FailedDependencyAction.class);
279            throw new ArgumentException(ERR_TASKTOOL_INVALID_FDA.get(fda,
280                            StaticUtils.collectionToString(fdaValSet, ",")));
281          }
282        }
283      }
284    
285      /**
286       * {@inheritDoc}
287       */
288      public Date getStartDateTime() {
289        Date start = null;
290    
291        // If the start time arg is present parse its value
292        if (startArg != null && startArg.isPresent()) {
293          if (NOW.equals(startArg.getValue())) {
294            start = new Date();
295          } else {
296            try {
297              start = StaticUtils.parseDateTimeString(startArg.getValue());
298            } catch (ParseException pe) {
299              // ignore; validated in validateTaskArgs()
300            }
301          }
302        }
303        return start;
304      }
305    
306      /**
307       * {@inheritDoc}
308       */
309      public List<String> getDependencyIds() {
310        if (dependencyArg.isPresent()) {
311          return dependencyArg.getValues();
312        } else {
313          return Collections.emptyList();
314        }
315      }
316    
317      /**
318       * {@inheritDoc}
319       */
320      public FailedDependencyAction getFailedDependencyAction() {
321        FailedDependencyAction fda = null;
322        if (failedDependencyActionArg.isPresent()) {
323          String fdaString = failedDependencyActionArg.getValue();
324          fda = FailedDependencyAction.fromString(fdaString);
325        }
326        return fda;
327      }
328    
329      /**
330       * {@inheritDoc}
331       */
332      public List<String> getNotifyUponCompletionEmailAddresses() {
333        if (completionNotificationArg.isPresent()) {
334          return completionNotificationArg.getValues();
335        } else {
336          return Collections.emptyList();
337        }
338      }
339    
340      /**
341       * {@inheritDoc}
342       */
343      public List<String> getNotifyUponErrorEmailAddresses() {
344        if (errorNotificationArg.isPresent()) {
345          return errorNotificationArg.getValues();
346        } else {
347          return Collections.emptyList();
348        }
349      }
350    
351      /**
352       * Either invokes initiates this tool's local action or schedule this
353       * tool using the tasks interface based on user input.
354       *
355       * @param argParser used to parse user arguments
356       * @param initializeServer indicates whether or not to initialize the
357       *        directory server in the case of a local action
358       * @param out stream to write messages; may be null
359       * @param err stream to write messages; may be null
360       * @return int indicating the result of this action
361       */
362      protected int process(LDAPConnectionArgumentParser argParser,
363                            boolean initializeServer,
364                            PrintStream out, PrintStream err) {
365        int ret;
366    
367        if (testIfOffline())
368        {
369          if (!processAsTask())
370          {
371            return RUN_OFFLINE;
372          }
373          else
374          {
375            return RUN_ONLINE;
376          }
377        }
378    
379        if (processAsTask())
380        {
381          if (initializeServer)
382          {
383            try
384            {
385              DirectoryServer.bootstrapClient();
386              DirectoryServer.initializeJMX();
387            }
388            catch (Exception e)
389            {
390              Message message = ERR_SERVER_BOOTSTRAP_ERROR.get(
391                      getExceptionMessage(e));
392              err.println(wrapText(message, MAX_LINE_WIDTH));
393              return 1;
394            }
395          }
396    
397          try {
398            LDAPConnection conn = argParser.connect(out, err);
399            TaskClient tc = new TaskClient(conn);
400            TaskEntry taskEntry = tc.schedule(this);
401            Message startTime = taskEntry.getScheduledStartTime();
402            if (startTime == null || startTime.length() == 0) {
403              out.println(
404                      wrapText(INFO_TASK_TOOL_TASK_SCHEDULED_NOW.get(
405                              taskEntry.getType(),
406                              taskEntry.getId()),
407                      MAX_LINE_WIDTH));
408    
409            } else {
410              out.println(
411                      wrapText(INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE.get(
412                              taskEntry.getType(),
413                              taskEntry.getId(),
414                              taskEntry.getScheduledStartTime()),
415                      MAX_LINE_WIDTH));
416            }
417            if (!startArg.isPresent()) {
418    
419              // Poll the task printing log messages until finished
420              String taskId = taskEntry.getId();
421              Set<Message> printedLogMessages = new HashSet<Message>();
422              do {
423                taskEntry = tc.getTaskEntry(taskId);
424                List<Message> logs = taskEntry.getLogMessages();
425                for (Message log : logs) {
426                  if (!printedLogMessages.contains(log)) {
427                    printedLogMessages.add(log);
428                    out.println(log);
429                  }
430                }
431    
432                try {
433                  Thread.sleep(SYNCHRONOUS_TASK_POLL_INTERVAL);
434                } catch (InterruptedException e) {
435                  // ignore
436                }
437    
438              } while (!taskEntry.isDone());
439              if (TaskState.isSuccessful(taskEntry.getTaskState())) {
440                out.println(
441                    wrapText(INFO_TASK_TOOL_TASK_SUCESSFULL.get(
442                            taskEntry.getType(),
443                            taskEntry.getId()),
444                    MAX_LINE_WIDTH));
445    
446                return 0;
447              } else {
448                out.println(
449                    wrapText(INFO_TASK_TOOL_TASK_NOT_SUCESSFULL.get(
450                            taskEntry.getType(),
451                            taskEntry.getId()),
452                    MAX_LINE_WIDTH));
453                return 1;
454              }
455            }
456            ret = 0;
457          } catch (LDAPConnectionException e) {
458            Message message = ERR_TASK_TOOL_START_TIME_NO_LDAP.get(e.getMessage());
459            if (err != null) err.println(wrapText(message, MAX_LINE_WIDTH));
460            ret = 1;
461          } catch (IOException ioe) {
462            Message message = ERR_TASK_TOOL_IO_ERROR.get(String.valueOf(ioe));
463            if (err != null) err.println(wrapText(message, MAX_LINE_WIDTH));
464            ret = 1;
465          } catch (ASN1Exception ae) {
466            Message message = ERR_TASK_TOOL_DECODE_ERROR.get(ae.getMessage());
467            if (err != null) err.println(wrapText(message, MAX_LINE_WIDTH));
468            ret = 1;
469          } catch (LDAPException le) {
470            Message message = ERR_TASK_TOOL_DECODE_ERROR.get(le.getMessage());
471            if (err != null) err.println(wrapText(message, MAX_LINE_WIDTH));
472            ret = 1;
473          } catch (OpenDsException e) {
474            Message message = e.getMessageObject();
475            if (err != null) err.println(wrapText(message, MAX_LINE_WIDTH));
476            ret = 1;
477          }
478        } else {
479          ret = processLocal(initializeServer, out, err);
480        }
481        return ret;
482      }
483    
484      private boolean processAsTask() {
485        return argParser.connectionArgumentsPresent();
486      }
487    
488      /**
489       * Indicates whether we must return if the command must be run in offline
490       * mode.
491       * @return <CODE>true</CODE> if we must return if the command must be run in
492       * offline mode and <CODE>false</CODE> otherwise.
493       */
494      private boolean testIfOffline()
495      {
496        boolean returnValue = false;
497        if (testIfOfflineArg != null)
498        {
499          returnValue = testIfOfflineArg.isPresent();
500        }
501        return returnValue;
502      }
503    }