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