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