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