/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.model; import java.text.MessageFormat; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.eclipse.skalli.commons.ComparatorUtils; /** * Class for reporting of validation issues. * A validation issue always has a {@link Severity severity}, is assigned to a certain entity * and has a message. If no explicit message is defined, a default message is assigned to * an issue.<br> * Besides that an issue may be related to a certain extension and/or to a certain property * of that extension. Optionally an issue can have a description (e.g. a hint how to solve * the issue) and a timestamp. */ public class Issue implements Comparable<Issue> { private Severity severity; private Class<? extends Issuer> issuer; private UUID entityId; private Class<? extends ExtensionEntityBase> extension; private Object propertyId; private int item; private String message; private String description; private long timestamp; /** * Default constructor. Required for XML streaming. */ public Issue() { } /** * Creates an issue with the given <code>severity</code> for an entity specified by * its unique identifier. The issue is created with a default message. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param entityId the unique identifier of the entity that causes this validation issue. */ public Issue(Severity severity, Class<? extends Issuer> issuer, UUID entityId) { this(severity, issuer, entityId, null, null, 0, null); } /** * Creates an issue with the given <code>severity</code> and custom message. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param message the message of the issue. */ public Issue(Severity severity, Class<? extends Issuer> issuer, String message) { this(severity, issuer, null, null, null, 0, message); } /** * Creates an issue with the given <code>severity</code> and custom message for an entity * specified by its unique identifier. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param entityId the unique identifier of the entity that causes this validation issue. * @param message the message of the the issue. */ public Issue(Severity severity, Class<? extends Issuer> issuer, UUID entityId, String message) { this(severity, issuer, entityId, null, null, 0, message); } /** * Creates an issue with the given <code>severity</code> for a property of a model extension that is * assigned to an entity specified by its unique identifier. The issue is created with a default message. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param entityId the unique identifier of an entity. * @param extension the class of a model extension, or <code>null</code>. * @param propertyId the property that causes this validation issue, or <code>null</code>. */ public Issue(Severity severity, Class<? extends Issuer> issuer, UUID entityId, Class<? extends ExtensionEntityBase> extension, Object propertyId) { this(severity, issuer, entityId, extension, propertyId, 0, null); } /** * Creates an issue with the given <code>severity</code> and custom message for a property of a model * extension that is assigned to an entity specified by its unique identifier. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param entityId the unique identifier of the entity that causes this validation issue. * @param extension the class of a model extension, or <code>null</code>. * @param propertyId the property that causes this validation issue, or <code>null</code>. * @param message the message of the the issue. */ public Issue(Severity severity, Class<? extends Issuer> issuer, UUID entityId, Class<? extends ExtensionEntityBase> extension, Object propertyId, String message) { this(severity, issuer, entityId, extension, propertyId, 0, message); } /** * Creates an issue with the given <code>severity</code> and custom message for a property of a model * extension that is assigned to an entity specified by its unique identifier. * * @param severity the severity of the issue. * @param issuer the issuer that raises this issue, e.g. a validator. * @param entityId the unique identifier of the entity that causes this validation issue. * @param extension the class of a model extension, or <code>null</code>. * @param propertyId the property that causes this validation issue, or <code>null</code>. * @param item a unique item number that distinguishes issues related to the * same property/extension/entity/issuer. * @param message the message of the the issue. */ public Issue(Severity severity, Class<? extends Issuer> issuer, UUID entityId, Class<? extends ExtensionEntityBase> extension, Object propertyId, int item, String message) { if (severity == null) { throw new IllegalArgumentException("argument 'severity' must not be null"); } if (issuer == null) { throw new IllegalArgumentException("argument 'issuer' must not be null"); } this.severity = severity; this.issuer = issuer; this.entityId = entityId; this.extension = extension; this.propertyId = propertyId; this.item = item; this.message = message; } /** * Returns the severity of this issue. */ public Severity getSeverity() { return severity; } /** * Returns the issuer that raised this issue, e.g. a validator. */ public Class<? extends Issuer> getIssuer() { return issuer; } /** * Returns the unique identifier of the entity that causes this issue. */ public UUID getEntityId() { return entityId; } /** * Returns the message of this issue. If no custom message has been defined, * a default message is created from the other parameters of the issue. */ public String getMessage() { if (StringUtils.isNotBlank(message)) { return message; } String msg = null; if (entityId != null) { if (extension != null) { if (propertyId != null) { msg = MessageFormat.format("Property {0} of extension {1} of entity {2} is invalid", propertyId, extension.getName(), entityId); } else { msg = MessageFormat.format("Extension {0} of entity {1} is invalid", extension.getName(), entityId); } } else { msg = MessageFormat.format("Entity {0} is invalid", entityId); } } else { if (extension != null) { if (propertyId != null) { msg = MessageFormat.format("Property {0} of extension {1} is invalid", propertyId, extension.getName()); } else { msg = MessageFormat.format("Extension {0} is invalid", extension.getName()); } } else { msg = "invalid"; } } return msg; } /** * Sets the message of this issue. */ public void setMessage(String message) { this.message = message; } /** * Returns the class of the model extension that causes this issue. */ public Class<? extends ExtensionEntityBase> getExtension() { return extension; } /** * Sets the class of the model extension that causes this issue. */ public void setExtension(Class<? extends ExtensionEntityBase> extension) { this.extension = extension; } /** * Returns the identifier of the property that causes this issue. * @see {@link org.eclipse.skalli.services.projects.PropertyName} */ public Object getPropertyId() { return propertyId; } /** * Sets the identifier of the property that causes this issue. * @see {@link org.eclipse.skalli.services.projects.PropertyName} */ public void setPropertyId(Object propertyId) { this.propertyId = propertyId; } /** * Returns a unique item number that distinguishes issues related * to the same property/extension/entity/issuer. */ public int getItem() { return item; } /** * Sets a unique item number that distinguishes issues related * to the same property/extension/entity/issuer. */ public void setItem(int item) { this.item = item; } /** * Returns the description of this issue. */ public String getDescription() { return description; } /** * Sets the description of this issue. */ public void setDescription(String description) { this.description = description; } /** * Returns the timestanp of this issue. */ public long getTimestamp() { return timestamp; } /** * Sets the timestanp of this issue. */ public void setTimestamp(long timestamp) { this.timestamp = timestamp; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((entityId == null) ? 0 : entityId.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof Issue) { return compareTo((Issue) obj) == 0; } return false; } /** * Compares two issues according to their severity ({@link Severity.FATAL} first), * entity id, extension class name, property id and issuer (in that order). * Issues related to a whole entity (no extension, no property) are consider to be less * than issues related to a whole extension (extension set, but no property). * Issues related to a whole extension (extension set, but no property) are consider to be less * than issues related to a certain property (extension and property both). */ @Override public int compareTo(Issue issue) { int result = severity.compareTo(issue.severity); if (result == 0) { result = ComparatorUtils.compare(entityId, issue.entityId); if (result == 0) { result = ComparatorUtils.compareAsStrings(extension, issue.extension); if (result == 0) { result = ComparatorUtils.compareAsStrings(propertyId, issue.propertyId); if (result == 0) { result = ComparatorUtils.compare(issuer.getName(), issue.issuer.getName()); if (result == 0) { result = item < issue.item ? -1 : (item == issue.item ? 0 : 1); } } } } } return result; } /** * This method returns {@link #getMessage()}. */ @Override public String toString() { return getMessage(); } /** * Composes a message from a given message and the {@link Issue#getMessage() detail messages * of the given issues. The messages of the issues are appended in form of a bulleted list * (using <tt>"-"</tt> as bullet) in the order defined by {@link Issue#compareTo(Issue)}. * If no explicit <code>message</code> is specified, then only the list of issue messages * is returned. If there is only a single issue given, then {@link Issue#getMessage()} is * returned without leading bullet. */ @SuppressWarnings("nls") public static String getMessage(String message, SortedSet<Issue> issues) { StringBuilder sb = new StringBuilder(); boolean hasMessage = StringUtils.isNotBlank(message); if (hasMessage) { sb.append(message); } if (issues != null) { int n = issues.size(); int i = 0; for (Issue issue : issues) { message = issue.getMessage(); if (StringUtils.isNotBlank(message)) { if (hasMessage && i == 0 || i > 0) { sb.append("\n"); } if (hasMessage || n > 1) { sb.append(" - "); } sb.append(message); ++i; } } } return sb.toString(); } /** * Renders the given message and set of issues as HTML bulleted list (<ut>) with * the message as caption. If not message is specified, only the bulleted list is rendered. * Example: * <ul> * <li style="color:#8a1f11;background-color:#fbe3e4;border-color:#fbc2c4;border-style:solid;border-width:1px; * margin: 2px 0px 2px 0px;list-style-type:none;padding:2px 2px 2px 15px;width:300px;min-height:20px;"> * <b>ERROR</b>  Description must not be empty</li> * </ul> * * @param message the message to render as caption of the list, or <code>null</code>. * @param issues the set of issues to render, or <code>null</code>. */ public static String asHTMLList(String message, Set<Issue> issues) { return asHTMLList(message, issues, null); } /** * Renders the given message and set of issues as HTML bulleted list (<ut>) with * the message as caption. If not message is specified, only the bulleted list is rendered. * Example: * <ul> * <li style="color:#8a1f11;background-color:#fbe3e4;border-color:#fbc2c4;border-style:solid;border-width:1px; * margin: 2px 0px 2px 0px;list-style-type:none;padding:2px 2px 2px 15px;width:600px;min-height:20px;"> * <b>ERROR</b>  Development Infrastructure: SCM location is invalid</li> * </ul> * @param message the message to render as caption of the list, or <code>null</code>. * @param issues the set of issues to render, or <code>null</code>. * @param displayNames the display names of extensions that are referenced by the given issues. If for * a given issue the display name of the extension it belongs to is known, the display name is * rendered (as link with <tt>href="#<extensionName>"</tt>) between the severity label an the * message of the issue. */ @SuppressWarnings("nls") public static String asHTMLList(String message, Set<Issue> issues, Map<String, String> displayNames) { StringBuilder sb = new StringBuilder(); if (StringUtils.isNotBlank(message)) { sb.append(message); } if (issues != null && issues.size() > 0) { sb.append("<ul>"); for (Issue issue : issues) { sb.append("<li class=\"").append(issue.getSeverity().name()).append("\">"); sb.append("<strong>").append(issue.getSeverity().name()).append("</strong>  "); if (displayNames != null) { Class<? extends ExtensionEntityBase> extension = issue.getExtension(); if (extension != null && displayNames.containsKey(extension.getName())) { sb.append("<a href=\"#" + extension.getName() + "\">"); sb.append(displayNames.get(extension.getName())).append("</a>: "); } } sb.append(issue.getMessage()); sb.append("</li>"); } sb.append("</ul>"); } return sb.toString(); } /** * Returns issues that are equal of more serious than the given <code>minSeverity</code>. * * @param minSeverity the minimal severity of issues to include in the result. * @return a set of issues sorted by {@link Issue#compareTo(Issue)}, or an empty set. */ public static SortedSet<Issue> filterBySeverity(SortedSet<Issue> issues, Severity minSeverity) { TreeSet<Issue> result = new TreeSet<Issue>(); if (issues != null) { for (Issue issue : issues) { if (issue.getSeverity().compareTo(minSeverity) <= 0) { result.add(issue); } } } return result; } /** * Returns <code>true</code>, if {@link Severity#FATAL} issues are present * in the given set of issues. */ public static boolean hasFatalIssues(SortedSet<Issue> issues) { return filterBySeverity(issues, Severity.FATAL).size() > 0; } }