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<Task> 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<TaskProperty,List<Object>> 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}