CVE-2020-5245: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Dropwizard-Validation before 1.3.19, and 2.0.2 may allow arbitrary code execution on the host system, with the privileges of the Dropwizard service account, by injecting arbitrary Java Expression Language expressions when using the self-validating feature.

The issue has been fixed in dropwizard-validation 1.3.19 and 2.0.2.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2020-02-24T18:15Z
Repository: https://github.com/dropwizard/dropwizard
Language: Java
Files changed: dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java, dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java
Commit date: 2020-02-20 02:36:40 +0100
Commit message: Escape EL expressions in ViolationCollector (#3157)

Fixes #3153
Diff: diff --git a/dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java b/dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java
index 69cf5e63d..5c0005cb7 100644
--- a/dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java
+++ b/dropwizard-validation/src/main/java/io/dropwizard/validation/selfvalidating/ViolationCollector.java
@@ -1,32 +1,102 @@
 package io.dropwizard.validation.selfvalidating;
 
+import javax.annotation.Nullable;
 import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * This class is a simple wrapper around the ConstraintValidatorContext of hibernate validation.
  * It collects all the violations of the SelfValidation methods of an object.
  */
 public class ViolationCollector {
+    private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\$\\{");
 
     private boolean violationOccurred = false;
     private ConstraintValidatorContext context;
 
 
     public ViolationCollector(ConstraintValidatorContext context) {
         this.context = context;
     }
 
     /**
-     * Adds a new violation to this collector. This also sets violationOccurred to true.
+     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
      *
-     * @param msg the message of the violation
+     * @param message the message of the violation (any EL expression will be escaped and not parsed)
      */
-    public void addViolation(String msg) {
+    public void addViolation(String message) {
         violationOccurred = true;
-        context.buildConstraintViolationWithTemplate(msg)
-            .addConstraintViolation();
+        String messageTemplate = escapeEl(message);
+        context.buildConstraintViolationWithTemplate(messageTemplate)
+                .addConstraintViolation();
+    }
+
+    /**
+     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
+     *
+     * @param propertyName the name of the property
+     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
+     * @since 2.0.2
+     */
+    public void addViolation(String propertyName, String message) {
+        violationOccurred = true;
+        String messageTemplate = escapeEl(message);
+        context.buildConstraintViolationWithTemplate(messageTemplate)
+                .addPropertyNode(propertyName)
+                .addConstraintViolation();
+    }
+
+    /**
+     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
+     *
+     * @param propertyName the name of the property with the violation
+     * @param index        the index of the element with the violation
+     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
+     * @since 2.0.2
+     */
+    public void addViolation(String propertyName, Integer index, String message) {
+        violationOccurred = true;
+        String messageTemplate = escapeEl(message);
+        context.buildConstraintViolationWithTemplate(messageTemplate)
+                .addPropertyNode(propertyName)
+                .addBeanNode().inIterable().atIndex(index)
+                .addConstraintViolation();
+    }
+
+    /**
+     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
+     *
+     * @param propertyName the name of the property with the violation
+     * @param key          the key of the element with the violation
+     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
+     * @since 2.0.2
+     */
+    public void addViolation(String propertyName, String key, String message) {
+        violationOccurred = true;
+        String messageTemplate = escapeEl(message);
+        context.buildConstraintViolationWithTemplate(messageTemplate)
+                .addPropertyNode(propertyName)
+                .addBeanNode().inIterable().atKey(key)
+                .addConstraintViolation();
+    }
+
+    @Nullable
+    private String escapeEl(@Nullable String s) {
+        if (s == null || s.isEmpty()) {
+            return s;
+        }
+
+        final Matcher m = ESCAPE_PATTERN.matcher(s);
+        final StringBuffer sb = new StringBuffer(s.length() + 16);
+        while (m.find()) {
+            m.appendReplacement(sb, "\\\\\\${");
+        }
+        m.appendTail(sb);
+
+        return sb.toString();
     }
 
     /**
      * This method returns the wrapped context for raw access to the validation framework. If you use
      * the context to add violations make sure to call <code>setViolationOccurred(true)</code>.
diff --git a/dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java b/dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java
index d3a7e79fa..7ccf91557 100644
--- a/dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java
+++ b/dropwizard-validation/src/test/java/io/dropwizard/validation/SelfValidationTest.java
@@ -1,66 +1,70 @@
 package io.dropwizard.validation;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import javax.annotation.concurrent.NotThreadSafe;
-import javax.validation.Validator;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
 import io.dropwizard.validation.selfvalidating.SelfValidating;
 import io.dropwizard.validation.selfvalidating.SelfValidation;
 import io.dropwizard.validation.selfvalidating.ViolationCollector;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 import uk.org.lidalia.slf4jext.Level;
 import uk.org.lidalia.slf4jtest.LoggingEvent;
 import uk.org.lidalia.slf4jtest.TestLoggerFactory;
 
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.validation.Validator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
 @NotThreadSafe
 public class SelfValidationTest {
 
     private static final String FAILED = "failed";
     private static final String FAILED_RESULT = " " + FAILED;
-    
-    @BeforeEach @AfterEach
+
+    @AfterEach
+    @BeforeEach
     public void clearAllLoggers() {
         //this must be a clear all because the validation runs in other threads
         TestLoggerFactory.clearAll();
     }
 
     @SelfValidating
     public static class FailingExample {
+        @SuppressWarnings("unused")
         @SelfValidation
         public void validateFail(ViolationCollector col) {
             col.addViolation(FAILED);
         }
     }
-    
+
     public static class SubclassExample extends FailingExample {
+        @SuppressWarnings("unused")
         @SelfValidation
         public void subValidateFail(ViolationCollector col) {
-            col.addViolation(FAILED+"subclass");
-        } 
+            col.addViolation(FAILED + "subclass");
+        }
     }
 
     @SelfValidating
     public static class AnnotatedSubclassExample extends FailingExample {
+        @SuppressWarnings("unused")
         @SelfValidation
         public void subValidateFail(ViolationCollector col) {
-            col.addViolation(FAILED+"subclass");
-        } 
+            col.addViolation(FAILED + "subclass");
+        }
     }
-    
+
     public static class OverridingExample extends FailingExample {
         @Override
         public void validateFail(ViolationCollector col) {
-        } 
+        }
     }
 
     @SelfValidating
     public static class DirectContextExample {
+        @SuppressWarnings("unused")
         @SelfValidation
         public void validateFail(ViolationCollector col) {
             col.getContext().buildConstraintViolationWithTemplate(FAILED).addConstraintViolation();
             col.setViolationOccurred(true);
         }
@@ -100,23 +104,32 @@ public class SelfValidationTest {
     }
 
 
     @SelfValidating
     public static class ComplexExample {
+        @SuppressWarnings("unused")
         @SelfValidation
         public void validateFail1(ViolationCollector col) {
             col.addViolation(FAILED + "1");
         }
 
+        @SuppressWarnings("unused")
         @SelfValidation
         public void validateFail2(ViolationCollector col) {
-            col.addViolation(FAILED + "2");
+            col.addViolation("p2", FAILED);
         }
 
+        @SuppressWarnings("unused")
         @SelfValidation
         public void validateFail3(ViolationCollector col) {
-            col.addViolation(FAILED + "3");
+            col.addViolation("p", 3, FAILED);
+        }
+
+        @SuppressWarnings("unused")
+        @SelfValidation
+        public void validateFail4(ViolationCollector col) {
+            col.addViolation("p", "four", FAILED);
         }
 
         @SuppressWarnings("unused")
         @SelfValidation
         public void validateCorrect(ViolationCollector col) {
@@ -125,123 +138,146 @@ public class SelfValidationTest {
 
     @SelfValidating
     public static class NoValidations {
     }
 
+    @SelfValidating
+    public static class InjectionExample {
+        @SuppressWarnings("unused")
+        @SelfValidation
+        public void validateFail(ViolationCollector col) {
+            col.addViolation("${'value'}");
+            col.addViolation("${'property'}", "${'value'}");
+            col.addViolation("${'property'}", 1, "${'value'}");
+            col.addViolation("${'property'}", "${'key'}", "${'value'}");
+        }
+    }
+
     private final Validator validator = BaseValidator.newValidator();
 
     @Test
-    public void failingExample() throws Exception {
+    public void failingExample() {
         assertThat(ConstraintViolations.format(validator.validate(new FailingExample())))
-            .containsExactlyInAnyOrder(FAILED_RESULT);
+                .containsExactlyInAnyOrder(FAILED_RESULT);
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
-    
+
     @Test
-    public void subClassExample() throws Exception {
+    public void subClassExample() {
         assertThat(ConstraintViolations.format(validator.validate(new SubclassExample())))
-            .containsExactlyInAnyOrder(
-                    FAILED_RESULT,
-                    FAILED_RESULT+"subclass"
-            );
+                .containsExactlyInAnyOrder(
+                        FAILED_RESULT,
+                        FAILED_RESULT + "subclass"
+                );
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
-    
+
     @Test
-    public void annotatedSubClassExample() throws Exception {
+    public void annotatedSubClassExample() {
         assertThat(ConstraintViolations.format(validator.validate(new AnnotatedSubclassExample())))
-            .containsExactlyInAnyOrder(
-                    FAILED_RESULT,
-                    FAILED_RESULT+"subclass"
-            );
+                .containsExactlyInAnyOrder(
+                        FAILED_RESULT,
+                        FAILED_RESULT + "subclass"
+                );
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
-    
+
     @Test
-    public void overridingSubClassExample() throws Exception {
+    public void overridingSubClassExample() {
         assertThat(ConstraintViolations.format(validator.validate(new OverridingExample())))
-            .isEmpty();
+                .isEmpty();
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
 
     @Test
-    public void correctExample() throws Exception {
+    public void correctExample() {
         assertThat(ConstraintViolations.format(validator.validate(new CorrectExample())))
-            .isEmpty();
+                .isEmpty();
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
 
     @Test
-    public void multipleTestingOfSameClass() throws Exception {
+    public void multipleTestingOfSameClass() {
         assertThat(ConstraintViolations.format(validator.validate(new CorrectExample())))
-            .isEmpty();
+                .isEmpty();
         assertThat(ConstraintViolations.format(validator.validate(new CorrectExample())))
-            .isEmpty();
+                .isEmpty();
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
 
     @Test
-    public void testDirectContextUsage() throws Exception {
+    public void testDirectContextUsage() {
         assertThat(ConstraintViolations.format(validator.validate(new DirectContextExample())))
-            .containsExactlyInAnyOrder(FAILED_RESULT);
+                .containsExactlyInAnyOrder(FAILED_RESULT);
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
 
     @Test
-    public void complexExample() throws Exception {
+    public void complexExample() {
         assertThat(ConstraintViolations.format(validator.validate(new ComplexExample())))
-            .containsExactlyInAnyOrder(
-                FAILED_RESULT + "1",
-                FAILED_RESULT + "2",
-                FAILED_RESULT + "3"
-            );
+                .containsExactly(
+                        " failed1",
+                        "p2 failed",
+                        "p[3] failed",
+                        "p[four] failed");
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .isEmpty();
+                .isEmpty();
     }
 
     @Test
     public void invalidExample() throws Exception {
         assertThat(ConstraintViolations.format(validator.validate(new InvalidExample())))
-            .isEmpty();
+                .isEmpty();
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-            .containsExactlyInAnyOrder(
-                    new LoggingEvent(
-                            Level.ERROR, 
-                            "The method {} is annotated with @SelfValidation but does not have a single parameter of type {}",
-                            InvalidExample.class.getMethod("validateFailAdditionalParameters", ViolationCollector.class, int.class),
-                            ViolationCollector.class
-                    ),
-                    new LoggingEvent(
-                            Level.ERROR, 
-                            "The method {} is annotated with @SelfValidation but does not return void. It is ignored",
-                            InvalidExample.class.getMethod("validateFailReturn", ViolationCollector.class)
-                    ),
-                    new LoggingEvent(
-                            Level.ERROR, 
-                            "The method {} is annotated with @SelfValidation but is not public",
-                            InvalidExample.class.getDeclaredMethod("validateFailPrivate", ViolationCollector.class)
-                    )
-            );
+                .containsExactlyInAnyOrder(
+                        new LoggingEvent(
+                                Level.ERROR,
+                                "The method {} is annotated with @SelfValidation but does not have a single parameter of type {}",
+                                InvalidExample.class.getMethod("validateFailAdditionalParameters", ViolationCollector.class, int.class),
+                                ViolationCollector.class
+                        ),
+                        new LoggingEvent(
+                                Level.ERROR,
+                                "The method {} is annotated with @SelfValidation but does not return void. It is ignored",
+                                InvalidExample.class.getMethod("validateFailReturn", ViolationCollector.class)
+                        ),
+                        new LoggingEvent(
+                                Level.ERROR,
+                                "The method {} is annotated with @SelfValidation but is not public",
+                                InvalidExample.class.getDeclaredMethod("validateFailPrivate", ViolationCollector.class)
+                        )
+                );
     }
 
     @Test
-    public void giveWarningIfNoValidationMethods() throws Exception {
+    public void giveWarningIfNoValidationMethods() {
         assertThat(ConstraintViolations.format(validator.validate(new NoValidations())))
-            .isEmpty();
+                .isEmpty();
         assertThat(TestLoggerFactory.getAllLoggingEvents())
-        .containsExactlyInAnyOrder(
-                new LoggingEvent(
-                        Level.WARN, 
-                        "The class {} is annotated with @SelfValidating but contains no valid methods that are annotated with @SelfValidation",
-                        NoValidations.class
-                )
-                
+                .containsExactlyInAnyOrder(
+                        new LoggingEvent(
+                                Level.WARN,
+                                "The class {} is annotated with @SelfValidating but contains no valid methods that are annotated with @SelfValidation",
+                                NoValidations.class
+                        )
+
+                );
+    }
+
+    @Test
+    public void violationMessagesAreEscaped() {
+        assertThat(ConstraintViolations.format(validator.validate(new InjectionExample()))).containsExactly(
+                " ${'value'}",
+                "${'property'} ${'value'}",
+                "${'property'}[${'key'}] ${'value'}",
+                "${'property'}[1] ${'value'}"
         );
+        assertThat(TestLoggerFactory.getAllLoggingEvents()).isEmpty();
     }
 }

Vulnerable code:      * Adds a new violation to this collector. This also sets violationOccurred to true.
     * @param msg the message of the violation
    public void addViolation(String msg) {
        context.buildConstraintViolationWithTemplate(msg)
            .addConstraintViolation();
import static org.assertj.core.api.Assertions.assertThat;

import javax.annotation.concurrent.NotThreadSafe;
import javax.validation.Validator;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

    
    @BeforeEach @AfterEach
    
            col.addViolation(FAILED+"subclass");
        } 
            col.addViolation(FAILED+"subclass");
        } 
    
        } 
            col.addViolation(FAILED + "2");
            col.addViolation(FAILED + "3");
    public void failingExample() throws Exception {
            .containsExactlyInAnyOrder(FAILED_RESULT);
            .isEmpty();
    
    public void subClassExample() throws Exception {
            .containsExactlyInAnyOrder(
                    FAILED_RESULT,
                    FAILED_RESULT+"subclass"
            );
            .isEmpty();
    
    public void annotatedSubClassExample() throws Exception {
            .containsExactlyInAnyOrder(
                    FAILED_RESULT,
                    FAILED_RESULT+"subclass"
            );
            .isEmpty();
    
    public void overridingSubClassExample() throws Exception {
            .isEmpty();
            .isEmpty();
    public void correctExample() throws Exception {
            .isEmpty();
            .isEmpty();
    public void multipleTestingOfSameClass() throws Exception {
            .isEmpty();
            .isEmpty();
            .isEmpty();
    public void testDirectContextUsage() throws Exception {
            .containsExactlyInAnyOrder(FAILED_RESULT);
            .isEmpty();
    public void complexExample() throws Exception {
            .containsExactlyInAnyOrder(
                FAILED_RESULT + "1",
                FAILED_RESULT + "2",
                FAILED_RESULT + "3"
            );
            .isEmpty();
            .isEmpty();
            .containsExactlyInAnyOrder(
                    new LoggingEvent(
                            Level.ERROR, 
                            "The method {} is annotated with @SelfValidation but does not have a single parameter of type {}",
                            InvalidExample.class.getMethod("validateFailAdditionalParameters", ViolationCollector.class, int.class),
                            ViolationCollector.class
                    ),
                    new LoggingEvent(
                            Level.ERROR, 
                            "The method {} is annotated with @SelfValidation but does not return void. It is ignored",
                            InvalidExample.class.getMethod("validateFailReturn", ViolationCollector.class)
                    ),
                    new LoggingEvent(
                            Level.ERROR, 
                            "The method {} is annotated with @SelfValidation but is not public",
                            InvalidExample.class.getDeclaredMethod("validateFailPrivate", ViolationCollector.class)
                    )
            );
    public void giveWarningIfNoValidationMethods() throws Exception {
            .isEmpty();
        .containsExactlyInAnyOrder(
                new LoggingEvent(
                        Level.WARN, 
                        "The class {} is annotated with @SelfValidating but contains no valid methods that are annotated with @SelfValidation",
                        NoValidations.class
                )
                
Fixed code: import javax.annotation.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
    private static final Pattern ESCAPE_PATTERN = Pattern.compile("\\$\\{");
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     * @param message the message of the violation (any EL expression will be escaped and not parsed)
    public void addViolation(String message) {
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
                .addConstraintViolation();
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName the name of the property
     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
     * @since 2.0.2
     */
    public void addViolation(String propertyName, String message) {
        violationOccurred = true;
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
                .addPropertyNode(propertyName)
                .addConstraintViolation();
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName the name of the property with the violation
     * @param index        the index of the element with the violation
     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
     * @since 2.0.2
     */
    public void addViolation(String propertyName, Integer index, String message) {
        violationOccurred = true;
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
                .addPropertyNode(propertyName)
                .addBeanNode().inIterable().atIndex(index)
                .addConstraintViolation();
    }

    /**
     * Adds a new violation to this collector. This also sets {@code violationOccurred} to {@code true}.
     *
     * @param propertyName the name of the property with the violation
     * @param key          the key of the element with the violation
     * @param message      the message of the violation (any EL expression will be escaped and not parsed)
     * @since 2.0.2
     */
    public void addViolation(String propertyName, String key, String message) {
        violationOccurred = true;
        String messageTemplate = escapeEl(message);
        context.buildConstraintViolationWithTemplate(messageTemplate)
                .addPropertyNode(propertyName)
                .addBeanNode().inIterable().atKey(key)
                .addConstraintViolation();
    }

    @Nullable
    private String escapeEl(@Nullable String s) {
        if (s == null || s.isEmpty()) {
            return s;
        }

        final Matcher m = ESCAPE_PATTERN.matcher(s);
        final StringBuffer sb = new StringBuffer(s.length() + 16);
        while (m.find()) {
            m.appendReplacement(sb, "\\\\\\${");
        }
        m.appendTail(sb);

        return sb.toString();
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.annotation.concurrent.NotThreadSafe;
import javax.validation.Validator;

import static org.assertj.core.api.Assertions.assertThat;


    @AfterEach
    @BeforeEach
        @SuppressWarnings("unused")

        @SuppressWarnings("unused")
            col.addViolation(FAILED + "subclass");
        }
        @SuppressWarnings("unused")
            col.addViolation(FAILED + "subclass");
        }

        }
        @SuppressWarnings("unused")
        @SuppressWarnings("unused")
        @SuppressWarnings("unused")
            col.addViolation("p2", FAILED);
        @SuppressWarnings("unused")
            col.addViolation("p", 3, FAILED);
        }

        @SuppressWarnings("unused")
        @SelfValidation
        public void validateFail4(ViolationCollector col) {
            col.addViolation("p", "four", FAILED);
    @SelfValidating
    public static class InjectionExample {
        @SuppressWarnings("unused")
        @SelfValidation
        public void validateFail(ViolationCollector col) {
            col.addViolation("${'value'}");
            col.addViolation("${'property'}", "${'value'}");
            col.addViolation("${'property'}", 1, "${'value'}");
            col.addViolation("${'property'}", "${'key'}", "${'value'}");
        }
    }

    public void failingExample() {
                .containsExactlyInAnyOrder(FAILED_RESULT);
                .isEmpty();

    public void subClassExample() {
                .containsExactlyInAnyOrder(
                        FAILED_RESULT,
                        FAILED_RESULT + "subclass"
                );
                .isEmpty();

    public void annotatedSubClassExample() {
                .containsExactlyInAnyOrder(
                        FAILED_RESULT,
                        FAILED_RESULT + "subclass"
                );
                .isEmpty();

    public void overridingSubClassExample() {
                .isEmpty();
                .isEmpty();
    public void correctExample() {
                .isEmpty();
                .isEmpty();
    public void multipleTestingOfSameClass() {
                .isEmpty();
                .isEmpty();
                .isEmpty();
    public void testDirectContextUsage() {
                .containsExactlyInAnyOrder(FAILED_RESULT);
                .isEmpty();
    public void complexExample() {
                .containsExactly(
                        " failed1",
                        "p2 failed",
                        "p[3] failed",
                        "p[four] failed");
                .isEmpty();
                .isEmpty();
                .containsExactlyInAnyOrder(
                        new LoggingEvent(
                                Level.ERROR,
                                "The method {} is annotated with @SelfValidation but does not have a single parameter of type {}",
                                InvalidExample.class.getMethod("validateFailAdditionalParameters", ViolationCollector.class, int.class),
                                ViolationCollector.class
                        ),
                        new LoggingEvent(
                                Level.ERROR,
                                "The method {} is annotated with @SelfValidation but does not return void. It is ignored",
                                InvalidExample.class.getMethod("validateFailReturn", ViolationCollector.class)
                        ),
                        new LoggingEvent(
                                Level.ERROR,
                                "The method {} is annotated with @SelfValidation but is not public",
                                InvalidExample.class.getDeclaredMethod("validateFailPrivate", ViolationCollector.class)
                        )
                );
    public void giveWarningIfNoValidationMethods() {
                .isEmpty();
                .containsExactlyInAnyOrder(
                        new LoggingEvent(
                                Level.WARN,
                                "The class {} is annotated with @SelfValidating but contains no valid methods that are annotated with @SelfValidation",
                                NoValidations.class
                        )

                );
    }

    @Test
    public void violationMessagesAreEscaped() {
        assertThat(ConstraintViolations.format(validator.validate(new InjectionExample()))).containsExactly(
                " ${'value'}",
                "${'property'} ${'value'}",
                "${'property'}[${'key'}] ${'value'}",
                "${'property'}[1] ${'value'}"
        assertThat(TestLoggerFactory.getAllLoggingEvents()).isEmpty();