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