LOG4J2-2428: extract and use a single event validator
diff --git a/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/LogEventFactory.java b/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/LogEventFactory.java
index ac43507..4105b97 100644
--- a/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/LogEventFactory.java
+++ b/log4j-audit/log4j-audit-api/src/main/java/org/apache/logging/log4j/audit/LogEventFactory.java
@@ -43,7 +43,6 @@
 import org.apache.logging.log4j.audit.exception.ConstraintValidationException;
 import org.apache.logging.log4j.catalog.api.plugins.ConstraintPlugins;
 import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.apache.logging.log4j.spi.ExtendedLogger;
 
 import static org.apache.logging.log4j.catalog.api.util.StringUtils.appendNewline;
 
@@ -87,20 +86,24 @@
 
 		Class<?>[] interfaces = new Class<?>[] { intrface };
 
-        String eventId = NamingUtils.lowerFirst(intrface.getSimpleName());
-        int msgLength = getMaxLength(intrface);
-        AuditMessage msg = new AuditMessage(eventId, msgLength);
-		AuditEvent audit = (AuditEvent) Proxy.newProxyInstance(intrface
+	    AuditMessage msg = buildAuditMessage(intrface);
+	    AuditEvent audit = (AuditEvent) Proxy.newProxyInstance(intrface
 				.getClassLoader(), interfaces, new AuditProxy(msg, intrface));
 
 		return (T) audit;
 	}
 
-    private static <T> int getMaxLength(Class<T> intrface) {
+	private static <T> int getMaxLength(Class<T> intrface) {
         MaxLength maxLength = intrface.getAnnotation(MaxLength.class);
         return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength.value();
     }
 
+	private static AuditMessage buildAuditMessage(Class<?> intrface) {
+		String eventId = NamingUtils.lowerFirst(intrface.getSimpleName());
+		int msgLength = getMaxLength(intrface);
+		return new AuditMessage(eventId, msgLength);
+	}
+
     /**
      *
      * This method is used to construct and AuditMessage from a set of properties and the Event interface
@@ -120,49 +123,51 @@
      * @param handler Class that gets control when an exception occurs logging the event.
      */
     public static void logEvent(Class<?> intrface, Map<String, String> properties, AuditExceptionHandler handler) {
-        StringBuilder errors = new StringBuilder();
-        validateContextConstraints(intrface, errors);
+	    AuditMessage msg = buildAuditMessage(intrface);
 
-        String eventId = NamingUtils.lowerFirst(intrface.getSimpleName());
-        int maxLength = getMaxLength(intrface);
-        AuditMessage msg = new AuditMessage(eventId, maxLength);
+	    if (properties != null) {
+		    for (Map.Entry<String, String> entry : properties.entrySet()) {
+			    msg.put(entry.getKey(), entry.getValue());
+		    }
+	    }
 
-        if (properties == null) {
-            properties = Collections.emptyMap();
-        }
-        List<Property> props = getProperties(intrface);
-        Map<String, Property> propertyMap = new HashMap<>();
-
-        for (Property property : props) {
-            propertyMap.put(property.name, property);
-            if (property.isRequired && !properties.containsKey(property.name)) {
-                if (errors.length() > 0) {
-                    errors.append("\n");
-                }
-                errors.append("Required attribute ").append(property.name).append(" is missing from ").append(eventId);
-            }
-            if (properties.containsKey(property.name)) {
-                validateConstraints(false, property.constraints, property.name, properties, errors);
-            }
-        }
-
-        for (Map.Entry<String, String> entry : properties.entrySet()) {
-            if (!propertyMap.containsKey(entry.getKey())) {
-                if (errors.length() > 0) {
-                    errors.append("Attribute ").append(entry.getKey()).append(" is not defined for ").append(eventId);
-                }
-            }
-        }
-
-        if (errors.length() > 0) {
-            throw new ConstraintValidationException(errors.toString());
-        }
-        for (Map.Entry<String, String> entry : properties.entrySet()) {
-            msg.put(entry.getKey(), entry.getValue());
-        }
-        logEvent(msg, handler);
+	    validateEvent(intrface, msg);
+	    logEvent(msg, handler);
     }
 
+	private static void validateEvent(Class<?> intrface, AuditMessage msg) {
+		StringBuilder errors = new StringBuilder();
+		validateContextConstraints(intrface, errors);
+
+		List<Property> props = getProperties(intrface);
+		Map<String, Property> propertyMap = new HashMap<>();
+
+		for (Property property : props) {
+		    propertyMap.put(property.name, property);
+		    if (property.isRequired && !msg.containsKey(property.name)) {
+		        if (errors.length() > 0) {
+		            errors.append("\n");
+		        }
+		        errors.append("Required attribute ").append(property.name).append(" is missing from ").append(msg.getId().getName());
+		    }
+		    if (msg.containsKey(property.name)) {
+		        validateConstraints(false, property.constraints, property.name, msg, errors);
+		    }
+		}
+
+		msg.forEach((key, value) -> {
+			if (!propertyMap.containsKey(key)) {
+				if (errors.length() > 0) {
+					errors.append("Attribute ").append(key).append(" is not defined for ").append(msg.getId().getName());
+				}
+			}
+		});
+
+		if (errors.length() > 0) {
+		    throw new ConstraintValidationException(errors.toString());
+		}
+	}
+
     /**
      * Used to Log the actual AuditMessage.
      * @param msg The AuditMessage.
@@ -260,50 +265,16 @@
 
 		@Override
         @SuppressWarnings("unchecked")
-		public Object invoke(Object o, Method method, Object[] objects)
-				throws Throwable {
+		public Object invoke(Object o, Method method, Object[] objects) {
 			if (method.getName().equals("toString") && method.getParameterCount() == 0) {
 				return msg.toString();
 			}
 
 			if (method.getName().equals("logEvent")) {
 
-				StringBuilder errors = new StringBuilder();
-				validateContextConstraints(intrface, errors);
+				validateEvent(intrface, msg);
 
-                StringBuilder missing = new StringBuilder();
-				Method[] methods = intrface.getMethods();
-
-				for (Method _method : methods) {
-					String name = NamingUtils.lowerFirst(NamingUtils.getMethodShortName(_method.getName()));
-
-					Annotation[] annotations = _method.getDeclaredAnnotations();
-					for (Annotation annotation : annotations) {
-                        if (annotation instanceof Required && msg.get(name) == null) {
-                            if (missing.length() > 0) {
-                                missing.append(", ");
-                            }
-                            missing.append(name);
-                        }
-					}
-				}
-				if (errors.length() > 0) {
-				    if (missing.length() > 0) {
-				        errors.append("\n");
-				        errors.append("Required attributes are missing: ");
-				        errors.append(missing.toString());
-                    }
-                } else if (missing.length() > 0) {
-                    errors.append("Required attributes are missing: ");
-				    errors = missing;
-                }
-
-				if (errors.length() > 0) {
-					throw new ConstraintValidationException("Event " + msg.getId().getName() +
-							" has errors :\n" + errors.toString());
-				}
-
-                logEvent(msg, auditExceptionHandler);
+				logEvent(msg, auditExceptionHandler);
                 return null;
 			}
             if (method.getName().equals("setCompletionStatus")) {
@@ -366,11 +337,11 @@
 
 			return null;
 		}
-    }
+	}
 
     private static void validateConstraints(boolean isRequestContext, Constraint[] constraints, String name,
-                                            Map<String, String> properties, StringBuilder errors) {
-        String value = isRequestContext ? ThreadContext.get(name) : properties.get(name);
+                                            AuditMessage msg, StringBuilder errors) {
+        String value = isRequestContext ? ThreadContext.get(name) : msg.get(name);
         validateConstraints(isRequestContext, constraints, name, value, errors);
     }