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