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    
032    import org.opends.server.backends.task.Task;
033    import org.opends.server.backends.task.TaskState;
034    import org.opends.server.backends.task.FailedDependencyAction;
035    import org.opends.server.types.Entry;
036    import org.opends.server.types.AttributeType;
037    import org.opends.server.types.Attribute;
038    import org.opends.server.types.AttributeValue;
039    import org.opends.server.types.DN;
040    import static org.opends.server.util.ServerConstants.*;
041    
042    import java.util.Map;
043    import java.util.HashMap;
044    import java.util.Set;
045    import java.util.HashSet;
046    import java.util.List;
047    import java.util.LinkedHashSet;
048    import java.util.ArrayList;
049    import java.util.TimeZone;
050    import java.util.Date;
051    import java.util.Collections;
052    import java.lang.reflect.Method;
053    import java.text.SimpleDateFormat;
054    import java.text.DateFormat;
055    import java.text.ParseException;
056    
057    /**
058     * Processes information from a task entry from the directory and
059     * provides accessors for attribute information.  In some cases the
060     * data is formatted into more human-friendly formats.
061     */
062    public class TaskEntry {
063    
064      private static Map<String, Message> mapClassToTypeName =
065              new HashMap<String, Message>();
066    
067      private static Map<String, Message> mapAttrToDisplayName =
068              new HashMap<String, Message>();
069    
070    
071      // These attributes associated with the ds-task object
072      // class are all handled explicitly below in the constructor
073      private static Set<String> supAttrNames = new HashSet<String>();
074      static {
075        supAttrNames.add("ds-task-id");
076        supAttrNames.add("ds-task-class-name");
077        supAttrNames.add("ds-task-state");
078        supAttrNames.add("ds-task-scheduled-start-time");
079        supAttrNames.add("ds-task-actual-start-time");
080        supAttrNames.add("ds-task-completion-time");
081        supAttrNames.add("ds-task-dependency-id");
082        supAttrNames.add("ds-task-failed-dependency-action");
083        supAttrNames.add("ds-task-log-message");
084        supAttrNames.add("ds-task-notify-on-completion");
085        supAttrNames.add("ds-task-notify-on-error");
086      }
087    
088      private String id;
089      private String className;
090      private String state;
091      private String schedStart;
092      private String actStart;
093      private String compTime;
094      private List<String> depends;
095      private String depFailAct;
096      private List<String> logs;
097      private List<String> notifyComp;
098      private List<String> notifyErr;
099      private DN dn;
100    
101      /**
102       * Task of the same type that implements.  Used for obtaining
103       * task name and attribute display information.
104       */
105      private Task task;
106    
107      private Map<Message, List<String>> taskSpecificAttrValues =
108              new HashMap<Message, List<String>>();
109    
110      /**
111       * Creates a parameterized instance.
112       *
113       * @param entry to wrap
114       */
115      public TaskEntry(Entry entry) {
116        dn = entry.getDN();
117    
118        String p = "ds-task-";
119        id =         getSingleStringValue(entry, p + "id");
120        className =  getSingleStringValue(entry, p + "class-name");
121        state =      getSingleStringValue(entry, p + "state");
122        schedStart = getSingleStringValue(entry, p + "scheduled-start-time");
123        actStart =   getSingleStringValue(entry, p + "actual-start-time");
124        compTime =   getSingleStringValue(entry, p + "completion-time");
125        depends =    getMultiStringValue(entry,  p + "dependency-id");
126        depFailAct = getSingleStringValue(entry, p + "failed-dependency-action");
127        logs =       getMultiStringValue(entry,  p + "log-message");
128        notifyErr =  getMultiStringValue(entry,  p + "notify-on-error");
129        notifyComp = getMultiStringValue(entry,  p + "notify-on-completion");
130    
131    
132        // Build a map of non-superior attribute value pairs for display
133        Map<AttributeType, List<Attribute>> attrMap = entry.getUserAttributes();
134        for (AttributeType type : attrMap.keySet()) {
135          String typeName = type.getNormalizedPrimaryName();
136    
137          // See if we've handled it already above
138          if (!supAttrNames.contains(typeName)) {
139            Message attrTypeName = getAttributeDisplayName(
140                    type.getNormalizedPrimaryName());
141            List<Attribute> attrList = entry.getUserAttribute(type);
142            for (Attribute attr : attrList) {
143              LinkedHashSet<AttributeValue> valuesSet = attr.getValues();
144              for (AttributeValue av : valuesSet) {
145                List<String> valueList = taskSpecificAttrValues.get(attrTypeName);
146                if (valueList == null) {
147                  valueList = new ArrayList<String>();
148                  taskSpecificAttrValues.put(attrTypeName, valueList);
149                }
150                valueList.add(av.getStringValue());
151              }
152            }
153          }
154        }
155      }
156    
157      /**
158       * Gets the DN of the wrapped entry.
159       *
160       * @return DN of entry
161       */
162      public DN getDN() {
163        return dn;
164      }
165    
166      /**
167       * Gets the ID of the task.
168       *
169       * @return String ID of the task
170       */
171      public String getId() {
172        return id;
173      }
174    
175      /**
176       * Gets the name of the class implementing the task represented here.
177       *
178       * @return String name of class
179       */
180      public String getClassName() {
181        return className;
182      }
183    
184      /**
185       * Gets the state of the task.
186       *
187       * @return Message representing state
188       */
189      public Message getState() {
190        Message m = Message.EMPTY;
191        if (state != null) {
192          TaskState ts = TaskState.fromString(state);
193          if (ts != null) {
194            m = ts.getDisplayName();
195          }
196        }
197        return m;
198      }
199    
200      /**
201       * Gets the human-friendly scheduled time.
202       *
203       * @return String time
204       */
205      public Message getScheduledStartTime() {
206        return formatTimeString(schedStart);
207      }
208    
209      /**
210       * Gets the human-friendly start time.
211       *
212       * @return String time
213       */
214      public Message getActualStartTime() {
215        return formatTimeString(actStart);
216      }
217    
218      /**
219       * Gets the human-friendly completion time.
220       *
221       * @return String time
222       */
223      public Message getCompletionTime() {
224        return formatTimeString(compTime);
225      }
226    
227      /**
228       * Gets the IDs of tasks upon which this task depends.
229       *
230       * @return array of IDs
231       */
232      public List<String> getDependencyIds() {
233        return Collections.unmodifiableList(depends);
234      }
235    
236      /**
237       * Gets the action to take if this task fails.
238       *
239       * @return String action
240       */
241      public Message getFailedDependencyAction() {
242        Message m = null;
243        if (depFailAct != null) {
244          FailedDependencyAction fda =
245                  FailedDependencyAction.fromString(depFailAct);
246          if (fda != null) {
247            m = fda.getDisplayName();
248          }
249        }
250        return m;
251      }
252    
253      /**
254       * Gets the logs associated with this task's execution.
255       *
256       * @return array of log messages
257       */
258      public List<Message> getLogMessages() {
259        List<Message> formattedLogs = new ArrayList<Message>();
260        for (String aLog : logs) {
261          formattedLogs.add(Message.raw(aLog));
262        }
263        return Collections.unmodifiableList(formattedLogs);
264      }
265    
266      /**
267       * Gets the email messages that will be used for notifications
268       * when the task completes.
269       *
270       * @return array of email addresses
271       */
272      public List<String> getCompletionNotificationEmailAddresses() {
273        return Collections.unmodifiableList(notifyComp);
274      }
275    
276      /**
277       * Gets the email messages that will be used for notifications
278       * when the task encounters an error.
279       *
280       * @return array of email addresses
281       */
282      public List<String> getErrorNotificationEmailAddresses() {
283        return Collections.unmodifiableList(notifyErr);
284      }
285    
286      /**
287       * Gets a user presentable string indicating the type of this task.
288       *
289       * @return Message type
290       */
291      public Message getType() {
292        Message type = Message.EMPTY;
293        if (className != null) {
294          type = mapClassToTypeName.get(className);
295          if (type == null) {
296            Task task = getTask();
297            if (task != null) {
298              try {
299                Method m = Task.class.getMethod("getDisplayName");
300                Object oName = m.invoke(task);
301                if (oName instanceof Message) {
302                  mapClassToTypeName.put(className, (Message) oName);
303                  type = (Message) oName;
304                }
305              } catch (Exception e) {
306                // ignore; this is best effort
307              }
308            }
309          }
310    
311          // If we still can't get the type just resort
312          // to the class displayName
313          if (type == null) {
314            type = Message.raw(className);
315          }
316        }
317        return type;
318      }
319    
320      /**
321       * Indicates whether or not this task supports a cancel operation.
322       *
323       * @return boolean where true means this task supports being canceled.
324       */
325      public boolean isCancelable() {
326        boolean cancelable = false;
327        TaskState state = getTaskState();
328        if (state != null) {
329          Task task = getTask();
330          cancelable = (TaskState.isPending(state) ||
331                  (TaskState.isRunning(state) &&
332                          task != null &&
333                          task.isInterruptable()));
334        }
335        return cancelable;
336      }
337    
338      /**
339       * Gets a mapping of attributes that are specific to the implementing
340       * task as opposed to the superior, or base, task.
341       *
342       * @return mapping of atribute field labels to lists of string values for
343       *         each field.
344       */
345      public Map<Message, List<String>> getTaskSpecificAttributeValuePairs() {
346        return taskSpecificAttrValues;
347      }
348    
349      /**
350       * Gets the task state.
351       *
352       * @return TaskState of task
353       */
354      public TaskState getTaskState() {
355        TaskState ts = null;
356        if (state != null) {
357          ts = TaskState.fromString(state);
358        }
359        return ts;
360      }
361    
362      /**
363       * Indicates whether or not this task is done.
364       *
365       * @return boolean where true means this task is done
366       */
367      public boolean isDone() {
368        TaskState ts = getTaskState();
369        return ts != null && TaskState.isDone(ts);
370      }
371    
372      private String getSingleStringValue(Entry entry, String attrName) {
373        List<Attribute> attrList = entry.getAttribute(attrName);
374        if (attrList != null && attrList.size() == 1) {
375          Set<AttributeValue> values = attrList.get(0).getValues();
376          if (values != null && values.size() == 1) {
377            return values.iterator().next().getStringValue();
378          }
379        }
380        return "";
381      }
382    
383      private List<String> getMultiStringValue(Entry entry, String attrName) {
384        List<String> valuesList = new ArrayList<String>();
385        List<Attribute> attrList = entry.getAttribute(attrName);
386        if (attrList != null) {
387          for (Attribute attr : attrList) {
388            Set<AttributeValue> values = attr.getValues();
389            if (values != null) {
390              for (AttributeValue value : values) {
391                valuesList.add(value.getStringValue());
392              }
393            }
394          }
395        }
396        return valuesList;
397      }
398    
399      private Message getAttributeDisplayName(String attrName) {
400        Message name = mapAttrToDisplayName.get(attrName);
401        if (name == null) {
402          Task task = getTask();
403          if (task != null) {
404            try {
405              Method m = Task.class.getMethod(
406                      "getAttributeDisplayName", String.class);
407              Object o = m.invoke(task, attrName);
408              if (o != null && Message.class.isAssignableFrom(o.getClass())) {
409                name= (Message)o;
410                mapAttrToDisplayName.put(attrName, name);
411              }
412            } catch (Exception e) {
413              // ignore
414            }
415          }
416        }
417        if (name == null) {
418          name = Message.raw(attrName);
419        }
420        return name;
421      }
422    
423      /**
424       * Formats a time string into a human friendly format.
425       * @param timeString the is human hostile
426       * @return string of time that is human friendly
427       */
428      private Message formatTimeString(String timeString) {
429        Message ret = Message.EMPTY;
430        if (timeString != null && timeString.length() > 0) {
431          try {
432            SimpleDateFormat dateFormat;
433            if (timeString.endsWith("Z")) {
434              dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
435              dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
436            } else {
437              dateFormat = new SimpleDateFormat(DATE_FORMAT_COMPACT_LOCAL_TIME);
438            }
439            Date date = dateFormat.parse(timeString);
440            DateFormat df = DateFormat.getDateTimeInstance(
441                    DateFormat.MEDIUM,
442                    DateFormat.LONG);
443            String dateString = df.format(date);
444            ret = Message.raw(dateString);
445          } catch (ParseException pe){
446            ret = Message.raw(timeString);
447          }
448        }
449        return ret;
450      }
451    
452      private Task getTask() {
453        if (task == null && className != null) {
454          try {
455            Class<?> clazz = Class.forName(className);
456            Object o = clazz.newInstance();
457            if (Task.class.isAssignableFrom(o.getClass())) {
458              this.task = (Task) o;
459            }
460          } catch (Exception e) {
461            // ignore; this is best effort
462          }
463        }
464        return task;
465      }
466    
467    }