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