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}