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 }