001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 028 package org.opends.server.tools.tasks; 029 030 import org.opends.messages.Message; 031 import static org.opends.messages.ToolMessages.*; 032 import org.opends.server.config.ConfigConstants; 033 import static org.opends.server.config.ConfigConstants.*; 034 import org.opends.server.protocols.asn1.ASN1Exception; 035 import org.opends.server.protocols.asn1.ASN1OctetString; 036 import org.opends.server.protocols.ldap.AddRequestProtocolOp; 037 import org.opends.server.protocols.ldap.AddResponseProtocolOp; 038 import org.opends.server.protocols.ldap.LDAPAttribute; 039 import org.opends.server.protocols.ldap.LDAPConstants; 040 import org.opends.server.protocols.ldap.LDAPControl; 041 import org.opends.server.protocols.ldap.LDAPFilter; 042 import org.opends.server.protocols.ldap.LDAPMessage; 043 import org.opends.server.protocols.ldap.LDAPModification; 044 import org.opends.server.protocols.ldap.LDAPResultCode; 045 import org.opends.server.protocols.ldap.ModifyRequestProtocolOp; 046 import org.opends.server.protocols.ldap.ModifyResponseProtocolOp; 047 import org.opends.server.protocols.ldap.SearchRequestProtocolOp; 048 import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp; 049 import org.opends.server.tools.LDAPConnection; 050 import org.opends.server.tools.LDAPReader; 051 import org.opends.server.tools.LDAPWriter; 052 import org.opends.server.types.DereferencePolicy; 053 import org.opends.server.types.Entry; 054 import org.opends.server.types.LDAPException; 055 import org.opends.server.types.ModificationType; 056 import org.opends.server.types.RawAttribute; 057 import org.opends.server.types.RawModification; 058 import org.opends.server.types.SearchResultEntry; 059 import org.opends.server.types.SearchScope; 060 import static org.opends.server.types.ResultCode.*; 061 import org.opends.server.backends.task.TaskState; 062 import org.opends.server.backends.task.FailedDependencyAction; 063 import static org.opends.server.util.ServerConstants.*; 064 import org.opends.server.util.StaticUtils; 065 066 import java.io.IOException; 067 import java.text.SimpleDateFormat; 068 import java.util.ArrayList; 069 import java.util.Collections; 070 import java.util.Date; 071 import java.util.LinkedHashSet; 072 import java.util.List; 073 import java.util.concurrent.atomic.AtomicInteger; 074 075 /** 076 * Helper class for interacting with the task backend on behalf of utilities 077 * that are capable of being scheduled. 078 */ 079 public class TaskClient { 080 081 /** 082 * Connection through which task scheduling will take place. 083 */ 084 protected LDAPConnection connection; 085 086 /** 087 * Keeps track of message IDs. 088 */ 089 private AtomicInteger nextMessageID = new AtomicInteger(0); 090 091 /** 092 * Creates a new TaskClient for interacting with the task backend remotely. 093 * @param conn for accessing the task backend 094 */ 095 public TaskClient(LDAPConnection conn) { 096 this.connection = conn; 097 } 098 099 /** 100 * Schedule a task for execution by writing an entry to the task backend. 101 * 102 * @param information to be scheduled 103 * @return String task ID assigned the new task 104 * @throws IOException if there is a stream communication problem 105 * @throws LDAPException if there is a problem getting information 106 * out to the directory 107 * @throws ASN1Exception if there is a problem with the encoding 108 * @throws TaskClientException if there is a problem with the task entry 109 */ 110 public synchronized TaskEntry schedule(TaskScheduleInformation information) 111 throws LDAPException, IOException, ASN1Exception, TaskClientException 112 { 113 LDAPReader reader = connection.getLDAPReader(); 114 LDAPWriter writer = connection.getLDAPWriter(); 115 116 // Use a formatted time/date for the ID so that is remotely useful 117 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssMM"); 118 String taskID = df.format(new Date()); 119 120 ASN1OctetString entryDN = 121 new ASN1OctetString(ATTR_TASK_ID + "=" + taskID + "," + 122 SCHEDULED_TASK_BASE_RDN + "," + DN_TASK_ROOT); 123 124 ArrayList<LDAPControl> controls = new ArrayList<LDAPControl>(); 125 126 ArrayList<RawAttribute> attributes = new ArrayList<RawAttribute>(); 127 128 ArrayList<ASN1OctetString> ocValues = new ArrayList<ASN1OctetString>(3); 129 ocValues.add(new ASN1OctetString("top")); 130 ocValues.add(new ASN1OctetString(ConfigConstants.OC_TASK)); 131 ocValues.add(new ASN1OctetString(information.getTaskObjectclass())); 132 attributes.add(new LDAPAttribute(ATTR_OBJECTCLASS, ocValues)); 133 134 ArrayList<ASN1OctetString> taskIDValues = new ArrayList<ASN1OctetString>(1); 135 taskIDValues.add(new ASN1OctetString(taskID)); 136 attributes.add(new LDAPAttribute(ATTR_TASK_ID, taskIDValues)); 137 138 ArrayList<ASN1OctetString> classValues = new ArrayList<ASN1OctetString>(1); 139 classValues.add(new ASN1OctetString(information.getTaskClass().getName())); 140 attributes.add(new LDAPAttribute(ATTR_TASK_CLASS, classValues)); 141 142 // add the start time if necessary 143 Date startDate = information.getStartDateTime(); 144 if (startDate != null) { 145 String startTimeString = StaticUtils.formatDateTimeString(startDate); 146 ArrayList<ASN1OctetString> startDateValues = 147 new ArrayList<ASN1OctetString>(1); 148 startDateValues.add(new ASN1OctetString(startTimeString)); 149 attributes.add(new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME, 150 startDateValues)); 151 } 152 153 // add dependency IDs 154 List<String> dependencyIds = information.getDependencyIds(); 155 if (dependencyIds != null && dependencyIds.size() > 0) { 156 ArrayList<ASN1OctetString> dependencyIdValues = 157 new ArrayList<ASN1OctetString>(dependencyIds.size()); 158 for (String dependencyId : dependencyIds) { 159 dependencyIdValues.add(new ASN1OctetString(dependencyId)); 160 } 161 attributes.add(new LDAPAttribute(ATTR_TASK_DEPENDENCY_IDS, 162 dependencyIdValues)); 163 164 // add the dependency action 165 FailedDependencyAction fda = information.getFailedDependencyAction(); 166 if (fda == null) { 167 fda = FailedDependencyAction.defaultValue(); 168 } 169 ArrayList<ASN1OctetString> fdaValues = 170 new ArrayList<ASN1OctetString>(1); 171 fdaValues.add(new ASN1OctetString(fda.name())); 172 attributes.add(new LDAPAttribute(ATTR_TASK_FAILED_DEPENDENCY_ACTION, 173 fdaValues)); 174 } 175 176 // add completion notification email addresses 177 List<String> compNotifEmailAddresss = 178 information.getNotifyUponCompletionEmailAddresses(); 179 if (compNotifEmailAddresss != null && compNotifEmailAddresss.size() > 0) { 180 ArrayList<ASN1OctetString> compNotifEmailAddrValues = 181 new ArrayList<ASN1OctetString>(compNotifEmailAddresss.size()); 182 for (String emailAddr : compNotifEmailAddresss) { 183 compNotifEmailAddrValues.add(new ASN1OctetString(emailAddr)); 184 } 185 attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_COMPLETION, 186 compNotifEmailAddrValues)); 187 } 188 189 // add error notification email addresses 190 List<String> errNotifEmailAddresss = 191 information.getNotifyUponErrorEmailAddresses(); 192 if (errNotifEmailAddresss != null && errNotifEmailAddresss.size() > 0) { 193 ArrayList<ASN1OctetString> errNotifEmailAddrValues = 194 new ArrayList<ASN1OctetString>(errNotifEmailAddresss.size()); 195 for (String emailAddr : errNotifEmailAddresss) { 196 errNotifEmailAddrValues.add(new ASN1OctetString(emailAddr)); 197 } 198 attributes.add(new LDAPAttribute(ATTR_TASK_NOTIFY_ON_ERROR, 199 errNotifEmailAddrValues)); 200 } 201 202 information.addTaskAttributes(attributes); 203 204 AddRequestProtocolOp addRequest = new AddRequestProtocolOp(entryDN, 205 attributes); 206 LDAPMessage requestMessage = 207 new LDAPMessage(nextMessageID.getAndIncrement(), addRequest, controls); 208 209 // Send the request to the server and read the response. 210 LDAPMessage responseMessage; 211 writer.writeMessage(requestMessage); 212 213 responseMessage = reader.readMessage(); 214 if (responseMessage == null) 215 { 216 throw new LDAPException( 217 LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, 218 ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get()); 219 } 220 221 if (responseMessage.getProtocolOpType() != 222 LDAPConstants.OP_TYPE_ADD_RESPONSE) 223 { 224 throw new LDAPException( 225 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 226 ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get( 227 responseMessage.getProtocolOpName())); 228 } 229 230 AddResponseProtocolOp addResponse = 231 responseMessage.getAddResponseProtocolOp(); 232 Message errorMessage = addResponse.getErrorMessage(); 233 if (errorMessage != null) { 234 throw new LDAPException( 235 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 236 errorMessage); 237 } 238 return getTaskEntry(taskID); 239 } 240 241 /** 242 * Gets all the ds-task entries from the task root. 243 * 244 * @return list of entries from the task root 245 * @throws IOException if there is a stream communication problem 246 * @throws LDAPException if there is a problem getting information 247 * out to the directory 248 * @throws ASN1Exception if there is a problem with the encoding 249 */ 250 public synchronized List<TaskEntry> getTaskEntries() 251 throws LDAPException, IOException, ASN1Exception { 252 List<Entry> entries = new ArrayList<Entry>(); 253 254 writeSearch(new SearchRequestProtocolOp( 255 new ASN1OctetString(ConfigConstants.DN_TASK_ROOT), 256 SearchScope.WHOLE_SUBTREE, 257 DereferencePolicy.NEVER_DEREF_ALIASES, 258 Integer.MAX_VALUE, 259 Integer.MAX_VALUE, 260 false, 261 LDAPFilter.decode("(objectclass=ds-task)"), 262 new LinkedHashSet<String>())); 263 264 LDAPReader reader = connection.getLDAPReader(); 265 byte opType; 266 do { 267 LDAPMessage responseMessage = reader.readMessage(); 268 if (responseMessage == null) { 269 throw new LDAPException( 270 LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, 271 ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get()); 272 } else { 273 opType = responseMessage.getProtocolOpType(); 274 if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) { 275 SearchResultEntryProtocolOp searchEntryOp = 276 responseMessage.getSearchResultEntryProtocolOp(); 277 SearchResultEntry entry = searchEntryOp.toSearchResultEntry(); 278 entries.add(entry); 279 } 280 } 281 } 282 while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE); 283 List<TaskEntry> taskEntries = new ArrayList<TaskEntry>(entries.size()); 284 for (Entry entry : entries) { 285 taskEntries.add(new TaskEntry(entry)); 286 } 287 return Collections.unmodifiableList(taskEntries); 288 } 289 290 /** 291 * Gets the entry of the task whose ID is <code>id</code> from the directory. 292 * 293 * @param id of the entry to retrieve 294 * @return Entry for the task 295 * @throws IOException if there is a stream communication problem 296 * @throws LDAPException if there is a problem getting information 297 * out to the directory 298 * @throws ASN1Exception if there is a problem with the encoding 299 * @throws TaskClientException if there is no task with the requested id 300 */ 301 public synchronized TaskEntry getTaskEntry(String id) 302 throws LDAPException, IOException, ASN1Exception, TaskClientException 303 { 304 Entry entry = null; 305 306 writeSearch(new SearchRequestProtocolOp( 307 new ASN1OctetString(ConfigConstants.DN_TASK_ROOT), 308 SearchScope.WHOLE_SUBTREE, 309 DereferencePolicy.NEVER_DEREF_ALIASES, 310 Integer.MAX_VALUE, 311 Integer.MAX_VALUE, 312 false, 313 LDAPFilter.decode("(ds-task-id=" + id + ")"), 314 new LinkedHashSet<String>())); 315 316 LDAPReader reader = connection.getLDAPReader(); 317 byte opType; 318 do { 319 LDAPMessage responseMessage = reader.readMessage(); 320 if (responseMessage == null) { 321 Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); 322 throw new LDAPException(UNAVAILABLE.getIntValue(), message); 323 } else { 324 opType = responseMessage.getProtocolOpType(); 325 if (opType == LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY) { 326 SearchResultEntryProtocolOp searchEntryOp = 327 responseMessage.getSearchResultEntryProtocolOp(); 328 entry = searchEntryOp.toSearchResultEntry(); 329 } 330 } 331 } 332 while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE); 333 if (entry == null) { 334 throw new TaskClientException(ERR_TASK_CLIENT_UNKNOWN_TASK.get(id)); 335 } 336 return new TaskEntry(entry); 337 } 338 339 340 /** 341 * Changes that the state of the task in the backend to a canceled state. 342 * 343 * @param id if the task to cancel 344 * @return Entry of the task before the modification 345 * @throws IOException if there is a stream communication problem 346 * @throws LDAPException if there is a problem getting information 347 * out to the directory 348 * @throws ASN1Exception if there is a problem with the encoding 349 * @throws TaskClientException if there is no task with the requested id 350 */ 351 public synchronized TaskEntry cancelTask(String id) 352 throws TaskClientException, IOException, ASN1Exception, LDAPException 353 { 354 LDAPReader reader = connection.getLDAPReader(); 355 LDAPWriter writer = connection.getLDAPWriter(); 356 357 TaskEntry entry = getTaskEntry(id); 358 TaskState state = entry.getTaskState(); 359 if (state != null) { 360 if (!TaskState.isDone(state)) { 361 362 ASN1OctetString dn = new ASN1OctetString(entry.getDN().toString()); 363 364 ArrayList<RawModification> mods = new ArrayList<RawModification>(); 365 366 ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>(); 367 String newState; 368 if (TaskState.isPending(state)) { 369 newState = TaskState.CANCELED_BEFORE_STARTING.name(); 370 } else { 371 newState = TaskState.STOPPED_BY_ADMINISTRATOR.name(); 372 } 373 values.add(new ASN1OctetString(newState)); 374 LDAPAttribute attr = new LDAPAttribute(ATTR_TASK_STATE, values); 375 mods.add(new LDAPModification(ModificationType.REPLACE, attr)); 376 377 // We have to reset the start time or the scheduler will 378 // reschedule to task. 379 // attr = new LDAPAttribute(ATTR_TASK_SCHEDULED_START_TIME); 380 // mods.add(new LDAPModification(ModificationType.DELETE, attr)); 381 382 ModifyRequestProtocolOp modRequest = 383 new ModifyRequestProtocolOp(dn, mods); 384 LDAPMessage requestMessage = 385 new LDAPMessage(nextMessageID.getAndIncrement(), modRequest, null); 386 387 writer.writeMessage(requestMessage); 388 389 LDAPMessage responseMessage = reader.readMessage(); 390 391 if (responseMessage == null) { 392 Message message = ERR_TASK_CLIENT_UNEXPECTED_CONNECTION_CLOSURE.get(); 393 throw new LDAPException(UNAVAILABLE.getIntValue(), message); 394 } 395 396 if (responseMessage.getProtocolOpType() != 397 LDAPConstants.OP_TYPE_MODIFY_RESPONSE) 398 { 399 throw new LDAPException( 400 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 401 ERR_TASK_CLIENT_INVALID_RESPONSE_TYPE.get( 402 responseMessage.getProtocolOpName())); 403 } 404 405 ModifyResponseProtocolOp modResponse = 406 responseMessage.getModifyResponseProtocolOp(); 407 Message errorMessage = modResponse.getErrorMessage(); 408 if (errorMessage != null) { 409 throw new LDAPException( 410 LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR, 411 errorMessage); 412 } 413 } else { 414 throw new TaskClientException( 415 ERR_TASK_CLIENT_UNCANCELABLE_TASK.get(id)); 416 } 417 } else { 418 throw new TaskClientException( 419 ERR_TASK_CLIENT_TASK_STATE_UNKNOWN.get(id)); 420 } 421 return getTaskEntry(id); 422 } 423 424 425 /** 426 * Writes a search to the directory writer. 427 * @param searchRequest to write 428 * @throws IOException if there is a stream communication problem 429 */ 430 private void writeSearch(SearchRequestProtocolOp searchRequest) 431 throws IOException { 432 LDAPWriter writer = connection.getLDAPWriter(); 433 LDAPMessage requestMessage = new LDAPMessage( 434 nextMessageID.getAndIncrement(), 435 searchRequest, 436 new ArrayList<LDAPControl>()); 437 438 // Send the request to the server and read the response. 439 writer.writeMessage(requestMessage); 440 } 441 442 }