
com.almworks.jira.structure.api.StructureException Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api;
import com.almworks.jira.structure.api2g.item.ItemIdentity;
import com.almworks.jira.structure.util.StructureUtil;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.MutableIssue;
import com.atlassian.jira.user.ApplicationUser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
/**
* {@code StructureException} can be thrown for different causes that involve Structure plugin. It usually means that the
* operation that is traced back to the user's action, cannot be performed and should be reported as error.
*
* Each exception is associated with a specific value from enumeration {@link StructureError}. Exception may also
* carry additional message description and information about affected structure, view or issue. The general method
* {@link #getMessage()}, returns all information included with the exception, except for the stack trace and cause.
*
*
* Displaying User-Friendly Errors
*
* To display an error on the user interface: use {@link #getLocalizedMessage()} or {@link #getLocalizedMessage(com.atlassian.crowd.embedded.api.User)}
* to get a human-friendly error in the user's locale. However, in many cases the error message will not be localized
* and the result of {@code getLocalizedMessage()} will contain the problem description as a developer had put it in English.
* (It might not be human-friendly even for English audience!)
*
* So for the best result, you should check {@link #isLocalized()}
* method and if exception is not localized, use some wrapper text to display the error to the user in a good way:
*
*
*
* Java code:
* try {
* ...
* } catch (StructureException e) {
* if (e.isLocalized()) {
* setDisplayedError(e.getLocalizedMessage());
* } else {
* setDisplayedError(getText("my.errors.structure-error", e.getLocalizedMessage()));
* }
* }
*
* I18N Properties:
* my.errors.structure-error=Some problem happened with structure, sorry! ({0})
*
*
* Throwing StructureException
*
* An instance of {@code StructureException} may be created by using one of the available constructors, but
* it might be more convenient to start from {@link StructureError} and use chained builder commands, ending with
* message specification:
*
*
* throw StructureError.GENERIC_ERROR.withMessage("cannot foo bar");
* ...
* throw StructureError.INVALID_JQL.causedBy(caughtException).forStructure(id).withoutMessage();
* ...
* throw StructureError.VIEW_EDIT_DENIED.forView(id).withLocalizedMessage("error.view.edit.denied", id, name);
*
*
* @see StructureError
*/
public class StructureException extends Exception {
// used by tests to avoid exceptions
private static final boolean ISSUE_LOOKUP_DISABLED = "true".equalsIgnoreCase(System.getProperty("structure.exception.disable.lookup"));
private final StructureError myError;
private final String myProblemDetails;
private final long myStructure;
private final long myView;
private final long myIssue;
private final ItemIdentity myItemId;
private final String myMessageKey;
private final Object[] myMessageParameters;
/**
* Constructs an instance of exception.
*
* @param error structure error code
*/
public StructureException(@Nullable StructureError error) {
this(error, null, null, null, null, null, null, null);
}
/**
* Constructs an instance of exception.
*
* @param error structure error code
* @param message additional message text
*/
public StructureException(@Nullable StructureError error, @Nullable String message) {
this(error, null, null, null, null, null, message, null);
}
/**
* Constructs an instance of exception.
*
* @param error structure error code
* @param structure structure in question
*/
public StructureException(@Nullable StructureError error, @Nullable Long structure) {
this(error, null, structure, null, null, null, null, null);
}
/**
* Constructs an instance of exception.
*
* @param error structure error code
* @param structure structure in question
* @param issue related issue
*/
public StructureException(@Nullable StructureError error, @Nullable Long structure, @Nullable Long issue) {
this(error, null, structure, null, issue, null, null, null);
}
/**
* Constructs an instance of exception.
*
* @param error structure error code
* @param structure structure in question
* @param issue related issue
* @param message additional message text
*/
public StructureException(@Nullable StructureError error, @Nullable Long structure, @Nullable Long issue, @Nullable String message) {
this(error, null, structure, null, issue, null, message, null);
}
/**
* Constructs an instance of exception. For convenience, use one of the overloaded constructors.
*
* @param error structure error code
* @param structure structure in question
* @param issue related issue
* @param message additional message text
* @param cause throwable cause
*/
public StructureException(@Nullable StructureError error, @Nullable Long structure, @Nullable Long issue, @Nullable String message, @Nullable Throwable cause) {
this(error, cause, structure, null, issue, null, message, null);
}
public StructureException(@Nullable StructureError error, @Nullable Long structure, @Nullable Long issue, @Nullable Long view) {
this(error, null, structure, view, issue, null, null, null);
}
public StructureException(@Nullable StructureError error, @Nullable Long structure, @Nullable Long issue, @Nullable Long view, @Nullable String message) {
this(error, null, structure, view, issue, null, message, null);
}
protected StructureException(StructureError error, @Nullable Throwable cause, @Nullable Long structure,
@Nullable Long view, @Nullable Long issue, @Nullable ItemIdentity itemId, @Nullable String message, @Nullable String messageKey,
@Nullable Object... messageParameters)
{
super(createMessage(error, structure, view, issue, message, messageKey, messageParameters), cause);
myError = error == null ? StructureError.GENERIC_ERROR : error;
// avoid double calculation of root-locale message by providing message that already contains it
String details = createDetails(message, messageKey, messageParameters);
myProblemDetails = details.isEmpty() ? "error " + myError : details;
myStructure = structure == null ? 0L : structure;
myView = view == null ? 0L : view;
myIssue = issue == null ? 0L : issue;
myItemId = itemId;
myMessageKey = messageKey;
myMessageParameters = messageParameters;
}
private static String createMessage(StructureError error, Long structure, Long view, Long issue,
@Nullable String message, @Nullable String messageKey, @Nullable Object... messageParameters)
{
StringBuilder b = new StringBuilder();
if (error == null) error = StructureError.GENERIC_ERROR;
String details = createDetails(message, messageKey, messageParameters);
if (!details.isEmpty()) b.append(details).append(" - ");
b.append("structure ").append(error.name()).append(" (code:").append(error.getCode());
if (structure != null && structure > 0) b.append(" structure:").append(structure);
if (view != null && view > 0) b.append(" view:").append(view);
if (issue != null && issue > 0) {
b.append(" issue:").append(issue);
if (!ISSUE_LOOKUP_DISABLED) {
appendIssueKey(b, issue);
}
}
b.append(')');
return b.toString();
}
@NotNull
private static String createDetails(String message, String messageKey, Object... messageParameters) {
if (message == null) {
message = getRootLocaleMessage(messageKey, messageParameters);
if (message == null) message = "";
}
return message;
}
private static void appendIssueKey(StringBuilder b, Long issue) {
try {
IssueManager issueManager = ComponentAccessor.getIssueManager();
MutableIssue io = issueManager.getIssueObject(issue);
if (io != null) b.append(" key:").append(io.getKey());
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// ignore
}
}
private static String getRootLocaleMessage(String messageKey, Object... messageParameters) {
// we could use Locale.ROOT here, but it won't work in development environment because there's no StructureMessages.properties
try {
return StructureUtil.getText(Locale.ENGLISH, null, messageKey, messageParameters);
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// protect from errors and exceptions in the exception initializer
return "";
}
}
// overriding default toString(), which uses getLocalizedMessage()
public String toString() {
String s = getClass().getName();
String message = getMessage();
return message != null ? s + ": " + message : s;
}
/**
* @return error code
*/
public StructureError getError() {
return myError;
}
/**
* @return the part of {@link #getMessage()} that corresponds to the original description of the problem.
* Does not contain structure ID, error code, and related issue information, so is more user-friendly than {@link #getMessage()}
* in case when that information can be more suitably described by the caller.
* */
public String getProblemDetails() {
return myProblemDetails;
}
/**
* @return related structure, or 0 if no structure is related
*/
public long getStructure() {
return myStructure;
}
public long getView() {
return myView;
}
/**
* @return related issue, or 0 if no issue is related
*/
public long getIssue() {
return myIssue;
}
public ItemIdentity getItemId() {
return myItemId;
}
public boolean isLocalized() {
return myMessageKey != null;
}
@Override
public String getLocalizedMessage() {
return isLocalized() ? StructureUtil.getTextInCurrentUserLocale(myMessageKey, myMessageParameters) : getProblemDetails();
}
public String getLocalizedMessage(@Nullable User user) {
return isLocalized() ? StructureUtil.getText(null, user, myMessageKey, myMessageParameters) : getProblemDetails();
}
public String getLocalizedMessage(@Nullable ApplicationUser user) {
return isLocalized() ? StructureUtil.getText2(null, user, myMessageKey, myMessageParameters) : getProblemDetails();
}
public static class Builder {
private StructureError myError;
private String myMessage;
private Long myStructure;
private Long myView;
private Long myIssue;
private ItemIdentity myItemId;
private String myMessageKey;
private Object[] myMessageParameters;
private Throwable myCause;
public Builder(StructureError error) {
myError = error;
}
public StructureException withLocalizedMessage(String messageKey, Object... messageParameters) {
myMessage = messageKey == null ? "" : getRootLocaleMessage(messageKey, messageParameters);
myMessageKey = messageKey;
myMessageParameters = messageParameters;
return build();
}
public StructureException withMessage(String message) {
myMessage = message;
myMessageKey = null;
myMessageParameters = null;
return build();
}
public StructureException withoutMessage() {
myMessage = null;
myMessageKey = null;
myMessageParameters = null;
return build();
}
public Builder forStructure(Long structure) {
myStructure = structure;
return this;
}
public Builder forView(Long view) {
myView = view;
return this;
}
public Builder forIssue(Long issue) {
myIssue = issue;
return this;
}
public Builder forItem(ItemIdentity itemId) {
myItemId = itemId;
return this;
}
public Builder causedBy(Throwable cause) {
myCause = cause;
return this;
}
private StructureException build() {
return new StructureException(myError, myCause, myStructure, myView, myIssue, myItemId, myMessage, myMessageKey, myMessageParameters);
}
}
}