[LOG4J2-2421] check for the presence of required ThreadContext atributes
diff --git a/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/AbstractEventLogger.java b/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/AbstractEventLogger.java
index da2254a..575b950 100644
--- a/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/AbstractEventLogger.java
+++ b/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/AbstractEventLogger.java
@@ -163,8 +163,7 @@
         }
 
         List<String> reqCtxAttrs = catalogManager.getRequiredContextAttributes(eventName, event.getCatalogId());
-
-        if (reqCtxAttrs != null) {
+        if (reqCtxAttrs != null && !reqCtxAttrs.isEmpty()) {
             StringBuilder sb = new StringBuilder();
             for (String attr : reqCtxAttrs) {
                 if (!ThreadContext.containsKey(attr)) {
@@ -179,7 +178,23 @@
                         " is missing required RequestContextMapping values for " + sb.toString());
             }
         }
+
         Map<String, Attribute> reqCtxAttributes = catalogManager.getRequestContextAttributes();
+        for (Map.Entry<String, Attribute> entry : reqCtxAttributes.entrySet()) {
+            Attribute attribute = entry.getValue();
+            String attr = entry.getKey();
+            if (attribute.isRequired() && !ThreadContext.containsKey(attr)) {
+                if (errors.length() > 0) {
+                    errors.append(", ");
+                }
+                errors.append(attr);
+            }
+        }
+        if (errors.length() > 0) {
+            throw new AuditException("Event " + eventName +
+                                             " is missing required Thread Context values for " + errors.toString());
+        }
+
         for (Map.Entry<String, String> entry : ThreadContext.getImmutableContext().entrySet()) {
             Attribute attribute = reqCtxAttributes.get(entry.getKey());
             if (attribute == null) {
@@ -195,6 +210,7 @@
         if (errors.length() > 0) {
             throw new AuditException("Event " + eventName + " has incorrect data in the Thread Context: " + errors.toString());
         }
+
         msg.putAll(attributes);
         try {
             logEvent(msg);
diff --git a/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/AuditLoggerTest.java b/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/AuditLoggerTest.java
index abe6eb8..eab5dd1 100644
--- a/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/AuditLoggerTest.java
+++ b/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/AuditLoggerTest.java
@@ -70,11 +70,14 @@
     @Before
     public void before() {
         app.clear();
+        ThreadContext.clearMap();
     }
 
     @Test
     public void testAuditLogger() {
+        ThreadContext.put("accountNumber", "12345");
         ThreadContext.put("companyId", "12345");
+        ThreadContext.put("userId", "JohnDoe");
         ThreadContext.put("ipAddress", "127.0.0.1");
         ThreadContext.put("environment", "dev");
         ThreadContext.put("product", "TestProduct");
@@ -100,7 +103,16 @@
     }
 
     @Test(expected = AuditException.class)
-    public void testBadAttribute() {
+    public void testMissingRequestContextAttribute() {
+        Map<String, String> properties = new HashMap<String, String>();
+        properties.put("toAccount", "123456");
+        properties.put("fromAccount", "111111");
+        properties.put("amount", "111.55");
+        auditLogger.logEvent("transfer", properties);
+    }
+
+    @Test(expected = AuditException.class)
+    public void testMissingEventAttribute() {
         ThreadContext.put("companyId", "12345");
         ThreadContext.put("ipAddress", "127.0.0.1");
         ThreadContext.put("environment", "dev");
diff --git a/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/TransferTest.java b/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/TransferTest.java
index 7c5ce5e..ebc46ba 100644
--- a/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/TransferTest.java
+++ b/log4j-audit/log4j-audit-api/src/test/java/org/apache/logging/log4j/audit/TransferTest.java
@@ -65,7 +65,7 @@
     }
 
     @Test(expected = ConstraintValidationException.class)
-    public void testValidationFailure() {
+    public void testValidationFailureForMissingRequestContextAttribute() {
         Transfer transfer = LogEventFactory.getEvent(Transfer.class);
         ThreadContext.put("companyId", "12345");
         ThreadContext.put("ipAddress", "127.0.0.1");
@@ -80,6 +80,23 @@
         fail("Should have thrown an AuditException");
     }
 
+    @Test(expected = ConstraintValidationException.class)
+    public void testValidationFailureForMissingEventAttribute() {
+        Transfer transfer = LogEventFactory.getEvent(Transfer.class);
+        ThreadContext.put("accountNumber", "12345");
+        ThreadContext.put("companyId", "12345");
+        ThreadContext.put("userId", "JohnDoe");
+        ThreadContext.put("ipAddress", "127.0.0.1");
+        ThreadContext.put("environment", "dev");
+        ThreadContext.put("product", "TestProduct");
+        ThreadContext.put("timeZone", "America/Phoenix");
+        ThreadContext.put("loginId", "TestUser");
+        transfer.setToAccount(123456);
+        transfer.setFromAccount(111111);
+        transfer.logEvent();
+        fail("Should have thrown an AuditException");
+    }
+
     @Test
     public void testAuditClass() {
         Transfer transfer = LogEventFactory.getEvent(Transfer.class);
@@ -117,7 +134,7 @@
     }
 
     @Test(expected = ConstraintValidationException.class)
-    public void testAuditLogException() {
+    public void testAuditLogWithMissingRequestContextAttribute() {
         ThreadContext.put("userId", "JohnDoe");
         ThreadContext.put("ipAddress", "127.0.0.1");
         ThreadContext.put("environment", "dev");
@@ -131,6 +148,21 @@
         LogEventFactory.logEvent(Transfer.class, properties);
     }
 
+    @Test(expected = ConstraintValidationException.class)
+    public void testAuditLogWithMissingEventAttribute() {
+        ThreadContext.put("accountNumber", "12345");
+        ThreadContext.put("userId", "JohnDoe");
+        ThreadContext.put("ipAddress", "127.0.0.1");
+        ThreadContext.put("environment", "dev");
+        ThreadContext.put("product", "TestProduct");
+        ThreadContext.put("timeZone", "America/Phoenix");
+        ThreadContext.put("loginId", "TestUser");
+        Map<String, String> properties = new HashMap<>();
+        properties.put("toAccount", "123456");
+        properties.put("fromAccount", "111111");
+        LogEventFactory.logEvent(Transfer.class, properties);
+    }
+
     @Test
     public void testAuditLog() {
         ThreadContext.put("accountNumber", "12345");