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 2009-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2015 ForgeRock AS. 016 */ 017package org.opends.server.tools.tasks; 018 019import static org.forgerock.opendj.ldap.ResultCode.*; 020import static org.opends.messages.ToolMessages.*; 021import static org.opends.server.config.ConfigConstants.*; 022 023import java.io.IOException; 024import java.text.SimpleDateFormat; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Date; 028import java.util.LinkedHashSet; 029import java.util.List; 030import java.util.UUID; 031import java.util.concurrent.atomic.AtomicInteger; 032 033import org.forgerock.i18n.LocalizableMessage; 034import org.forgerock.opendj.ldap.ByteString; 035import org.forgerock.opendj.ldap.DecodeException; 036import org.forgerock.opendj.ldap.DereferenceAliasesPolicy; 037import org.forgerock.opendj.ldap.ModificationType; 038import org.forgerock.opendj.ldap.SearchScope; 039import org.opends.server.backends.task.FailedDependencyAction; 040import org.opends.server.backends.task.TaskState; 041import org.opends.server.config.ConfigConstants; 042import org.opends.server.protocols.ldap.AddRequestProtocolOp; 043import org.opends.server.protocols.ldap.AddResponseProtocolOp; 044import org.opends.server.protocols.ldap.DeleteRequestProtocolOp; 045import org.opends.server.protocols.ldap.DeleteResponseProtocolOp; 046import org.opends.server.protocols.ldap.LDAPAttribute; 047import org.opends.server.protocols.ldap.LDAPConstants; 048import org.opends.server.protocols.ldap.LDAPFilter; 049import org.opends.server.protocols.ldap.LDAPMessage; 050import org.opends.server.protocols.ldap.LDAPModification; 051import org.opends.server.protocols.ldap.LDAPResultCode; 052import org.opends.server.protocols.ldap.ModifyRequestProtocolOp; 053import org.opends.server.protocols.ldap.ModifyResponseProtocolOp; 054import org.opends.server.protocols.ldap.SearchRequestProtocolOp; 055import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp; 056import org.opends.server.tools.LDAPConnection; 057import org.opends.server.tools.LDAPReader; 058import org.opends.server.tools.LDAPWriter; 059import org.opends.server.types.Control; 060import org.opends.server.types.Entry; 061import org.opends.server.types.LDAPException; 062import org.opends.server.types.RawAttribute; 063import org.opends.server.types.RawModification; 064import org.opends.server.types.SearchResultEntry; 065import org.opends.server.util.StaticUtils; 066 067/** 068 * Helper class for interacting with the task backend on behalf of utilities 069 * that are capable of being scheduled. 070 */ 071public class TaskClient { 072 073 /** 074 * Connection through which task scheduling will take place. 075 */ 076 protected LDAPConnection connection; 077 078 /** 079 * Keeps track of message IDs. 080 */ 081 private final AtomicInteger nextMessageID = new AtomicInteger(0); 082 083 /** 084 * Creates a new TaskClient for interacting with the task backend remotely. 085 * @param conn for accessing the task backend 086 */ 087 public TaskClient(LDAPConnection conn) { 088 this.connection = conn; 089 } 090 091 /** 092 * Returns the ID of the task entry for a given list of task attributes. 093 * @param taskAttributes the task attributes. 094 * @return the ID of the task entry for a given list of task attributes. 095 */ 096 public static String getTaskID(List<RawAttribute> taskAttributes) 097 { 098 String taskID = null; 099 100 RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID, 101 taskAttributes); 102 103 if (recurringIDAttr != null) { 104 taskID = recurringIDAttr.getValues().get(0).toString(); 105 } else { 106 RawAttribute taskIDAttr = getAttribute(ATTR_TASK_ID, 107 taskAttributes); 108 taskID = taskIDAttr.getValues().get(0).toString(); 109 } 110 111 return taskID; 112 } 113 114 private static RawAttribute getAttribute(String attrName, 115 List<RawAttribute> taskAttributes) 116 { 117 for (RawAttribute attr : taskAttributes) 118 { 119 if (attr.getAttributeType().equalsIgnoreCase(attrName)) 120 { 121 return attr; 122 } 123 } 124 return null; 125 } 126 127 /** 128 * Returns the DN of the task entry for a given list of task attributes. 129 * @param taskAttributes the task attributes. 130 * @return the DN of the task entry for a given list of task attributes. 131 */ 132 public static String getTaskDN(List<RawAttribute> taskAttributes) 133 { 134 String entryDN = null; 135 String taskID = getTaskID(taskAttributes); 136 RawAttribute recurringIDAttr = getAttribute(ATTR_RECURRING_TASK_ID, 137 taskAttributes); 138 139 if (recurringIDAttr != null) { 140 entryDN = ATTR_RECURRING_TASK_ID + "=" + 141 taskID + "," + RECURRING_TASK_BASE_RDN + "," + DN_TASK_ROOT; 142 } else { 143 entryDN = ATTR_TASK_ID + "=" + taskID + "," + 144 SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT; 145 } 146 return entryDN; 147 } 148 149 private static boolean isScheduleRecurring(TaskScheduleInformation information) 150 { 151 return information.getRecurringDateTime() != null; 152 } 153 154 /** 155 * This is a commodity method that returns the common attributes (those 156 * related to scheduling) of a task entry for a given 157 * {@link TaskScheduleInformation} object. 158 * @param information the scheduling information. 159 * @return the schedule attributes of the task entry. 160 */ 161 public static ArrayList<RawAttribute> getTaskAttributes( 162 TaskScheduleInformation information) 163 { 164 String taskID = null; 165 boolean scheduleRecurring = isScheduleRecurring(information); 166 167 if (scheduleRecurring) { 168 taskID = information.getTaskId(); 169 if (taskID == null || taskID.length() == 0) { 170 taskID = information.getTaskClass().getSimpleName() + "-" + UUID.randomUUID(); 171 } 172 } else { 173 // Use a formatted time/date for the ID so that is remotely useful 174 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS"); 175 taskID = df.format(new Date()); 176 } 177 178 ArrayList<RawAttribute> attributes = new ArrayList<>(); 179 180 ArrayList<String> ocValues = new ArrayList<>(4); 181 ocValues.add("top"); 182 ocValues.add(ConfigConstants.OC_TASK); 183 if (scheduleRecurring) { 184 ocValues.add(ConfigConstants.OC_RECURRING_TASK); 185 } 186 ocValues.add(information.getTaskObjectclass()); 187 attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, ocValues)); 188 189 if (scheduleRecurring) { 190 attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_ID, taskID)); 191 } 192 attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskID)); 193 194 String classValue = information.getTaskClass().getName(); 195 attributes.add(new LDAPAttribute(ATTR_TASK_CLASS, classValue)); 196 197 // add the start time if necessary 198 Date startDate = information.getStartDateTime(); 199 if (startDate != null) { 200 String startTimeString = StaticUtils.formatDateTimeString(startDate); 201 attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME, startTimeString)); 202 } 203 204 if (scheduleRecurring) { 205 String recurringPatternValues = information.getRecurringDateTime(); 206 attributes.add(new LDAPAttribute(ATTR_RECURRING_TASK_SCHEDULE, recurringPatternValues)); 207 } 208 209 // add dependency IDs 210 List<String> dependencyIds = information.getDependencyIds(); 211 if (dependencyIds != null && !dependencyIds.isEmpty()) { 212 attributes.add(new LDAPAttribute(ATTR_TASK_DEPENDENCY_IDS, dependencyIds)); 213 214 // add the dependency action 215 FailedDependencyAction fda = information.getFailedDependencyAction(); 216 if (fda == null) { 217 fda = FailedDependencyAction.defaultValue(); 218 } 219 attributes.add(new LDAPAttribute(ATTR_TASK_FAILED_DEPENDENCY_ACTION, fda.name())); 220 } 221 222 // add completion notification email addresses 223 List<String> compNotifEmailAddresss = information.getNotifyUponCompletionEmailAddresses(); 224 if (compNotifEmailAddresss != null && !compNotifEmailAddresss.isEmpty()) { 225 attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_COMPLETION, compNotifEmailAddresss)); 226 } 227 228 // add error notification email addresses 229 List<String> errNotifEmailAddresss = information.getNotifyUponErrorEmailAddresses(); 230 if (errNotifEmailAddresss != null && !errNotifEmailAddresss.isEmpty()) { 231 attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_ERROR, errNotifEmailAddresss)); 232 } 233 234 information.addTaskAttributes(attributes); 235 236 return attributes; 237 } 238 239 /** 240 * Schedule a task for execution by writing an entry to the task backend. 241 * 242 * @param information to be scheduled 243 * @return String task ID assigned the new task 244 * @throws IOException if there is a stream communication problem 245 * @throws LDAPException if there is a problem getting information 246 * out to the directory 247 * @throws DecodeException if there is a problem with the encoding 248 * @throws TaskClientException if there is a problem with the task entry 249 */ 250 public synchronized TaskEntry schedule(TaskScheduleInformation information) 251 throws LDAPException, IOException, DecodeException, TaskClientException 252 { 253 LDAPReader reader = connection.getLDAPReader(); 254 LDAPWriter writer = connection.getLDAPWriter(); 255 256 ArrayList<Control> controls = new ArrayList<>(); 257 ArrayList<RawAttribute> attributes = getTaskAttributes(information); 258 259 ByteString entryDN = ByteString.valueOfUtf8(getTaskDN(attributes)); 260 AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN, attributes); 261 LDAPMessage requestMessage = 262 new LDAPMessage(nextMessageID.getAndIncrement(), addRequest, controls); 263 264 // Send the request to the server and read the response. 265 LDAPMessage responseMessage; 266 writer.writeMessage(requestMessage); 267 268 responseMessage = reader.readMessage(); 269 if (responseMessage == null) 270 { 271 throw new LDAPException( 272 LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, 273 ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get()); 274 } 275 276 if (responseMessage.getProtocolOpType() != 277 LDAPConstants.OP_TYPE_ADD_RESPONSE) 278 { 279 throw new LDAPException( 280 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 281 ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get( 282 responseMessage.getProtocolOpName())); 283 } 284 285 AddResponseProtocolOp addResponse = 286 responseMessage.getAddResponseProtocolOp(); 287 if (addResponse.getResultCode() != 0) { 288 throw new LDAPException( 289 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 290 addResponse.getErrorMessage()); 291 } 292 return getTaskEntry(getTaskID(attributes)); 293 } 294 295 /** 296 * Gets all the ds-task entries from the task root. 297 * 298 * @return list of entries from the task root 299 * @throws IOException if there is a stream communication problem 300 * @throws LDAPException if there is a problem getting information 301 * out to the directory 302 * @throws DecodeException if there is a problem with the encoding 303 */ 304 public synchronized List<TaskEntry> getTaskEntries() 305 throws LDAPException, IOException, DecodeException { 306 List<Entry> entries = new ArrayList<>(); 307 308 writeSearch(new SearchRequestProtocolOp( 309 ByteString.valueOfUtf8(ConfigConstants.DN_TASK_ROOT), 310 SearchScope.WHOLE_SUBTREE, 311 DereferenceAliasesPolicy.NEVER, 312 Integer.MAX_VALUE, 313 Integer.MAX_VALUE, 314 false, 315 LDAPFilter.decode("(objectclass=ds-task)"), 316 new LinkedHashSet<String>())); 317 318 LDAPReader reader = connection.getLDAPReader(); 319 byte opType; 320 do { 321 LDAPMessage responseMessage = reader.readMessage(); 322 if (responseMessage == null) { 323 throw new LDAPException( 324 LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, 325 ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get()); 326 } else { 327 opType = responseMessage.getProtocolOpType(); 328 if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) { 329 SearchResultEntryProtocolOp searchEntryOp = 330 responseMessage.getSearchResultEntryProtocolOp(); 331 SearchResultEntry entry = searchEntryOp.toSearchResultEntry(); 332 entries.add(entry); 333 } 334 } 335 } 336 while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE); 337 List<TaskEntry> taskEntries = new ArrayList<>(entries.size()); 338 for (Entry entry : entries) { 339 taskEntries.add(new TaskEntry(entry)); 340 } 341 return Collections.unmodifiableList(taskEntries); 342 } 343 344 /** 345 * Gets the entry of the task whose ID is <code>id</code> from the directory. 346 * 347 * @param id of the entry to retrieve 348 * @return Entry for the task 349 * @throws IOException if there is a stream communication problem 350 * @throws LDAPException if there is a problem getting information 351 * out to the directory 352 * @throws DecodeException if there is a problem with the encoding 353 * @throws TaskClientException if there is no task with the requested id 354 */ 355 public synchronized TaskEntry getTaskEntry(String id) 356 throws LDAPException, IOException, DecodeException, TaskClientException 357 { 358 Entry entry = null; 359 360 writeSearch(new SearchRequestProtocolOp( 361 ByteString.valueOfUtf8(ConfigConstants.DN_TASK_ROOT), 362 SearchScope.WHOLE_SUBTREE, 363 DereferenceAliasesPolicy.NEVER, 364 Integer.MAX_VALUE, 365 Integer.MAX_VALUE, 366 false, 367 LDAPFilter.decode("(" + ATTR_TASK_ID + "=" + id + ")"), 368 new LinkedHashSet<String>())); 369 370 LDAPReader reader = connection.getLDAPReader(); 371 byte opType; 372 do { 373 LDAPMessage responseMessage = reader.readMessage(); 374 if (responseMessage == null) { 375 LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); 376 throw new LDAPException(UNAVAILABLE.intValue(), message); 377 } else { 378 opType = responseMessage.getProtocolOpType(); 379 if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) { 380 SearchResultEntryProtocolOp searchEntryOp = 381 responseMessage.getSearchResultEntryProtocolOp(); 382 entry = searchEntryOp.toSearchResultEntry(); 383 } 384 } 385 } 386 while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE); 387 if (entry == null) { 388 throw new TaskClientException(ERR_TASK_CLIENT_UNKNOWN_TASK.get(id)); 389 } 390 return new TaskEntry(entry); 391 } 392 393 394 /** 395 * Changes that the state of the task in the backend to a canceled state. 396 * 397 * @param id if the task to cancel 398 * @throws IOException if there is a stream communication problem 399 * @throws LDAPException if there is a problem getting information 400 * out to the directory 401 * @throws DecodeException if there is a problem with the encoding 402 * @throws TaskClientException if there is no task with the requested id 403 */ 404 public synchronized void cancelTask(String id) 405 throws TaskClientException, IOException, DecodeException, LDAPException 406 { 407 LDAPReader reader = connection.getLDAPReader(); 408 LDAPWriter writer = connection.getLDAPWriter(); 409 410 TaskEntry entry = getTaskEntry(id); 411 TaskState state = entry.getTaskState(); 412 if (state != null) { 413 if (!TaskState.isDone(state)) { 414 415 ByteString dn = ByteString.valueOfUtf8(entry.getDN().toString()); 416 417 ArrayList<RawModification> mods = new ArrayList<>(); 418 419 String newState; 420 if (TaskState.isPending(state)) { 421 newState = TaskState.CANCELED_BEFORE_STARTING.name(); 422 } else { 423 newState = TaskState.STOPPED_BY_ADMINISTRATOR.name(); 424 } 425 LDAPAttribute attr = new LDAPAttribute(ATTR_TASK_STATE, newState); 426 mods.add(new LDAPModification(ModificationType.REPLACE, attr)); 427 428 ModifyRequestProtocolOp modRequest = 429 new ModifyRequestProtocolOp(dn, mods); 430 LDAPMessage requestMessage = 431 new LDAPMessage(nextMessageID.getAndIncrement(), modRequest, null); 432 433 writer.writeMessage(requestMessage); 434 435 LDAPMessage responseMessage = reader.readMessage(); 436 437 if (responseMessage == null) { 438 LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); 439 throw new LDAPException(UNAVAILABLE.intValue(), message); 440 } 441 442 if (responseMessage.getProtocolOpType() != 443 LDAPConstants.OP_TYPE_MODIFY_RESPONSE) 444 { 445 throw new LDAPException( 446 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 447 ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get( 448 responseMessage.getProtocolOpName())); 449 } 450 451 ModifyResponseProtocolOp modResponse = 452 responseMessage.getModifyResponseProtocolOp(); 453 LocalizableMessage errorMessage = modResponse.getErrorMessage(); 454 if (errorMessage != null) { 455 throw new LDAPException( 456 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 457 errorMessage); 458 } 459 } else if (TaskState.isRecurring(state)) { 460 461 ByteString dn = ByteString.valueOfUtf8(entry.getDN().toString()); 462 DeleteRequestProtocolOp deleteRequest = 463 new DeleteRequestProtocolOp(dn); 464 465 LDAPMessage requestMessage = new LDAPMessage( 466 nextMessageID.getAndIncrement(), deleteRequest, null); 467 468 writer.writeMessage(requestMessage); 469 470 LDAPMessage responseMessage = reader.readMessage(); 471 472 if (responseMessage == null) { 473 LocalizableMessage message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); 474 throw new LDAPException(UNAVAILABLE.intValue(), message); 475 } 476 477 if (responseMessage.getProtocolOpType() != 478 LDAPConstants.OP_TYPE_DELETE_RESPONSE) 479 { 480 throw new LDAPException( 481 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 482 ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get( 483 responseMessage.getProtocolOpName())); 484 } 485 486 DeleteResponseProtocolOp deleteResponse = 487 responseMessage.getDeleteResponseProtocolOp(); 488 LocalizableMessage errorMessage = deleteResponse.getErrorMessage(); 489 if (errorMessage != null) { 490 throw new LDAPException( 491 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 492 errorMessage); 493 } 494 } else { 495 throw new TaskClientException( 496 ERR_TASK_CLIENT_UNCANCELABLE_TASK.get(id)); 497 } 498 } else { 499 throw new TaskClientException( 500 ERR_TASK_CLIENT_TASK_STATE_UNKNOWN.get(id)); 501 } 502 } 503 504 505 /** 506 * Writes a search to the directory writer. 507 * @param searchRequest to write 508 * @throws IOException if there is a stream communication problem 509 */ 510 private void writeSearch(SearchRequestProtocolOp searchRequest) 511 throws IOException { 512 LDAPWriter writer = connection.getLDAPWriter(); 513 LDAPMessage requestMessage = new LDAPMessage( 514 nextMessageID.getAndIncrement(), 515 searchRequest, 516 new ArrayList<Control>()); 517 518 // Send the request to the server and read the response. 519 writer.writeMessage(requestMessage); 520 } 521 522}