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