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