001/* 002 * Copyright 2008-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-2018 Ping Identity Corporation 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.ldap.sdk.unboundidds.tasks; 022 023 024 025import java.util.LinkedList; 026import java.util.List; 027 028import com.unboundid.ldap.sdk.Entry; 029import com.unboundid.ldap.sdk.Filter; 030import com.unboundid.ldap.sdk.LDAPConnection; 031import com.unboundid.ldap.sdk.LDAPException; 032import com.unboundid.ldap.sdk.Modification; 033import com.unboundid.ldap.sdk.ModificationType; 034import com.unboundid.ldap.sdk.ResultCode; 035import com.unboundid.ldap.sdk.SearchResult; 036import com.unboundid.ldap.sdk.SearchResultEntry; 037import com.unboundid.ldap.sdk.SearchScope; 038import com.unboundid.util.ThreadSafety; 039import com.unboundid.util.ThreadSafetyLevel; 040 041import static com.unboundid.ldap.sdk.unboundidds.tasks.TaskMessages.*; 042import static com.unboundid.util.Debug.*; 043 044 045 046/** 047 * This class provides a number of utility methods for interacting with tasks in 048 * Ping Identity, UnboundID, or Alcatel-Lucent 8661 server instances. 049 * <BR> 050 * <BLOCKQUOTE> 051 * <B>NOTE:</B> This class, and other classes within the 052 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 053 * supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661 054 * server products. These classes provide support for proprietary 055 * functionality or for external specifications that are not considered stable 056 * or mature enough to be guaranteed to work in an interoperable way with 057 * other types of LDAP servers. 058 * </BLOCKQUOTE> 059 * <BR> 060 * It provides methods for the following: 061 * <UL> 062 * <LI>Retrieving information about all scheduled, running, and 063 * recently-completed tasks in the server.</LI> 064 * <LI>Retrieving a specific task by its task ID.</LI> 065 * <LI>Scheduling a new task.</LI> 066 * <LI>Waiting for a scheduled task to complete.</LI> 067 * <LI>Canceling a scheduled task.</LI> 068 * <LI>Deleting a scheduled task.</LI> 069 * </UL> 070 * <H2>Example</H2> 071 * The following example demonstrates the process for retrieving information 072 * about all tasks within the server and printing their contents using the 073 * generic API: 074 * <PRE> 075 * List<Task> allTasks = TaskManager.getTasks(connection); 076 * for (Task task : allTasks) 077 * { 078 * String taskID = task.getTaskID(); 079 * String taskName = task.getTaskName(); 080 * TaskState taskState = task.getState(); 081 * Map<TaskProperty,List<Object>> taskProperties = 082 * task.getTaskPropertyValues(); 083 * } 084 * </PRE> 085 */ 086@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 087public final class TaskManager 088{ 089 /** 090 * Prevent this class from being instantiated. 091 */ 092 private TaskManager() 093 { 094 // No implementation is required. 095 } 096 097 098 099 /** 100 * Constructs the DN that should be used for the entry with the specified 101 * task ID. 102 * 103 * @param taskID The task ID for which to construct the entry DN. 104 * 105 * @return The constructed task entry DN. 106 */ 107 private static String getTaskDN(final String taskID) 108 { 109 // In general, constructing DNs is bad, but we'll do it here because we know 110 // we're dealing specifically with the Ping Identity, UnboundID, or 111 // Alcatel-Lucent 8661 Directory Server and we can ensure that this 112 // location will not change without extremely good reasons. 113 return Task.ATTR_TASK_ID + '=' + taskID + ',' + 114 Task.SCHEDULED_TASKS_BASE_DN; 115 } 116 117 118 119 /** 120 * Retrieves the task with the specified task ID using the given connection. 121 * 122 * @param connection The connection to the Directory Server from which to 123 * retrieve the task. It must not be {@code null}. 124 * @param taskID The task ID for the task to retrieve. It must not be 125 * {@code null}. 126 * 127 * @return The requested task, or {@code null} if no such task exists in the 128 * server. An attempt will be made to instantiate the task as the 129 * most appropriate task type, but if this is not possible then it 130 * will be a generic {@code Task} object. 131 * 132 * @throws LDAPException If a problem occurs while communicating with the 133 * Directory Server over the provided connection. 134 * 135 * @throws TaskException If the retrieved entry cannot be parsed as a task. 136 */ 137 public static Task getTask(final String taskID, 138 final LDAPConnection connection) 139 throws LDAPException, TaskException 140 { 141 try 142 { 143 final Entry taskEntry = connection.getEntry(getTaskDN(taskID)); 144 if (taskEntry == null) 145 { 146 return null; 147 } 148 149 return Task.decodeTask(taskEntry); 150 } 151 catch (final LDAPException le) 152 { 153 debugException(le); 154 if (le.getResultCode() == ResultCode.NO_SUCH_OBJECT) 155 { 156 return null; 157 } 158 159 throw le; 160 } 161 } 162 163 164 165 /** 166 * Retrieves all of the tasks defined in the Directory Server using the 167 * provided connection. 168 * 169 * @param connection The connection to the Directory Server instance from 170 * which to retrieve the defined tasks. 171 * 172 * @return A list of all tasks defined in the associated Directory Server. 173 * 174 * @throws LDAPException If a problem occurs while communicating with the 175 * Directory Server over the provided connection. 176 */ 177 public static List<Task> getTasks(final LDAPConnection connection) 178 throws LDAPException 179 { 180 final Filter filter = 181 Filter.createEqualityFilter("objectClass", Task.OC_TASK); 182 183 final SearchResult result = connection.search(Task.SCHEDULED_TASKS_BASE_DN, 184 SearchScope.SUB, filter); 185 186 final LinkedList<Task> tasks = new LinkedList<Task>(); 187 for (final SearchResultEntry e : result.getSearchEntries()) 188 { 189 try 190 { 191 tasks.add(Task.decodeTask(e)); 192 } 193 catch (final TaskException te) 194 { 195 debugException(te); 196 197 // We got an entry that couldn't be parsed as a task. This is an error, 198 // but we don't want to spoil the ability to retrieve other tasks that 199 // could be decoded, so we'll just ignore it for now. 200 } 201 } 202 203 return tasks; 204 } 205 206 207 208 /** 209 * Schedules a new instance of the provided task in the Directory Server. 210 * 211 * @param task The task to be scheduled. 212 * @param connection The connection to the Directory Server in which the 213 * task is to be scheduled. 214 * 215 * @return A {@code Task} object representing the task that was scheduled and 216 * re-read from the server. 217 * 218 * @throws LDAPException If a problem occurs while communicating with the 219 * Directory Server, or if it rejects the task. 220 * 221 * @throws TaskException If the entry read back from the server after the 222 * task was created could not be parsed as a task. 223 */ 224 public static Task scheduleTask(final Task task, 225 final LDAPConnection connection) 226 throws LDAPException, TaskException 227 { 228 final Entry taskEntry = task.createTaskEntry(); 229 connection.add(task.createTaskEntry()); 230 231 final Entry newTaskEntry = connection.getEntry(taskEntry.getDN()); 232 if (newTaskEntry == null) 233 { 234 // This should never happen. 235 throw new LDAPException(ResultCode.NO_SUCH_OBJECT); 236 } 237 238 return Task.decodeTask(newTaskEntry); 239 } 240 241 242 243 /** 244 * Submits a request to cancel the task with the specified task ID. Note that 245 * some tasks may not support being canceled. Further, for tasks that do 246 * support being canceled it may take time for the cancel request to be 247 * processed and for the task to actually be canceled. 248 * 249 * @param taskID The task ID of the task to be canceled. 250 * @param connection The connection to the Directory Server in which to 251 * perform the operation. 252 * 253 * @throws LDAPException If a problem occurs while communicating with the 254 * Directory Server. 255 */ 256 public static void cancelTask(final String taskID, 257 final LDAPConnection connection) 258 throws LDAPException 259 { 260 // Note: we should use the CANCELED_BEFORE_STARTING state when we want to 261 // cancel a task regardless of whether it's pending or running. If the 262 // task is running, the server will convert it to STOPPED_BY_ADMINISTRATOR. 263 final Modification mod = 264 new Modification(ModificationType.REPLACE, Task.ATTR_TASK_STATE, 265 TaskState.CANCELED_BEFORE_STARTING.getName()); 266 connection.modify(getTaskDN(taskID), mod); 267 } 268 269 270 271 /** 272 * Attempts to delete the task with the specified task ID. 273 * 274 * @param taskID The task ID of the task to be deleted. 275 * @param connection The connection to the Directory Server in which to 276 * perform the operation. 277 * 278 * @throws LDAPException If a problem occurs while communicating with the 279 * Directory Server. 280 */ 281 public static void deleteTask(final String taskID, 282 final LDAPConnection connection) 283 throws LDAPException 284 { 285 connection.delete(getTaskDN(taskID)); 286 } 287 288 289 290 /** 291 * Waits for the specified task to complete. 292 * 293 * @param taskID The task ID of the task to poll. 294 * @param connection The connection to the Directory Server containing 295 * the desired task. 296 * @param pollFrequency The minimum length of time in milliseconds between 297 * checks to see if the task has completed. A value 298 * less than or equal to zero will cause the client to 299 * check as quickly as possible. 300 * @param maxWaitTime The maximum length of time in milliseconds to wait 301 * for the task to complete before giving up. A value 302 * less than or equal to zero indicates that it will 303 * keep checking indefinitely until the task has 304 * completed. 305 * 306 * @return Task The decoded task after it has completed, or after the 307 * maximum wait time has expired. 308 * 309 * @throws LDAPException If a problem occurs while communicating with the 310 * Directory Server. 311 * 312 * @throws TaskException If a problem occurs while attempting to parse the 313 * task entry as a task, or if the specified task 314 * entry could not be found. 315 */ 316 public static Task waitForTask(final String taskID, 317 final LDAPConnection connection, 318 final long pollFrequency, 319 final long maxWaitTime) 320 throws LDAPException, TaskException 321 { 322 final long stopWaitingTime; 323 if (maxWaitTime > 0) 324 { 325 stopWaitingTime = System.currentTimeMillis() + maxWaitTime; 326 } 327 else 328 { 329 stopWaitingTime = Long.MAX_VALUE; 330 } 331 332 while (true) 333 { 334 final Task t = getTask(taskID, connection); 335 if (t == null) 336 { 337 throw new TaskException(ERR_TASK_MANAGER_WAIT_NO_SUCH_TASK.get(taskID)); 338 } 339 340 if (t.isCompleted()) 341 { 342 return t; 343 } 344 345 final long timeRemaining = stopWaitingTime - System.currentTimeMillis(); 346 if (timeRemaining <= 0) 347 { 348 return t; 349 } 350 351 try 352 { 353 Thread.sleep(Math.min(pollFrequency, timeRemaining)); 354 } 355 catch (final InterruptedException ie) 356 { 357 debugException(ie); 358 Thread.currentThread().interrupt(); 359 throw new TaskException(ERR_TASK_MANAGER_WAIT_INTERRUPTED.get(taskID), 360 ie); 361 } 362 } 363 } 364}