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 2010 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.tools.tasks; 028 029import static org.opends.messages.AdminToolMessages.*; 030import static org.opends.messages.ToolMessages.*; 031 032import java.text.ParseException; 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.Date; 036import java.util.HashSet; 037import java.util.List; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.opends.quicksetup.util.PlainTextProgressMessageFormatter; 041import org.opends.quicksetup.util.ProgressMessageFormatter; 042import org.opends.server.admin.client.cli.TaskScheduleArgs; 043import org.opends.server.backends.task.FailedDependencyAction; 044import org.opends.server.backends.task.RecurringTask; 045import org.opends.server.types.DirectoryException; 046import org.opends.server.util.StaticUtils; 047 048import com.forgerock.opendj.cli.ClientException; 049import com.forgerock.opendj.cli.ReturnCode; 050 051import com.forgerock.opendj.cli.ConsoleApplication; 052import com.forgerock.opendj.cli.MenuBuilder; 053import com.forgerock.opendj.cli.MenuResult; 054 055/** 056 * A class that is in charge of interacting with the user to ask about 057 * scheduling options for a task. 058 * <br> 059 * It takes as argument an {@link TaskScheduleArgs} object with the arguments 060 * provided by the user and updates the provided {@link TaskScheduleUserData} 061 * with the information provided by the user. 062 * 063 */ 064public class TaskScheduleInteraction 065{ 066 private boolean headerDisplayed; 067 private final TaskScheduleUserData uData; 068 private final TaskScheduleArgs args; 069 private final ConsoleApplication app; 070 private final LocalizableMessage taskName; 071 private List<? extends TaskEntry> taskEntries = 072 Collections.emptyList(); 073 private ProgressMessageFormatter formatter = 074 new PlainTextProgressMessageFormatter(); 075 076 /** 077 * The enumeration used by the menu displayed to ask the user about the 078 * type of scheduling (if any) to be done. 079 * 080 */ 081 private enum ScheduleOption { 082 RUN_NOW(INFO_RUN_TASK_NOW.get()), 083 RUN_LATER(INFO_RUN_TASK_LATER.get()), 084 SCHEDULE_TASK(INFO_SCHEDULE_TASK.get()); 085 086 private LocalizableMessage prompt; 087 private ScheduleOption(LocalizableMessage prompt) 088 { 089 this.prompt = prompt; 090 } 091 LocalizableMessage getPrompt() 092 { 093 return prompt; 094 } 095 096 /** 097 * The default option to be proposed to the user. 098 * @return the default option to be proposed to the user. 099 */ 100 public static ScheduleOption defaultValue() 101 { 102 return RUN_NOW; 103 } 104 } 105 106 /** 107 * Default constructor. 108 * @param uData the task schedule user data. 109 * @param args the object with the arguments provided by the user. The code 110 * assumes that the arguments have already been parsed. 111 * @param app the console application object used to prompt for data. 112 * @param taskName the name of the task to be used in the prompt messages. 113 */ 114 public TaskScheduleInteraction(TaskScheduleUserData uData, 115 TaskScheduleArgs args, ConsoleApplication app, 116 LocalizableMessage taskName) 117 { 118 this.uData = uData; 119 this.args = args; 120 this.app = app; 121 this.taskName = taskName; 122 } 123 124 /** 125 * Executes the interaction with the user. 126 * @throws ClientException if there is an error prompting the user. 127 */ 128 public void run() throws ClientException 129 { 130 headerDisplayed = false; 131 132 runStartNowOrSchedule(); 133 134 runCompletionNotification(); 135 136 runErrorNotification(); 137 138 runDependency(); 139 140 if (!uData.getDependencyIds().isEmpty()) 141 { 142 runFailedDependencyAction(); 143 } 144 } 145 146 /** 147 * Returns the task entries that are defined in the server. These are 148 * used to prompt the user about the task dependencies. 149 * @return the task entries that are defined in the server. 150 */ 151 public List<? extends TaskEntry> getTaskEntries() 152 { 153 return taskEntries; 154 } 155 156 /** 157 * Sets the task entries that are defined in the server. These are 158 * used to prompt the user about the task dependencies. If no task entries 159 * are provided, the user will not be prompted for task dependencies. 160 * @param taskEntries the task entries that are defined in the server. 161 */ 162 public void setTaskEntries(List<? extends TaskEntry> taskEntries) 163 { 164 this.taskEntries = taskEntries; 165 } 166 167 /** 168 * Returns the formatter that is used to generate messages. 169 * @return the formatter that is used to generate messages. 170 */ 171 public ProgressMessageFormatter getFormatter() 172 { 173 return formatter; 174 } 175 176 /** 177 * Sets the formatter that is used to generate messages. 178 * @param formatter the formatter that is used to generate messages. 179 */ 180 public void setFormatter(ProgressMessageFormatter formatter) 181 { 182 this.formatter = formatter; 183 } 184 185 private void runFailedDependencyAction() throws ClientException 186 { 187 if (args.dependencyArg.isPresent()) 188 { 189 uData.setFailedDependencyAction(args.getFailedDependencyAction()); 190 } 191 else 192 { 193 askForFailedDependencyAction(); 194 } 195 } 196 197 private void askForFailedDependencyAction() throws ClientException 198 { 199 checkHeaderDisplay(); 200 201 MenuBuilder<FailedDependencyAction> builder = new MenuBuilder<>(app); 202 builder.setPrompt(INFO_TASK_FAILED_DEPENDENCY_ACTION_PROMPT.get()); 203 builder.addCancelOption(false); 204 for (FailedDependencyAction choice : FailedDependencyAction.values()) 205 { 206 MenuResult<FailedDependencyAction> result = MenuResult.success(choice); 207 208 builder.addNumberedOption(choice.getDisplayName(), result); 209 210 if (choice.equals(FailedDependencyAction.defaultValue())) 211 { 212 builder.setDefault(choice.getDisplayName(), result); 213 } 214 } 215 MenuResult<FailedDependencyAction> m = builder.toMenu().run(); 216 if (m.isSuccess()) 217 { 218 uData.setFailedDependencyAction(m.getValue()); 219 } 220 else 221 { 222 throw new ClientException(ReturnCode.ERROR_UNEXPECTED, LocalizableMessage.EMPTY); 223 } 224 } 225 226 private void runDependency() throws ClientException 227 { 228 if (args.dependencyArg.isPresent()) 229 { 230 uData.setDependencyIds(args.getDependencyIds()); 231 } 232 else if (!taskEntries.isEmpty()) 233 { 234 askForDependency(); 235 } 236 } 237 238 private void askForDependency() throws ClientException 239 { 240 checkHeaderDisplay(); 241 242 boolean hasDependencies = 243 app.confirmAction(INFO_TASK_HAS_DEPENDENCIES_PROMPT.get(), false); 244 if (hasDependencies) 245 { 246 printAvailableDependencyTaskMessage(); 247 HashSet<String> dependencies = new HashSet<>(); 248 while (true) 249 { 250 String dependencyID = 251 app.readLineOfInput(INFO_TASK_DEPENDENCIES_PROMPT.get()); 252 if (dependencyID != null && !dependencyID.isEmpty()) 253 { 254 if (isTaskIDDefined(dependencyID)) 255 { 256 dependencies.add(dependencyID); 257 } 258 else 259 { 260 printTaskIDNotDefinedMessage(dependencyID); 261 } 262 } 263 else 264 { 265 break; 266 } 267 } 268 uData.setDependencyIds(new ArrayList<String>(dependencies)); 269 } 270 else 271 { 272 List<String> empty = Collections.emptyList(); 273 uData.setDependencyIds(empty); 274 } 275 } 276 277 private void printAvailableDependencyTaskMessage() 278 { 279 StringBuilder sb = new StringBuilder(); 280 String separator = formatter.getLineBreak().toString() + formatter.getTab(); 281 for (TaskEntry entry : taskEntries) 282 { 283 sb.append(separator); 284 sb.append(entry.getId()); 285 } 286 app.println(); 287 app.print(INFO_AVAILABLE_DEFINED_TASKS.get(sb)); 288 app.println(); 289 app.println(); 290 291 } 292 293 private void printTaskIDNotDefinedMessage(String dependencyID) 294 { 295 app.println(); 296 app.println(ERR_DEPENDENCY_TASK_NOT_DEFINED.get(dependencyID)); 297 } 298 299 private boolean isTaskIDDefined(String dependencyID) 300 { 301 boolean taskIDDefined = false; 302 for (TaskEntry entry : taskEntries) 303 { 304 if (dependencyID.equalsIgnoreCase(entry.getId())) 305 { 306 taskIDDefined = true; 307 break; 308 } 309 } 310 return taskIDDefined; 311 } 312 313 private void runErrorNotification() throws ClientException 314 { 315 if (args.errorNotificationArg.isPresent()) 316 { 317 uData.setNotifyUponErrorEmailAddresses( 318 args.getNotifyUponErrorEmailAddresses()); 319 } 320 else 321 { 322 askForErrorNotification(); 323 } 324 } 325 326 private void askForErrorNotification() throws ClientException 327 { 328 List<String> addresses = 329 askForEmailNotification(INFO_HAS_ERROR_NOTIFICATION_PROMPT.get(), 330 INFO_ERROR_NOTIFICATION_PROMPT.get()); 331 uData.setNotifyUponErrorEmailAddresses(addresses); 332 } 333 334 private List<String> askForEmailNotification(LocalizableMessage hasNotificationPrompt, 335 LocalizableMessage emailAddressPrompt) throws ClientException 336 { 337 checkHeaderDisplay(); 338 339 List<String> addresses = new ArrayList<>(); 340 boolean hasNotification = 341 app.confirmAction(hasNotificationPrompt, false); 342 if (hasNotification) 343 { 344 HashSet<String> set = new HashSet<>(); 345 while (true) 346 { 347 String address = app.readLineOfInput(emailAddressPrompt); 348 if (address == null || address.isEmpty()) 349 { 350 break; 351 } 352 if (!StaticUtils.isEmailAddress(address)) { 353 app.println(ERR_INVALID_EMAIL_ADDRESS.get(address)); 354 } 355 else 356 { 357 set.add(address); 358 } 359 } 360 addresses.addAll(set); 361 } 362 return addresses; 363 } 364 365 private void runCompletionNotification() throws ClientException 366 { 367 if (args.completionNotificationArg.isPresent()) 368 { 369 uData.setNotifyUponCompletionEmailAddresses( 370 args.getNotifyUponCompletionEmailAddresses()); 371 } 372 else 373 { 374 askForCompletionNotification(); 375 } 376 } 377 378 private void askForCompletionNotification() throws ClientException 379 { 380 List<String> addresses = 381 askForEmailNotification(INFO_HAS_COMPLETION_NOTIFICATION_PROMPT.get(), 382 INFO_COMPLETION_NOTIFICATION_PROMPT.get()); 383 uData.setNotifyUponCompletionEmailAddresses(addresses); 384 } 385 386 private void runStartNowOrSchedule() throws ClientException 387 { 388 if (args.startArg.isPresent()) 389 { 390 uData.setStartDate(args.getStartDateTime()); 391 uData.setStartNow(args.isStartNow()); 392 } 393 if (args.recurringArg.isPresent()) 394 { 395 uData.setRecurringDateTime(args.getRecurringDateTime()); 396 uData.setStartNow(false); 397 } 398 if (!args.startArg.isPresent() && 399 !args.recurringArg.isPresent()) 400 { 401 askToStartNowOrSchedule(); 402 } 403 } 404 405 private void askToStartNowOrSchedule() throws ClientException 406 { 407 checkHeaderDisplay(); 408 409 MenuBuilder<ScheduleOption> builder = new MenuBuilder<>(app); 410 builder.setPrompt(INFO_TASK_SCHEDULE_PROMPT.get(taskName)); 411 builder.addCancelOption(false); 412 for (ScheduleOption choice : ScheduleOption.values()) 413 { 414 MenuResult<ScheduleOption> result = MenuResult.success(choice); 415 if (choice == ScheduleOption.defaultValue()) 416 { 417 builder.setDefault(choice.getPrompt(), result); 418 } 419 builder.addNumberedOption(choice.getPrompt(), result); 420 } 421 MenuResult<ScheduleOption> m = builder.toMenu().run(); 422 if (m.isSuccess()) 423 { 424 switch (m.getValue()) 425 { 426 case RUN_NOW: 427 uData.setStartNow(true); 428 break; 429 case RUN_LATER: 430 uData.setStartNow(false); 431 askForStartDate(); 432 break; 433 case SCHEDULE_TASK: 434 uData.setStartNow(false); 435 askForTaskSchedule(); 436 break; 437 } 438 } 439 else 440 { 441 throw new ClientException(ReturnCode.ERROR_UNEXPECTED, LocalizableMessage.EMPTY); 442 } 443 } 444 445 private void askForStartDate() throws ClientException 446 { 447 checkHeaderDisplay(); 448 449 Date startDate = null; 450 while (startDate == null) 451 { 452 String sDate = app.readInput(INFO_TASK_START_DATE_PROMPT.get(), null); 453 try { 454 startDate = StaticUtils.parseDateTimeString(sDate); 455 // Check that the provided date is not previous to the current date. 456 Date currentDate = new Date(System.currentTimeMillis()); 457 if (currentDate.after(startDate)) 458 { 459 app.print(ERR_START_DATETIME_ALREADY_PASSED.get(sDate)); 460 app.println(); 461 app.println(); 462 startDate = null; 463 } 464 } catch (ParseException pe) { 465 app.println(ERR_START_DATETIME_FORMAT.get()); 466 app.println(); 467 } 468 } 469 uData.setStartDate(startDate); 470 } 471 472 private void askForTaskSchedule() throws ClientException 473 { 474 checkHeaderDisplay(); 475 476 String schedule = null; 477 478 while (schedule == null) 479 { 480 schedule = app.readInput(INFO_TASK_RECURRING_SCHEDULE_PROMPT.get(), 481 null); 482 try 483 { 484 RecurringTask.parseTaskTab(schedule); 485 app.println(); 486 } 487 catch (DirectoryException de) 488 { 489 schedule = null; 490 app.println(ERR_RECURRING_SCHEDULE_FORMAT_ERROR.get( 491 de.getMessageObject())); 492 app.println(); 493 } 494 } 495 uData.setRecurringDateTime(schedule); 496 } 497 498 private void checkHeaderDisplay() 499 { 500 if (!headerDisplayed) 501 { 502 app.println(); 503 app.print(INFO_TASK_SCHEDULE_PROMPT_HEADER.get()); 504 app.println(); 505 headerDisplayed = true; 506 } 507 app.println(); 508 509 } 510}