Commit 8a6e913d authored by Thomas Mortagne's avatar Thomas Mortagne
Browse files

XRENDERING-621: Make rendering errors translatables and extendables

* simplify the API
* make the code more reusable
parent 703f830b
......@@ -28,9 +28,15 @@
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.logging.LogLevel;
import org.xwiki.logging.LogUtils;
import org.xwiki.logging.event.LogEvent;
import org.xwiki.logging.marker.TranslationMarker;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.FormatBlock;
import org.xwiki.rendering.block.GroupBlock;
......@@ -55,7 +61,21 @@ public class DefaultErrorBlockGenerator implements ErrorBlockGenerator
protected Logger logger;
@Override
public List<Block> generateErrorBlocks(String message, String description, boolean isInline)
public List<Block> generateErrorBlocks(boolean inline, String messageId, String defaultMessage,
String defaultDescription, Object... arguments)
{
LogEvent message = LogUtils.newLogEvent(messageId != null ? new TranslationMarker(messageId) : null,
LogLevel.ERROR,
defaultMessage != null && !defaultMessage.endsWith(".") ? defaultMessage + '.' : defaultMessage, arguments);
LogEvent description = defaultDescription != null
? LogUtils.newLogEvent(messageId != null ? new TranslationMarker(messageId + ".description") : null,
LogLevel.ERROR, defaultDescription, arguments)
: null;
return generateErrorBlocks(inline, message, description);
}
protected List<Block> generateErrorBlocks(boolean inline, LogEvent message, LogEvent description)
{
List<Block> errorBlocks = new ArrayList<>();
......@@ -64,39 +84,80 @@ public List<Block> generateErrorBlocks(String message, String description, boole
Map<String, String> errorDescriptionBlockParams =
Collections.singletonMap(CLASS_ATTRIBUTE_NAME, CLASS_ATTRIBUTE_DESCRIPTION_VALUE);
Block descriptionBlock = new VerbatimBlock(description, isInline);
StringBuilder messageBuilder = new StringBuilder();
if (StringUtils.isNotEmpty(message.getMessage())) {
messageBuilder.append(message.getFormattedMessage());
}
List<Block> descriptionChildren = new ArrayList<>();
if (isInline) {
errorBlocks.add(new FormatBlock(Arrays.asList(new WordBlock(message)), Format.NONE, errorBlockParams));
errorBlocks.add(new FormatBlock(Arrays.asList(descriptionBlock), Format.NONE, errorDescriptionBlockParams));
// Description
addDescriptionBlock(inline, description, descriptionChildren);
// Stack trace
addStackTraceBlock(inline, message, messageBuilder, descriptionChildren);
if (!descriptionChildren.isEmpty()) {
messageBuilder.append(" Click on this message for details.");
}
if (inline) {
errorBlocks.add(new FormatBlock(Arrays.asList(new WordBlock(messageBuilder.toString())), Format.NONE,
errorBlockParams));
if (!descriptionChildren.isEmpty()) {
errorBlocks.add(new FormatBlock(descriptionChildren, Format.NONE, errorDescriptionBlockParams));
}
} else {
errorBlocks.add(new GroupBlock(Arrays.asList(new WordBlock(message)), errorBlockParams));
errorBlocks.add(new GroupBlock(Arrays.asList(descriptionBlock), errorDescriptionBlockParams));
errorBlocks
.add(new GroupBlock(Arrays.asList(new WordBlock(message.getFormattedMessage())), errorBlockParams));
if (!descriptionChildren.isEmpty()) {
errorBlocks.add(new GroupBlock(descriptionChildren, errorDescriptionBlockParams));
}
}
return errorBlocks;
}
@Override
public List<Block> generateErrorBlocks(String messagePrefix, Throwable throwable, boolean isInline)
private void addDescriptionBlock(boolean inline, LogEvent description, List<Block> descriptionChildren)
{
// Note: We're using ExceptionUtils.getRootCause(e).getMessage() instead of getRootCauseMessage()
// below because getRootCauseMessage() adds a technical prefix (the name of the exception), that
// we don't want to display to our users.
Throwable rootCause = ExceptionUtils.getRootCause(throwable);
if (rootCause == null) {
// If there's no nested exception, fall back to the throwable itself for getting the cause
rootCause = throwable;
if (description != null) {
descriptionChildren.add(new VerbatimBlock(description.getFormattedMessage(), inline));
}
}
private void addStackTraceBlock(boolean inline, LogEvent message, StringBuilder messageBuilder,
List<Block> descriptionChildren)
{
if (message.getThrowable() != null) {
// Note: We're using ExceptionUtils.getRootCause(e).getMessage() instead of getRootCauseMessage()
// below because getRootCauseMessage() adds a technical prefix (the name of the exception), that
// we don't want to display to our users.
Throwable rootCause = ExceptionUtils.getRootCause(message.getThrowable());
if (rootCause == null) {
// If there's no nested exception, fall back to the throwable itself for getting the cause
rootCause = message.getThrowable();
}
String augmentedMessage = String.format("%s%s", messagePrefix,
rootCause == null ? "" : String.format(". Cause: [%s]", rootCause.getMessage()));
augmentedMessage = String.format("%s%sClick on this message for details.", augmentedMessage,
augmentedMessage.trim().endsWith(".") ? " " : ". ");
descriptionChildren.add(new VerbatimBlock(ExceptionUtils.getStackTrace(message.getThrowable()), inline));
this.logger.debug(augmentedMessage);
// Also add more details to the message
messageBuilder.append(" Cause: [");
messageBuilder.append(rootCause.getMessage());
messageBuilder.append("].");
}
}
return generateErrorBlocks(augmentedMessage, ExceptionUtils.getStackTrace(throwable), isInline);
@Override
public List<Block> generateErrorBlocks(String message, String description, boolean isInline)
{
return generateErrorBlocks(isInline, null, message, description, ArrayUtils.EMPTY_OBJECT_ARRAY);
}
@Override
public List<Block> generateErrorBlocks(String messagePrefix, Throwable throwable, boolean isInline)
{
return generateErrorBlocks(isInline, null, messagePrefix, null, throwable);
}
@Override
......
......@@ -102,8 +102,8 @@
* context depending on the implementation of this component.
*
* @param inline whether the generated blocks should be inline or not
* @param marker a marker associated to the message. It's recommended to at least pass a {@link TranslationMarker}
* to indicate how to translate the message.
* @param messageId an identifier associated to the message. It's generally used, among other things, to find a
* translation for the message and the description in implementation which supports it.
* @param defaultMessage the default message following SLF4J's {@link Logger} syntax
* @param defaultDescription the default description following SLF4J's {@link Logger} syntax
* @param arguments a list arguments to insert in the message and the description and/or a {@link Throwable}
......@@ -111,9 +111,10 @@
* @since 14.0RC1
*/
@Unstable
default List<Block> generateErrorBlocks(boolean inline, Marker marker, String defaultMessage,
default List<Block> generateErrorBlocks(boolean inline, String messageId, String defaultMessage,
String defaultDescription, Object... arguments)
{
Marker marker = new TranslationMarker(messageId);
LogEvent message = LogUtils.newLogEvent(marker, LogLevel.ERROR, defaultMessage, arguments);
if (message.getThrowable() != null) {
......
......@@ -22,8 +22,6 @@
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.xwiki.logging.marker.TranslationMarker;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.MacroBlock;
import org.xwiki.rendering.block.MacroMarkerBlock;
......@@ -74,17 +72,17 @@ private Block wrapInMacroMarker(MacroBlock macroBlockToWrap, List<Block> newBloc
* Generates Blocks to signify that the passed Macro Block has failed to execute.
*
* @param macroToReplace
* @param marker a marker associated to the message. It's recommended to at least pass a {@link TranslationMarker}
* to indicate how to translate the message.
* @param messageId an identifier associated to the message. It's generally used, among other things, to find a
* translation for the message and the description in implementation which supports it.
* @param defaultMessage the default message following SLF4J's {@link Logger} syntax
* @param defaultDescription the default description following SLF4J's {@link Logger} syntax
* @param arguments a list arguments to insert in the message and the description and/or a {@link Throwable}
* @since 14.0RC1
*/
public void generateError(MacroBlock macroToReplace, Marker marker, String defaultMessage,
public void generateError(MacroBlock macroToReplace, String messageId, String defaultMessage,
String defaultDescription, Object... arguments)
{
List<Block> errorBlocks = this.errorBlockGenerator.generateErrorBlocks(macroToReplace.isInline(), marker,
List<Block> errorBlocks = this.errorBlockGenerator.generateErrorBlocks(macroToReplace.isInline(), messageId,
defaultMessage, defaultDescription, arguments);
macroToReplace.getParent().replaceChild(wrapInMacroMarker(macroToReplace, errorBlocks), macroToReplace);
}
......
......@@ -32,7 +32,6 @@
import org.xwiki.component.annotation.Component;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.logging.marker.TranslationMarker;
import org.xwiki.properties.BeanManager;
import org.xwiki.rendering.block.Block;
import org.xwiki.rendering.block.MacroBlock;
......@@ -68,15 +67,15 @@
@Singleton
public class MacroTransformation extends AbstractTransformation implements Initializable
{
private static final TranslationMarker TM_UNKNOWNMACRO = new TranslationMarker("rendering.macro.error.unknown");
private static final String TM_UNKNOWNMACRO = "rendering.macro.error.unknown";
private static final TranslationMarker TM_FAILEDMACRO = new TranslationMarker("rendering.macro.error.failed");
private static final String TM_FAILEDMACRO = "rendering.macro.error.failed";
private static final TranslationMarker TM_INVALIDMACRO = new TranslationMarker("rendering.macro.error.invalid");
private static final String TM_INVALIDMACRO = "rendering.macro.error.invalid";
private static final TranslationMarker TM_STANDALONEMACRO = new TranslationMarker("rendering.macro.error.standalone");
private static final String TM_STANDALONEMACRO = "rendering.macro.error.standalone";
private static final TranslationMarker TM_INVALIDMACROPARAMETER = new TranslationMarker("rendering.macro.error.invalidParameter");
private static final String TM_INVALIDMACROPARAMETER = "rendering.macro.error.invalidParameter";
private static class MacroLookupExceptionElement
{
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment