diff --git a/core/src/main/java/io/github/mmm/base/exception/ApplicationException.java b/core/src/main/java/io/github/mmm/base/exception/ApplicationException.java index 77cbd6d..fc2cf94 100644 --- a/core/src/main/java/io/github/mmm/base/exception/ApplicationException.java +++ b/core/src/main/java/io/github/mmm/base/exception/ApplicationException.java @@ -57,7 +57,7 @@ public ApplicationException(Localizable message) { * The constructor. * * @param message the {@link #getMessage() message} describing the problem briefly. - * @param cause is the {@link #getCause() cause} of this exception. + * @param cause the {@link #getCause() cause} of this exception. */ public ApplicationException(String message, Throwable cause) { @@ -67,8 +67,8 @@ public ApplicationException(String message, Throwable cause) { /** * The constructor. * - * @param message the {@link #getLocalizedMessage() message} describing the problem briefly. - * @param cause is the {@link #getCause() cause} of this exception. + * @param message the {@link #getNlsMessage() NLS message} describing the problem briefly. + * @param cause the {@link #getCause() cause} of this exception. */ public ApplicationException(Localizable message, Throwable cause) { @@ -79,7 +79,7 @@ public ApplicationException(Localizable message, Throwable cause) { * The constructor. * * @param message the {@link #getMessage() message} describing the problem briefly. - * @param cause is the {@link #getCause() cause} of this exception. May be null. + * @param cause the {@link #getCause() cause} of this exception. May be null. * @param uuid the explicit {@link #getUuid() UUID} or null to initialize by default (from given * {@link Throwable} or as new {@link UUID}). */ @@ -91,8 +91,8 @@ protected ApplicationException(String message, Throwable cause, UUID uuid) { /** * The constructor. * - * @param message the {@link #getLocalizedMessage() message} describing the problem briefly. - * @param cause is the {@link #getCause() cause} of this exception. May be null. + * @param message the {@link #getNlsMessage() NLS message} describing the problem briefly. + * @param cause the {@link #getCause() cause} of this exception. May be null. * @param uuid the explicit {@link #getUuid() UUID} or null to initialize by default (from given * {@link Throwable} or as new {@link UUID}). */ @@ -136,18 +136,28 @@ public final UUID getUuid() { return this.uuid; } + /** + * ATTENTION: Logging frameworks like logback do not follow Java exception standards and write {@link #toString()} but + * only {@link #getMessage()} of the {@link Exception} together with the stacktrace into the log. Since an + * {@link ApplicationException} contains additional information like {@link #getUuid() UUID} and {@link #getCode() + * code} that needs to be logged we are forced to return the message from this method in the following form: + * + *
[{@link #getCode() «code»}: ]{@link #getNlsMessage() «message»}
+   * «uuid»
+   * 
+ * + * Please note that the {@link #getCode() code} is omitted if the {@link #getCode()} method is not overridden and + * returning a custom code.
+ * In case you want to get the plain exception message, you therefore need to either call + * {@link #getLocalizedMessage()} or use {@link #getNlsMessage()}. + * + * @return the untranslated message in the form specified above. + */ @Override public String getMessage() { StringBuilder buffer = new StringBuilder(); - getLocalizedMessage(Locale.ROOT, buffer); - buffer.append(System.lineSeparator()); - buffer.append(this.uuid); - String code = getCode(); - if (!getClass().getSimpleName().equals(code)) { - buffer.append(":"); - buffer.append(code); - } + toString(Locale.ROOT, buffer, true); return buffer.toString(); } @@ -161,6 +171,12 @@ public Localizable getNlsMessage() { return this.message; } + @Override + public String getLocalizedMessage() { + + return getLocalizedMessage(Locale.getDefault()); + } + @Override public String getLocalizedMessage(Locale locale) { @@ -196,22 +212,14 @@ private static void printStackTrace(ApplicationException throwable, Locale local try { synchronized (buffer) { - buffer.append(throwable.getClass().getName()); - buffer.append(": "); - throwable.getLocalizedMessage(locale, buffer); - buffer.append(System.lineSeparator()); - UUID uuid = throwable.getUuid(); - if (uuid != null) { - buffer.append(uuid.toString()); - buffer.append(System.lineSeparator()); - } + throwable.toString(locale, buffer); StackTraceElement[] trace = throwable.getStackTrace(); for (int i = 0; i < trace.length; i++) { buffer.append("\tat "); buffer.append(trace[i].toString()); buffer.append(System.lineSeparator()); } - for (Throwable suppressed : ((Throwable) throwable).getSuppressed()) { + for (Throwable suppressed : throwable.getSuppressed()) { buffer.append("Suppressed: "); buffer.append(System.lineSeparator()); printStackTraceCause(suppressed, locale, buffer); @@ -342,14 +350,21 @@ public String toString(Locale locale) { */ public Appendable toString(Locale locale, Appendable appendable) { + return toString(locale, appendable, false); + } + + private Appendable toString(Locale locale, Appendable appendable, boolean omitClass) { + Appendable buffer = appendable; if (buffer == null) { buffer = new StringBuilder(32); } try { Class myClass = getClass(); - buffer.append(myClass.getName()); - buffer.append(": "); + if (!omitClass) { + buffer.append(myClass.getName()); + buffer.append(": "); + } String code = getCode(); if (!myClass.getSimpleName().equals(code)) { buffer.append(code); diff --git a/core/src/main/java/io/github/mmm/base/exception/ObjectNotFoundException.java b/core/src/main/java/io/github/mmm/base/exception/ObjectNotFoundException.java index 89f66a0..be7031f 100644 --- a/core/src/main/java/io/github/mmm/base/exception/ObjectNotFoundException.java +++ b/core/src/main/java/io/github/mmm/base/exception/ObjectNotFoundException.java @@ -2,6 +2,8 @@ * http://www.apache.org/licenses/LICENSE-2.0 */ package io.github.mmm.base.exception; +import java.util.Collection; + import io.github.mmm.base.i18n.Localizable; /** @@ -24,7 +26,7 @@ public class ObjectNotFoundException extends ApplicationException { /** * The constructor. * - * @param object is a description (e.g. the classname) of the object that was required but could not be found. + * @param object the description (e.g. the classname) of the object that was required but could NOT be found. */ public ObjectNotFoundException(Object object) { @@ -34,8 +36,8 @@ public ObjectNotFoundException(Object object) { /** * The constructor. * - * @param object is a description (e.g. the classname) of the object that was required but could not be found. - * @param key is the key to the required object. + * @param object the description (e.g. the classname) of the object that was required but could NOT be found. + * @param key the key to the required object. */ public ObjectNotFoundException(Object object, Object key) { @@ -45,16 +47,30 @@ public ObjectNotFoundException(Object object, Object key) { /** * The constructor. * - * @param object is a description (e.g. the classname) of the object that was required but could NOT be found. - * @param key is the key to the required object. - * @param cause is the {@link #getCause() cause} of this exception. + * @param object the description (e.g. the classname) of the object that was required but could NOT be found. + * @param key the key to the required object. + * @param cause the {@link #getCause() cause} of this exception. */ public ObjectNotFoundException(Object object, Object key, Throwable cause) { - super(createMessage(object, key), cause); + this(object, key, null, cause); } - private static String createMessage(Object object, Object key) { + /** + * The constructor. + * + * @param object the description (e.g. the classname) of the object that was required but could NOT be found. + * @param key the key to the required object. + * @param options the available options (e.g. {@link Collection} of comma separated {@link String} of the available + * keys). + * @param cause the {@link #getCause() cause} of this exception. + */ + public ObjectNotFoundException(Object object, Object key, Object options, Throwable cause) { + + super(createMessage(object, key, options), cause); + } + + private static String createMessage(Object object, Object key, Object options) { StringBuilder sb = new StringBuilder("Could not find "); sb.append(object); @@ -64,7 +80,10 @@ private static String createMessage(Object object, Object key) { sb.append(key); sb.append('\''); } - sb.append("."); + if (options != null) { + sb.append(" in "); + sb.append(options); + } return sb.toString(); } @@ -72,7 +91,7 @@ private static String createMessage(Object object, Object key) { * The constructor. * * @param message the {@link #getNlsMessage() NLS message}. - * @param cause is the {@link #getCause() cause} of this exception. May be null. + * @param cause the {@link #getCause() cause} of this exception. May be null. */ protected ObjectNotFoundException(Localizable message, Throwable cause) { diff --git a/core/src/test/java/io/github/mmm/base/exception/ApplicationExceptionTest.java b/core/src/test/java/io/github/mmm/base/exception/ApplicationExceptionTest.java new file mode 100644 index 0000000..433dfc5 --- /dev/null +++ b/core/src/test/java/io/github/mmm/base/exception/ApplicationExceptionTest.java @@ -0,0 +1,45 @@ +package io.github.mmm.base.exception; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Locale; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test of {@link ApplicationException}. + */ +public class ApplicationExceptionTest extends Assertions { + + @Test + public void testWithCode() { + + // arrange + String message = "Something went wrong."; + String code = "MagicCode"; + // act + ApplicationException exception = new ApplicationException(message) { + @Override + public String getCode() { + + return code; + } + }; + StringWriter sw = new StringWriter(1024); + PrintWriter pw = new PrintWriter(sw); + exception.printStackTrace(pw); + String stackTrace = sw.toString(); + // assert + assertThat(exception.getLocalizedMessage()).isEqualTo(message); + assertThat(exception.getLocalizedMessage(Locale.ROOT)).isEqualTo(message); + assertThat(exception.getNlsMessage().getMessage()).isEqualTo(message); + String msg = code + ": " + message + System.lineSeparator() + exception.getUuid(); + assertThat(exception.getMessage()).isEqualTo(msg); + String toString = exception.getClass().getName() + ": " + msg; + assertThat(exception.toString()).isEqualTo(toString); + assertThat(stackTrace).startsWith(toString) + .contains("io.github.mmm.base.exception.ApplicationExceptionTest.testWithCode("); + } + +} diff --git a/core/src/test/java/io/github/mmm/base/exception/ObjectNotFoundExceptionTest.java b/core/src/test/java/io/github/mmm/base/exception/ObjectNotFoundExceptionTest.java new file mode 100644 index 0000000..0147918 --- /dev/null +++ b/core/src/test/java/io/github/mmm/base/exception/ObjectNotFoundExceptionTest.java @@ -0,0 +1,51 @@ +package io.github.mmm.base.exception; + +import java.util.Collection; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test of {@link ObjectNotFoundException}. + */ +public class ObjectNotFoundExceptionTest extends Assertions { + + @Test + public void testSimple() { + + // arrange + String name = "io.github.mmm.UndefinedObject"; + // act + ObjectNotFoundException exception = new ObjectNotFoundException(name); + // assert + assertThat(exception.getLocalizedMessage()).isEqualTo("Could not find " + name); + } + + @Test + public void testWithKey() { + + // arrange + String name = "Entity"; + String key = "MagicKey"; + // act + ObjectNotFoundException exception = new ObjectNotFoundException(name, key); + // assert + assertThat(exception.getLocalizedMessage()).isEqualTo("Could not find " + name + " for key '" + key + "'"); + } + + @Test + public void testWithKeyAndOptions() { + + // arrange + String name = "Entity"; + String key = "MagicKey"; + Collection options = List.of("NormalKey", "Key", "HolyKey"); + // act + ObjectNotFoundException exception = new ObjectNotFoundException(name, key, options, null); + // assert + assertThat(exception.getLocalizedMessage()) + .isEqualTo("Could not find " + name + " for key '" + key + "' in [NormalKey, Key, HolyKey]"); + } + +} diff --git a/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java b/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java index d5ec47b..7c07c7e 100644 --- a/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java +++ b/metainfo/src/test/java/io/github/mmm/base/metadata/MetaInfoTest.java @@ -55,7 +55,7 @@ public void testGetRequired() { metaInfo.getRequired("key2"); failBecauseExceptionWasNotThrown(ObjectNotFoundException.class); } catch (ObjectNotFoundException e) { - assertThat(e).hasMessageContaining("Could not find MetaInfo-value for key 'key2'."); + assertThat(e).hasMessageContaining("Could not find MetaInfo-value for key 'key2'"); } } @@ -167,7 +167,7 @@ public void testWithPrefix() { metaInfo.getRequired("key2"); failBecauseExceptionWasNotThrown(ObjectNotFoundException.class); } catch (ObjectNotFoundException e) { - assertThat(e.getNlsMessage().getMessage()).isEqualTo("Could not find MetaInfo-value for key 'prefix.key2'."); + assertThat(e.getNlsMessage().getMessage()).isEqualTo("Could not find MetaInfo-value for key 'prefix.key2'"); } // and act