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    }