001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2012-2016 ForgeRock AS. 016 */ 017package org.opends.server.tools.tasks; 018 019import static org.opends.messages.TaskMessages.*; 020import static org.opends.messages.ToolMessages.*; 021import static org.opends.server.util.StaticUtils.*; 022 023import static com.forgerock.opendj.cli.Utils.*; 024import static com.forgerock.opendj.cli.CommonArguments.*; 025 026import java.io.IOException; 027import java.io.PrintStream; 028import java.util.Date; 029import java.util.HashSet; 030import java.util.List; 031import java.util.Set; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.opendj.ldap.DecodeException; 035import org.opends.server.admin.client.cli.TaskScheduleArgs; 036import org.opends.server.backends.task.FailedDependencyAction; 037import org.opends.server.backends.task.TaskState; 038import org.opends.server.core.DirectoryServer; 039import org.opends.server.loggers.JDKLogging; 040import org.opends.server.tools.LDAPConnection; 041import org.opends.server.tools.LDAPConnectionException; 042import org.opends.server.types.InitializationException; 043import org.opends.server.types.LDAPException; 044import org.opends.server.types.OpenDsException; 045import org.opends.server.util.BuildVersion; 046import org.opends.server.util.cli.LDAPConnectionArgumentParser; 047 048import com.forgerock.opendj.cli.Argument; 049import com.forgerock.opendj.cli.ArgumentException; 050import com.forgerock.opendj.cli.ArgumentGroup; 051import com.forgerock.opendj.cli.BooleanArgument; 052import com.forgerock.opendj.cli.ClientException; 053import com.forgerock.opendj.cli.StringArgument; 054 055/** 056 * Base class for tools that are capable of operating either by running 057 * local within this JVM or by scheduling a task to perform the same 058 * action running within the directory server through the tasks interface. 059 */ 060public abstract class TaskTool implements TaskScheduleInformation { 061 062 /** 063 * Magic value used to indicate that the user would like to schedule 064 * this operation to run immediately as a task as opposed to running 065 * the operation in the local VM. 066 */ 067 public static final String NOW = TaskScheduleArgs.NOW; 068 069 /** 070 * The error code used by the mixed-script to know if the java 071 * arguments for the off-line mode must be used. 072 */ 073 private static final int RUN_OFFLINE = 51; 074 /** 075 * The error code used by the mixed-script to know if the java 076 * arguments for the on-line mode must be used. 077 */ 078 private static final int RUN_ONLINE = 52; 079 080 /** 081 * Number of milliseconds this utility will wait before reloading 082 * this task's entry in the directory while it is polling for status. 083 */ 084 private static final int SYNCHRONOUS_TASK_POLL_INTERVAL = 1000; 085 086 private LDAPConnectionArgumentParser argParser; 087 088 private TaskScheduleArgs taskScheduleArgs; 089 090 /** 091 * Argument used to know whether we must test if we must run in off-line mode. 092 */ 093 private BooleanArgument testIfOfflineArg; 094 095 /** This CLI is always using the administration connector with SSL. */ 096 private static final boolean alwaysSSL = true; 097 098 /** 099 * Called when this utility should perform its actions locally in this 100 * JVM. 101 * 102 * @param initializeServer indicates whether to initialize the 103 * directory server in the case of a local action 104 * @param out stream to write messages; may be null 105 * @param err stream to write messages; may be null 106 * @return int indicating the result of this action 107 */ 108 protected abstract int processLocal(boolean initializeServer, 109 PrintStream out, 110 PrintStream err); 111 112 /** 113 * Cleanup task environment after offline run. 114 * Default implementation does nothing. 115 * Tasks which initialize some static part of the DirectoryServer instance (e.g admin data local backends) must 116 * override this method to shutdown all needed component to prevent JVM environment alteration. 117 */ 118 protected void cleanup() 119 { 120 // Do nothing by default. 121 } 122 123 /** 124 * Creates an argument parser prepopulated with arguments for processing 125 * input for scheduling tasks with the task backend. 126 * 127 * @param className of this tool 128 * @param toolDescription of this tool 129 * @return LDAPConnectionArgumentParser for processing CLI input 130 */ 131 protected LDAPConnectionArgumentParser createArgParser(String className, 132 LocalizableMessage toolDescription) 133 { 134 ArgumentGroup ldapGroup = new ArgumentGroup( 135 INFO_DESCRIPTION_TASK_LDAP_ARGS.get(), 1001); 136 137 argParser = new LDAPConnectionArgumentParser(className, 138 toolDescription, false, ldapGroup, alwaysSSL); 139 140 ArgumentGroup taskGroup = new ArgumentGroup( 141 INFO_DESCRIPTION_TASK_TASK_ARGS.get(), 1000); 142 143 try { 144 StringArgument propertiesFileArgument = 145 propertiesFileArgument(); 146 argParser.addArgument(propertiesFileArgument); 147 argParser.setFilePropertiesArgument(propertiesFileArgument); 148 149 BooleanArgument noPropertiesFileArgument = 150 noPropertiesFileArgument(); 151 argParser.addArgument(noPropertiesFileArgument); 152 argParser.setNoPropertiesFileArgument(noPropertiesFileArgument); 153 154 taskScheduleArgs = new TaskScheduleArgs(); 155 156 for (Argument arg : taskScheduleArgs.getArguments()) 157 { 158 argParser.addArgument(arg, taskGroup); 159 } 160 161 testIfOfflineArg = 162 BooleanArgument.builder("testIfOffline") 163 .description(INFO_DESCRIPTION_TEST_IF_OFFLINE.get()) 164 .hidden() 165 .buildAndAddToParser(argParser); 166 } catch (ArgumentException e) { 167 // should never happen 168 } 169 170 return argParser; 171 } 172 173 /** 174 * Validates arguments related to task scheduling. This should be 175 * called after the <code>ArgumentParser.parseArguments</code> has 176 * been called. 177 * 178 * @throws ArgumentException if there is a problem with the arguments. 179 * @throws ClientException if there is a problem with one of the values provided 180 * by the user. 181 */ 182 protected void validateTaskArgs() throws ArgumentException, ClientException 183 { 184 if (isRemoteTask()) 185 { 186 taskScheduleArgs.validateArgs(); 187 } 188 else 189 { 190 // server is offline => output logs to the console 191 JDKLogging.enableConsoleLoggingForOpenDJTool(); 192 taskScheduleArgs.validateArgsIfOffline(); 193 } 194 } 195 196 /** {@inheritDoc} */ 197 @Override 198 public Date getStartDateTime() { 199 return taskScheduleArgs.getStartDateTime(); 200 } 201 202 /** {@inheritDoc} */ 203 @Override 204 public String getRecurringDateTime() { 205 return taskScheduleArgs.getRecurringDateTime(); 206 } 207 208 /** {@inheritDoc} */ 209 @Override 210 public List<String> getDependencyIds() { 211 return taskScheduleArgs.getDependencyIds(); 212 } 213 214 /** {@inheritDoc} */ 215 @Override 216 public FailedDependencyAction getFailedDependencyAction() { 217 return taskScheduleArgs.getFailedDependencyAction(); 218 } 219 220 /** {@inheritDoc} */ 221 @Override 222 public List<String> getNotifyUponCompletionEmailAddresses() { 223 return taskScheduleArgs.getNotifyUponCompletionEmailAddresses(); 224 } 225 226 /** {@inheritDoc} */ 227 @Override 228 public List<String> getNotifyUponErrorEmailAddresses() { 229 return taskScheduleArgs.getNotifyUponErrorEmailAddresses(); 230 } 231 232 /** 233 * Either invokes initiates this tool's local action or schedule this 234 * tool using the tasks interface based on user input. 235 * 236 * @param argParser used to parse user arguments 237 * @param initializeServer indicates whether to initialize the 238 * directory server in the case of a local action 239 * @param out stream to write messages; may be null 240 * @param err stream to write messages; may be null 241 * @return int indicating the result of this action 242 */ 243 protected int process(LDAPConnectionArgumentParser argParser, 244 boolean initializeServer, 245 PrintStream out, PrintStream err) { 246 if (testIfOffline()) 247 { 248 if (!isRemoteTask()) 249 { 250 return RUN_OFFLINE; 251 } 252 else 253 { 254 return RUN_ONLINE; 255 } 256 } 257 258 if (!isRemoteTask()) 259 { 260 try 261 { 262 return processLocal(initializeServer, out, err); 263 } 264 finally 265 { 266 if (initializeServer) 267 { 268 cleanup(); 269 } 270 } 271 } 272 273 if (initializeServer) 274 { 275 try 276 { 277 DirectoryServer.bootstrapClient(); 278 DirectoryServer.initializeJMX(); 279 } 280 catch (Exception e) 281 { 282 printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getExceptionMessage(e))); 283 return 1; 284 } 285 } 286 287 LDAPConnection conn = null; 288 try { 289 conn = argParser.connect(out, err); 290 TaskClient tc = new TaskClient(conn); 291 TaskEntry taskEntry = tc.schedule(this); 292 LocalizableMessage startTime = taskEntry.getScheduledStartTime(); 293 if (taskEntry.getTaskState() == TaskState.RECURRING) { 294 printWrappedText(out, INFO_TASK_TOOL_RECURRING_TASK_SCHEDULED.get(taskEntry.getType(), taskEntry.getId())); 295 } else if (startTime == null || startTime.length() == 0) { 296 printWrappedText(out, INFO_TASK_TOOL_TASK_SCHEDULED_NOW.get(taskEntry.getType(), taskEntry.getId())); 297 } else { 298 printWrappedText(out, INFO_TASK_TOOL_TASK_SCHEDULED_FUTURE.get( 299 taskEntry.getType(), taskEntry.getId(), taskEntry.getScheduledStartTime())); 300 } 301 if (!taskScheduleArgs.startArg.isPresent()) { 302 303 // Poll the task printing log messages until finished 304 String taskId = taskEntry.getId(); 305 Set<LocalizableMessage> printedLogMessages = new HashSet<>(); 306 do { 307 taskEntry = tc.getTaskEntry(taskId); 308 List<LocalizableMessage> logs = taskEntry.getLogMessages(); 309 for (LocalizableMessage log : logs) { 310 if (printedLogMessages.add(log)) { 311 out.println(log); 312 } 313 } 314 315 try { 316 Thread.sleep(SYNCHRONOUS_TASK_POLL_INTERVAL); 317 } catch (InterruptedException e) { 318 // ignore 319 } 320 321 } while (!taskEntry.isDone()); 322 if (TaskState.isSuccessful(taskEntry.getTaskState())) { 323 if (taskEntry.getTaskState() != TaskState.RECURRING) { 324 printWrappedText(out, INFO_TASK_TOOL_TASK_SUCESSFULL.get(taskEntry.getType(), taskEntry.getId())); 325 } 326 return 0; 327 } else { 328 printWrappedText(out, INFO_TASK_TOOL_TASK_NOT_SUCESSFULL.get(taskEntry.getType(), taskEntry.getId())); 329 return 1; 330 } 331 } 332 return 0; 333 } catch (LDAPConnectionException e) { 334 if (isWrongPortException(e, 335 Integer.valueOf(argParser.getArguments().getPort()))) 336 { 337 printWrappedText(err, ERR_TASK_LDAP_FAILED_TO_CONNECT_WRONG_PORT.get( 338 argParser.getArguments().getHostName(), argParser.getArguments().getPort())); 339 } else { 340 printWrappedText(err, ERR_TASK_TOOL_START_TIME_NO_LDAP.get(e.getMessage())); 341 } 342 return 1; 343 } catch (DecodeException ae) { 344 printWrappedText(err, ERR_TASK_TOOL_DECODE_ERROR.get(ae.getMessage())); 345 return 1; 346 } catch (IOException ioe) { 347 printWrappedText(err, ERR_TASK_TOOL_IO_ERROR.get(ioe)); 348 return 1; 349 } catch (LDAPException le) { 350 printWrappedText(err, ERR_TASK_TOOL_LDAP_ERROR.get(le.getMessage())); 351 return 1; 352 } catch (OpenDsException e) { 353 printWrappedText(err, e.getMessageObject()); 354 return 1; 355 } catch (ArgumentException e) { 356 argParser.displayMessageAndUsageReference(err, e.getMessageObject()); 357 return 1; 358 } 359 finally 360 { 361 if (conn != null) 362 { 363 try 364 { 365 conn.close(null); 366 } 367 catch (Throwable t) 368 { 369 // Ignore. 370 } 371 } 372 } 373 } 374 375 private boolean isRemoteTask() { 376 return argParser.connectionArgumentsPresent(); 377 } 378 379 /** 380 * Returns {@code true} if the provided exception was caused by trying to 381 * connect to the wrong port and {@code false} otherwise. 382 * @param t the exception to be analyzed. 383 * @param port the port to which we tried to connect. 384 * @return {@code true} if the provided exception was caused by trying to 385 * connect to the wrong port and {@code false} otherwise. 386 */ 387 private boolean isWrongPortException(Throwable t, int port) 388 { 389 boolean isWrongPortException = false; 390 boolean isDefaultClearPort = (port - 389) % 1000 == 0; 391 while (t != null && isDefaultClearPort) 392 { 393 isWrongPortException = t instanceof java.net.SocketTimeoutException; 394 if (!isWrongPortException) 395 { 396 t = t.getCause(); 397 } 398 else 399 { 400 break; 401 } 402 } 403 return isWrongPortException; 404 } 405 406 407 /** 408 * Indicates whether we must return if the command must be run in off-line 409 * mode. 410 * @return <CODE>true</CODE> if we must return if the command must be run in 411 * off-line mode and <CODE>false</CODE> otherwise. 412 */ 413 public boolean testIfOffline() 414 { 415 boolean returnValue = false; 416 if (testIfOfflineArg != null) 417 { 418 returnValue = testIfOfflineArg.isPresent(); 419 } 420 return returnValue; 421 } 422 423 /** 424 * Checks that binary version and instance version are the same. 425 * 426 * @throws InitializationException 427 * If versions mismatch 428 */ 429 protected void checkVersion() throws InitializationException 430 { 431 // FIXME Do not perform this check if the tool is use in remote mode (see OPENDJ-1166) 432 BuildVersion.checkVersionMismatch(); 433 } 434}