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}