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 }