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