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