001/* 002 * Copyright 2008-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2008-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2008-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.tasks; 037 038 039 040import java.util.LinkedList; 041import java.util.List; 042 043import com.unboundid.ldap.sdk.Entry; 044import com.unboundid.ldap.sdk.Filter; 045import com.unboundid.ldap.sdk.LDAPConnection; 046import com.unboundid.ldap.sdk.LDAPInterface; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.Modification; 049import com.unboundid.ldap.sdk.ModificationType; 050import com.unboundid.ldap.sdk.ResultCode; 051import com.unboundid.ldap.sdk.SearchResult; 052import com.unboundid.ldap.sdk.SearchResultEntry; 053import com.unboundid.ldap.sdk.SearchScope; 054import com.unboundid.util.Debug; 055import com.unboundid.util.NotNull; 056import com.unboundid.util.Nullable; 057import com.unboundid.util.ThreadSafety; 058import com.unboundid.util.ThreadSafetyLevel; 059 060import static com.unboundid.ldap.sdk.unboundidds.tasks.TaskMessages.*; 061 062 063 064/** 065 * This class provides a number of utility methods for interacting with tasks in 066 * Ping Identity, UnboundID, or Nokia/Alcatel-Lucent 8661 server instances. 067 * <BR> 068 * <BLOCKQUOTE> 069 * <B>NOTE:</B> This class, and other classes within the 070 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 071 * supported for use against Ping Identity, UnboundID, and 072 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 073 * for proprietary functionality or for external specifications that are not 074 * considered stable or mature enough to be guaranteed to work in an 075 * interoperable way with other types of LDAP servers. 076 * </BLOCKQUOTE> 077 * <BR> 078 * It provides methods for the following: 079 * <UL> 080 * <LI>Retrieving information about all scheduled, running, and 081 * recently-completed tasks in the server.</LI> 082 * <LI>Retrieving a specific task by its task ID.</LI> 083 * <LI>Scheduling a new task.</LI> 084 * <LI>Waiting for a scheduled task to complete.</LI> 085 * <LI>Canceling a scheduled task.</LI> 086 * <LI>Deleting a scheduled task.</LI> 087 * </UL> 088 * <H2>Example</H2> 089 * The following example demonstrates the process for retrieving information 090 * about all tasks within the server and printing their contents using the 091 * generic API: 092 * <PRE> 093 * List<Task> allTasks = TaskManager.getTasks(connection); 094 * for (Task task : allTasks) 095 * { 096 * String taskID = task.getTaskID(); 097 * String taskName = task.getTaskName(); 098 * TaskState taskState = task.getState(); 099 * Map<TaskProperty,List<Object>> taskProperties = 100 * task.getTaskPropertyValues(); 101 * } 102 * </PRE> 103 */ 104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 105public final class TaskManager 106{ 107 /** 108 * Prevent this class from being instantiated. 109 */ 110 private TaskManager() 111 { 112 // No implementation is required. 113 } 114 115 116 117 /** 118 * Constructs the DN that should be used for the entry with the specified 119 * task ID. 120 * 121 * @param taskID The task ID for which to construct the entry DN. 122 * 123 * @return The constructed task entry DN. 124 */ 125 @NotNull() 126 private static String getTaskDN(@NotNull final String taskID) 127 { 128 // In general, constructing DNs is bad, but we'll do it here because we know 129 // we're dealing specifically with the Ping Identity, UnboundID, or 130 // Nokia/Alcatel-Lucent 8661 Directory Server and we can ensure that this 131 // location will not change without extremely good reasons. 132 return Task.ATTR_TASK_ID + '=' + taskID + ',' + 133 Task.SCHEDULED_TASKS_BASE_DN; 134 } 135 136 137 138 /** 139 * Retrieves the task with the specified task ID using the given connection. 140 * 141 * @param connection The connection to the Directory Server from which to 142 * retrieve the task. It must not be {@code null}. 143 * @param taskID The task ID for the task to retrieve. It must not be 144 * {@code null}. 145 * 146 * @return The requested task, or {@code null} if no such task exists in the 147 * server. An attempt will be made to instantiate the task as the 148 * most appropriate task type, but if this is not possible then it 149 * will be a generic {@code Task} object. 150 * 151 * @throws LDAPException If a problem occurs while communicating with the 152 * Directory Server over the provided connection. 153 * 154 * @throws TaskException If the retrieved entry cannot be parsed as a task. 155 */ 156 @Nullable() 157 public static Task getTask(@NotNull final String taskID, 158 @NotNull final LDAPConnection connection) 159 throws LDAPException, TaskException 160 { 161 return getTask(taskID, (LDAPInterface) connection); 162 } 163 164 165 166 /** 167 * Retrieves the task with the specified task ID using the given connection. 168 * 169 * @param connection The connection to the Directory Server from which to 170 * retrieve the task. It must not be {@code null}. 171 * @param taskID The task ID for the task to retrieve. It must not be 172 * {@code null}. 173 * 174 * @return The requested task, or {@code null} if no such task exists in the 175 * server. An attempt will be made to instantiate the task as the 176 * most appropriate task type, but if this is not possible then it 177 * will be a generic {@code Task} object. 178 * 179 * @throws LDAPException If a problem occurs while communicating with the 180 * Directory Server over the provided connection. 181 * 182 * @throws TaskException If the retrieved entry cannot be parsed as a task. 183 */ 184 @Nullable() 185 public static Task getTask(@NotNull final String taskID, 186 @NotNull final LDAPInterface connection) 187 throws LDAPException, TaskException 188 { 189 try 190 { 191 final Entry taskEntry = connection.getEntry(getTaskDN(taskID)); 192 if (taskEntry == null) 193 { 194 return null; 195 } 196 197 return Task.decodeTask(taskEntry); 198 } 199 catch (final LDAPException le) 200 { 201 Debug.debugException(le); 202 if (le.getResultCode() == ResultCode.NO_SUCH_OBJECT) 203 { 204 return null; 205 } 206 207 throw le; 208 } 209 } 210 211 212 213 /** 214 * Retrieves all of the tasks defined in the Directory Server using the 215 * provided connection. 216 * 217 * @param connection The connection to the Directory Server instance from 218 * which to retrieve the defined tasks. 219 * 220 * @return A list of all tasks defined in the associated Directory Server. 221 * 222 * @throws LDAPException If a problem occurs while communicating with the 223 * Directory Server over the provided connection. 224 */ 225 @NotNull() 226 public static List<Task> getTasks(@NotNull final LDAPConnection connection) 227 throws LDAPException 228 { 229 return getTasks((LDAPInterface) connection); 230 } 231 232 233 234 /** 235 * Retrieves all of the tasks defined in the Directory Server using the 236 * provided connection. 237 * 238 * @param connection The connection to the Directory Server instance from 239 * which to retrieve the defined tasks. 240 * 241 * @return A list of all tasks defined in the associated Directory Server. 242 * 243 * @throws LDAPException If a problem occurs while communicating with the 244 * Directory Server over the provided connection. 245 */ 246 @NotNull() 247 public static List<Task> getTasks(@NotNull final LDAPInterface connection) 248 throws LDAPException 249 { 250 final Filter filter = 251 Filter.createEqualityFilter("objectClass", Task.OC_TASK); 252 253 final SearchResult result = connection.search(Task.SCHEDULED_TASKS_BASE_DN, 254 SearchScope.SUB, filter); 255 256 final LinkedList<Task> tasks = new LinkedList<>(); 257 for (final SearchResultEntry e : result.getSearchEntries()) 258 { 259 try 260 { 261 tasks.add(Task.decodeTask(e)); 262 } 263 catch (final TaskException te) 264 { 265 Debug.debugException(te); 266 267 // We got an entry that couldn't be parsed as a task. This is an error, 268 // but we don't want to spoil the ability to retrieve other tasks that 269 // could be decoded, so we'll just ignore it for now. 270 } 271 } 272 273 return tasks; 274 } 275 276 277 278 /** 279 * Schedules a new instance of the provided task in the Directory Server. 280 * 281 * @param task The task to be scheduled. 282 * @param connection The connection to the Directory Server in which the 283 * task is to be scheduled. 284 * 285 * @return A {@code Task} object representing the task that was scheduled and 286 * re-read from the server. 287 * 288 * @throws LDAPException If a problem occurs while communicating with the 289 * Directory Server, or if it rejects the task. 290 * 291 * @throws TaskException If the entry read back from the server after the 292 * task was created could not be parsed as a task. 293 */ 294 @NotNull() 295 public static Task scheduleTask(@NotNull final Task task, 296 @NotNull final LDAPConnection connection) 297 throws LDAPException, TaskException 298 { 299 return scheduleTask(task, (LDAPInterface) connection); 300 } 301 302 303 304 /** 305 * Schedules a new instance of the provided task in the Directory Server. 306 * 307 * @param task The task to be scheduled. 308 * @param connection The connection to the Directory Server in which the 309 * task is to be scheduled. 310 * 311 * @return A {@code Task} object representing the task that was scheduled and 312 * re-read from the server. 313 * 314 * @throws LDAPException If a problem occurs while communicating with the 315 * Directory Server, or if it rejects the task. 316 * 317 * @throws TaskException If the entry read back from the server after the 318 * task was created could not be parsed as a task. 319 */ 320 @NotNull() 321 public static Task scheduleTask(@NotNull final Task task, 322 @NotNull final LDAPInterface connection) 323 throws LDAPException, TaskException 324 { 325 final Entry taskEntry = task.createTaskEntry(); 326 connection.add(task.createTaskEntry()); 327 328 final Entry newTaskEntry = connection.getEntry(taskEntry.getDN()); 329 if (newTaskEntry == null) 330 { 331 // This should never happen. 332 throw new LDAPException(ResultCode.NO_SUCH_OBJECT); 333 } 334 335 return Task.decodeTask(newTaskEntry); 336 } 337 338 339 340 /** 341 * Submits a request to cancel the task with the specified task ID. Note that 342 * some tasks may not support being canceled. Further, for tasks that do 343 * support being canceled it may take time for the cancel request to be 344 * processed and for the task to actually be canceled. 345 * 346 * @param taskID The task ID of the task to be canceled. 347 * @param connection The connection to the Directory Server in which to 348 * perform the operation. 349 * 350 * @throws LDAPException If a problem occurs while communicating with the 351 * Directory Server. 352 */ 353 public static void cancelTask(@NotNull final String taskID, 354 @NotNull final LDAPConnection connection) 355 throws LDAPException 356 { 357 cancelTask(taskID, (LDAPInterface) connection); 358 } 359 360 361 362 /** 363 * Submits a request to cancel the task with the specified task ID. Note that 364 * some tasks may not support being canceled. Further, for tasks that do 365 * support being canceled it may take time for the cancel request to be 366 * processed and for the task to actually be canceled. 367 * 368 * @param taskID The task ID of the task to be canceled. 369 * @param connection The connection to the Directory Server in which to 370 * perform the operation. 371 * 372 * @throws LDAPException If a problem occurs while communicating with the 373 * Directory Server. 374 */ 375 public static void cancelTask(@NotNull final String taskID, 376 @NotNull final LDAPInterface connection) 377 throws LDAPException 378 { 379 // Note: we should use the CANCELED_BEFORE_STARTING state when we want to 380 // cancel a task regardless of whether it's pending or running. If the 381 // task is running, the server will convert it to STOPPED_BY_ADMINISTRATOR. 382 final Modification mod = 383 new Modification(ModificationType.REPLACE, Task.ATTR_TASK_STATE, 384 TaskState.CANCELED_BEFORE_STARTING.getName()); 385 connection.modify(getTaskDN(taskID), mod); 386 } 387 388 389 390 /** 391 * Attempts to delete the task with the specified task ID. 392 * 393 * @param taskID The task ID of the task to be deleted. 394 * @param connection The connection to the Directory Server in which to 395 * perform the operation. 396 * 397 * @throws LDAPException If a problem occurs while communicating with the 398 * Directory Server. 399 */ 400 public static void deleteTask(@NotNull final String taskID, 401 @NotNull final LDAPConnection connection) 402 throws LDAPException 403 { 404 deleteTask(taskID, (LDAPInterface) connection); 405 } 406 407 408 409 /** 410 * Attempts to delete the task with the specified task ID. 411 * 412 * @param taskID The task ID of the task to be deleted. 413 * @param connection The connection to the Directory Server in which to 414 * perform the operation. 415 * 416 * @throws LDAPException If a problem occurs while communicating with the 417 * Directory Server. 418 */ 419 public static void deleteTask(@NotNull final String taskID, 420 @NotNull final LDAPInterface connection) 421 throws LDAPException 422 { 423 connection.delete(getTaskDN(taskID)); 424 } 425 426 427 428 /** 429 * Waits for the specified task to complete. 430 * 431 * @param taskID The task ID of the task to poll. 432 * @param connection The connection to the Directory Server containing 433 * the desired task. 434 * @param pollFrequency The minimum length of time in milliseconds between 435 * checks to see if the task has completed. A value 436 * less than or equal to zero will cause the client to 437 * check as quickly as possible. 438 * @param maxWaitTime The maximum length of time in milliseconds to wait 439 * for the task to complete before giving up. A value 440 * less than or equal to zero indicates that it will 441 * keep checking indefinitely until the task has 442 * completed. 443 * 444 * @return Task The decoded task after it has completed, or after the 445 * maximum wait time has expired. 446 * 447 * @throws LDAPException If a problem occurs while communicating with the 448 * Directory Server. 449 * 450 * @throws TaskException If a problem occurs while attempting to parse the 451 * task entry as a task, or if the specified task 452 * entry could not be found. 453 */ 454 @NotNull() 455 public static Task waitForTask(@NotNull final String taskID, 456 @NotNull final LDAPConnection connection, 457 final long pollFrequency, 458 final long maxWaitTime) 459 throws LDAPException, TaskException 460 { 461 return waitForTask(taskID, (LDAPInterface) connection, pollFrequency, 462 maxWaitTime); 463 } 464 465 466 467 /** 468 * Waits for the specified task to complete. 469 * 470 * @param taskID The task ID of the task to poll. 471 * @param connection The connection to the Directory Server containing 472 * the desired task. 473 * @param pollFrequency The minimum length of time in milliseconds between 474 * checks to see if the task has completed. A value 475 * less than or equal to zero will cause the client to 476 * check as quickly as possible. 477 * @param maxWaitTime The maximum length of time in milliseconds to wait 478 * for the task to complete before giving up. A value 479 * less than or equal to zero indicates that it will 480 * keep checking indefinitely until the task has 481 * completed. 482 * 483 * @return Task The decoded task after it has completed, or after the 484 * maximum wait time has expired. 485 * 486 * @throws LDAPException If a problem occurs while communicating with the 487 * Directory Server. 488 * 489 * @throws TaskException If a problem occurs while attempting to parse the 490 * task entry as a task, or if the specified task 491 * entry could not be found. 492 */ 493 @NotNull() 494 public static Task waitForTask(@NotNull final String taskID, 495 @NotNull final LDAPInterface connection, 496 final long pollFrequency, 497 final long maxWaitTime) 498 throws LDAPException, TaskException 499 { 500 final long stopWaitingTime; 501 if (maxWaitTime > 0) 502 { 503 stopWaitingTime = System.currentTimeMillis() + maxWaitTime; 504 } 505 else 506 { 507 stopWaitingTime = Long.MAX_VALUE; 508 } 509 510 while (true) 511 { 512 final Task t = getTask(taskID, connection); 513 if (t == null) 514 { 515 throw new TaskException(ERR_TASK_MANAGER_WAIT_NO_SUCH_TASK.get(taskID)); 516 } 517 518 if (t.isCompleted()) 519 { 520 return t; 521 } 522 523 final long timeRemaining = stopWaitingTime - System.currentTimeMillis(); 524 if (timeRemaining <= 0) 525 { 526 return t; 527 } 528 529 try 530 { 531 Thread.sleep(Math.min(pollFrequency, timeRemaining)); 532 } 533 catch (final InterruptedException ie) 534 { 535 Debug.debugException(ie); 536 Thread.currentThread().interrupt(); 537 throw new TaskException(ERR_TASK_MANAGER_WAIT_INTERRUPTED.get(taskID), 538 ie); 539 } 540 } 541 } 542}