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 }