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