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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.tools.tasks;
028
029import org.forgerock.i18n.LocalizableMessage;
030import org.forgerock.opendj.ldap.ByteString;
031import org.opends.server.backends.task.FailedDependencyAction;
032import org.opends.server.backends.task.Task;
033import org.opends.server.backends.task.TaskState;
034import org.opends.server.types.Attribute;
035import org.opends.server.types.AttributeType;
036import org.opends.server.types.DN;
037import org.opends.server.types.Entry;
038
039import java.util.ArrayList;
040import java.util.Collections;
041import java.util.Date;
042import java.util.HashMap;
043import java.util.HashSet;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047import java.util.TimeZone;
048import java.lang.reflect.Method;
049import java.text.DateFormat;
050import java.text.ParseException;
051import java.text.SimpleDateFormat;
052
053import static org.opends.server.util.ServerConstants.*;
054
055/**
056 * Processes information from a task entry from the directory and
057 * provides accessors for attribute information.  In some cases the
058 * data is formatted into more human-friendly formats.
059 */
060public class TaskEntry {
061
062  private static Map<String, LocalizableMessage> mapClassToTypeName = new HashMap<>();
063  private static Map<String, LocalizableMessage> mapAttrToDisplayName = new HashMap<>();
064
065  private int hashCode;
066
067  /**
068   * These attributes associated with the ds-task object
069   * class are all handled explicitly below in the constructor.
070   */
071  private static Set<String> supAttrNames = new HashSet<>();
072  static {
073    supAttrNames.add("ds-task-id");
074    supAttrNames.add("ds-task-class-name");
075    supAttrNames.add("ds-task-state");
076    supAttrNames.add("ds-task-scheduled-start-time");
077    supAttrNames.add("ds-task-actual-start-time");
078    supAttrNames.add("ds-task-completion-time");
079    supAttrNames.add("ds-task-dependency-id");
080    supAttrNames.add("ds-task-failed-dependency-action");
081    supAttrNames.add("ds-task-log-message");
082    supAttrNames.add("ds-task-notify-on-completion");
083    supAttrNames.add("ds-task-notify-on-error");
084    supAttrNames.add("ds-recurring-task-id");
085    supAttrNames.add("ds-recurring-task-schedule");
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 String schedTab;
095  private List<String> depends;
096  private String depFailAct;
097  private List<String> logs;
098  private List<String> notifyComp;
099  private List<String> notifyErr;
100  private DN dn;
101
102  /**
103   * Task of the same type that implements.  Used for obtaining
104   * task name and attribute display information.
105   */
106  private Task task;
107
108  private Map<LocalizableMessage, List<String>> taskSpecificAttrValues = new HashMap<>();
109
110  /**
111   * Creates a parameterized instance.
112   *
113   * @param entry to wrap
114   */
115  public TaskEntry(Entry entry) {
116    dn = entry.getName();
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    schedTab =   getSingleStringValue(entry, "ds-recurring-task-schedule");
131
132
133    // Build a map of non-superior attribute value pairs for display
134    Map<AttributeType, List<Attribute>> attrMap = entry.getUserAttributes();
135    for (AttributeType type : attrMap.keySet()) {
136      String typeName = type.getNameOrOID();
137
138      // See if we've handled it already above
139      if (!supAttrNames.contains(typeName)) {
140        LocalizableMessage attrTypeName = getAttributeDisplayName(typeName);
141        List<Attribute> attrList = entry.getUserAttribute(type);
142        for (Attribute attr : attrList) {
143          for (ByteString av : attr) {
144            List<String> valueList = taskSpecificAttrValues.get(attrTypeName);
145            if (valueList == null) {
146              valueList = new ArrayList<>();
147              taskSpecificAttrValues.put(attrTypeName, valueList);
148            }
149            valueList.add(av.toString());
150          }
151        }
152      }
153    }
154    hashCode += id.hashCode();
155    hashCode += className.hashCode();
156    hashCode += state.hashCode();
157    hashCode += schedStart.hashCode();
158    hashCode += actStart.hashCode();
159    hashCode += compTime.hashCode();
160    hashCode += depends.hashCode();
161    hashCode += depFailAct.hashCode();
162    hashCode += logs.hashCode();
163    hashCode += notifyErr.hashCode();
164    hashCode += notifyComp.hashCode();
165    hashCode += schedTab.hashCode();
166    hashCode += taskSpecificAttrValues.hashCode();
167  }
168
169  /**
170   * Retrieves a hash code for this task entry.
171   *
172   * @return  The hash code for this task entry.
173   */
174  @Override
175  public int hashCode()
176  {
177    return hashCode;
178  }
179
180  /** {@inheritDoc} */
181  @Override
182  public boolean equals(Object o)
183  {
184    if (this == o)
185    {
186      return true;
187    }
188
189    if (o == null)
190    {
191      return false;
192    }
193
194    if (! (o instanceof TaskEntry))
195    {
196      return false;
197    }
198
199    TaskEntry e = (TaskEntry) o;
200
201    return e.id.equals(id) &&
202    e.className.equals(className) &&
203    e.state.equals(state) &&
204    e.schedStart.equals(schedStart) &&
205    e.actStart.equals(actStart) &&
206    e.compTime.equals(compTime) &&
207    e.depends.equals(depends) &&
208    e.depFailAct.equals(depFailAct) &&
209    e.logs.equals(logs) &&
210    e.notifyErr.equals(notifyErr) &&
211    e.notifyComp.equals(notifyComp) &&
212    e.schedTab.equals(schedTab) &&
213    e.taskSpecificAttrValues.equals(taskSpecificAttrValues);
214  }
215
216  /**
217   * Gets the DN of the wrapped entry.
218   *
219   * @return DN of entry
220   */
221  public DN getDN() {
222    return dn;
223  }
224
225  /**
226   * Gets the ID of the task.
227   *
228   * @return String ID of the task
229   */
230  public String getId() {
231    return id;
232  }
233
234  /**
235   * Gets the name of the class implementing the task represented here.
236   *
237   * @return String name of class
238   */
239  public String getClassName() {
240    return className;
241  }
242
243  /**
244   * Gets the state of the task.
245   *
246   * @return LocalizableMessage representing state
247   */
248  public LocalizableMessage getState() {
249    LocalizableMessage m = LocalizableMessage.EMPTY;
250    if (state != null) {
251      TaskState ts = TaskState.fromString(state);
252      if (ts != null) {
253        m = ts.getDisplayName();
254      }
255    }
256    return m;
257  }
258
259  /**
260   * Gets the human-friendly scheduled time.
261   *
262   * @return String time
263   */
264  public LocalizableMessage getScheduledStartTime() {
265    return formatTimeString(schedStart);
266  }
267
268  /**
269   * Gets the human-friendly start time.
270   *
271   * @return String time
272   */
273  public LocalizableMessage getActualStartTime() {
274    return formatTimeString(actStart);
275  }
276
277  /**
278   * Gets the human-friendly completion time.
279   *
280   * @return String time
281   */
282  public LocalizableMessage getCompletionTime() {
283    return formatTimeString(compTime);
284  }
285
286  /**
287   * Gets recurring schedule tab.
288   *
289   * @return LocalizableMessage tab string
290   */
291  public LocalizableMessage getScheduleTab() {
292    return LocalizableMessage.raw(schedTab);
293  }
294
295  /**
296   * Gets the IDs of tasks upon which this task depends.
297   *
298   * @return array of IDs
299   */
300  public List<String> getDependencyIds() {
301    return Collections.unmodifiableList(depends);
302  }
303
304  /**
305   * Gets the action to take if this task fails.
306   *
307   * @return String action
308   */
309  public LocalizableMessage getFailedDependencyAction() {
310    LocalizableMessage m = null;
311    if (depFailAct != null) {
312      FailedDependencyAction fda =
313              FailedDependencyAction.fromString(depFailAct);
314      if (fda != null) {
315        m = fda.getDisplayName();
316      }
317    }
318    return m;
319  }
320
321  /**
322   * Gets the logs associated with this task's execution.
323   *
324   * @return array of log messages
325   */
326  public List<LocalizableMessage> getLogMessages() {
327    List<LocalizableMessage> formattedLogs = new ArrayList<>();
328    for (String aLog : logs) {
329      formattedLogs.add(LocalizableMessage.raw(aLog));
330    }
331    return Collections.unmodifiableList(formattedLogs);
332  }
333
334  /**
335   * Gets the email messages that will be used for notifications
336   * when the task completes.
337   *
338   * @return array of email addresses
339   */
340  public List<String> getCompletionNotificationEmailAddresses() {
341    return Collections.unmodifiableList(notifyComp);
342  }
343
344  /**
345   * Gets the email messages that will be used for notifications
346   * when the task encounters an error.
347   *
348   * @return array of email addresses
349   */
350  public List<String> getErrorNotificationEmailAddresses() {
351    return Collections.unmodifiableList(notifyErr);
352  }
353
354  /**
355   * Gets a user presentable string indicating the type of this task.
356   *
357   * @return LocalizableMessage type
358   */
359  public LocalizableMessage getType() {
360    LocalizableMessage type = LocalizableMessage.EMPTY;
361    if (className != null) {
362      type = mapClassToTypeName.get(className);
363      if (type == null) {
364        Task task = getTask();
365        if (task != null) {
366          LocalizableMessage message = task.getDisplayName();
367          mapClassToTypeName.put(className, message);
368          type = message;
369        }
370      }
371
372      // If we still can't get the type just resort
373      // to the class displayName
374      if (type == null) {
375        type = LocalizableMessage.raw(className);
376      }
377    }
378    return type;
379  }
380
381  /**
382   * Indicates whether or not this task supports a cancel operation.
383   *
384   * @return boolean where true means this task supports being canceled.
385   */
386  public boolean isCancelable() {
387    TaskState state = getTaskState();
388    if (state != null) {
389      Task task = getTask();
390      return TaskState.isPending(state)
391          || TaskState.isRecurring(state)
392          || (TaskState.isRunning(state)
393              && task != null
394              && task.isInterruptable());
395    }
396    return false;
397  }
398
399  /**
400   * Gets a mapping of attributes that are specific to the implementing
401   * task as opposed to the superior, or base, task.
402   *
403   * @return mapping of attribute field labels to lists of string values for each field.
404   */
405  public Map<LocalizableMessage, List<String>> getTaskSpecificAttributeValuePairs() {
406    return taskSpecificAttrValues;
407  }
408
409  /**
410   * Gets the task state.
411   *
412   * @return TaskState of task
413   */
414  public TaskState getTaskState() {
415    TaskState ts = null;
416    if (state != null) {
417      ts = TaskState.fromString(state);
418    }
419    return ts;
420  }
421
422  /**
423   * Indicates whether or not this task is done.
424   *
425   * @return boolean where true means this task is done
426   */
427  public boolean isDone() {
428    TaskState ts = getTaskState();
429    return ts != null && TaskState.isDone(ts);
430  }
431
432  private String getSingleStringValue(Entry entry, String attrName) {
433    List<Attribute> attrList = entry.getAttribute(attrName);
434    if (attrList != null && attrList.size() == 1) {
435      Attribute attr = attrList.get(0);
436      if (!attr.isEmpty()) {
437        return attr.iterator().next().toString();
438      }
439    }
440    return "";
441  }
442
443  private List<String> getMultiStringValue(Entry entry, String attrName) {
444    List<String> valuesList = new ArrayList<>();
445    List<Attribute> attrList = entry.getAttribute(attrName);
446    if (attrList != null) {
447      for (Attribute attr : attrList) {
448        for (ByteString value : attr) {
449          valuesList.add(value.toString());
450        }
451      }
452    }
453    return valuesList;
454  }
455
456  private LocalizableMessage getAttributeDisplayName(String attrName) {
457    LocalizableMessage name = mapAttrToDisplayName.get(attrName);
458    if (name == null) {
459      Task task = getTask();
460      if (task != null) {
461        try {
462          Method m = Task.class.getMethod(
463                  "getAttributeDisplayName", String.class);
464          Object o = m.invoke(task, attrName);
465          if (o != null && LocalizableMessage.class.isAssignableFrom(o.getClass())) {
466            name= (LocalizableMessage)o;
467            mapAttrToDisplayName.put(attrName, name);
468          }
469        } catch (Exception e) {
470          // ignore
471        }
472      }
473    }
474    if (name == null) {
475      name = LocalizableMessage.raw(attrName);
476    }
477    return name;
478  }
479
480  /**
481   * Formats a time string into a human friendly format.
482   * @param timeString the is human hostile
483   * @return string of time that is human friendly
484   */
485  private LocalizableMessage formatTimeString(String timeString) {
486    LocalizableMessage ret = LocalizableMessage.EMPTY;
487    if (timeString != null && timeString.length() > 0) {
488      try {
489        SimpleDateFormat dateFormat;
490        if (timeString.endsWith("Z")) {
491          dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
492          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
493        } else {
494          dateFormat = new SimpleDateFormat(DATE_FORMAT_COMPACT_LOCAL_TIME);
495        }
496        Date date = dateFormat.parse(timeString);
497        DateFormat df = DateFormat.getDateTimeInstance(
498                DateFormat.MEDIUM,
499                DateFormat.LONG);
500        String dateString = df.format(date);
501        ret = LocalizableMessage.raw(dateString);
502      } catch (ParseException pe){
503        ret = LocalizableMessage.raw(timeString);
504      }
505    }
506    return ret;
507  }
508
509  private Task getTask() {
510    if (task == null && className != null) {
511      try {
512        Class<?> clazz = Class.forName(className);
513        Object o = clazz.newInstance();
514        if (Task.class.isAssignableFrom(o.getClass())) {
515          this.task = (Task) o;
516        }
517      } catch (Exception e) {
518        // ignore; this is best effort
519      }
520    }
521    return task;
522  }
523
524}