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 }