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&lt;Task&gt; 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&lt;TaskProperty,List&lt;Object&gt;&gt; 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}