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}