/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package fedora.client.utility.validate.process;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import fedora.client.utility.validate.ValidationResult;
import fedora.client.utility.validate.ValidationResultNotation;
import fedora.client.utility.validate.ValidationResults;
/**
* An implementation of {@link ValidationResults} for use with the
* {@link ValidatorProcess}. When {@link #record(ValidationResult)} is called,
* the result is evaluated against the current Log4J configuration. If any of
* the notes qualify for logging, they will be preceded by an overall log
* record.
*
* @author Jim Blake
*/
public class Log4jValidationResults
implements ValidationResults {
public static final String LOGGING_CATEGORY_PREFIX = "Validator";
/** These Log4J settings will be used if no others are provided. */
private static final Properties DEFAULT_CONFIG_PROPERTIES =
initDefaultProperties();
/** Initialize the {@link #DEFAULT_CONFIG_PROPERTIES}. */
private static Properties initDefaultProperties() {
Properties props = new Properties();
// Create an appender for the root logger.
props.put("log4j.appender.STDOUT", "org.apache.log4j.ConsoleAppender");
props.put("log4j.appender.STDOUT.layout",
"org.apache.log4j.PatternLayout");
props.put("log4j.appender.STDOUT.layout.ConversionPattern",
"%d{yyyy-MM-dd' 'HH:mm:ss.SSS} %p [%c] %m%n");
// Assign the appender to the root logger.
props.put("log4j.rootLogger", "INFO, STDOUT");
// Create an appender for the validation messages.
props.put("log4j.appender.VALIDATOR",
"org.apache.log4j.ConsoleAppender");
props.put("log4j.appender.VALIDATOR.layout",
"org.apache.log4j.PatternLayout");
props.put("log4j.appender.VALIDATOR.layout.ConversionPattern",
"%p [%c] %m%n");
// Assign the appender to the validator, with no pass-through.
props.put("log4j.logger.Validator=INFO", "INFO, VALIDATOR");
props.put("log4j.additivity.Validator", "false");
return props;
}
/** How many notations were at error level? */
private int numberOfErrors;
/** How many notations were at error level and printed to the log? */
private int numberOfFilteredErrors;
/** How many notations were at warning level? */
private int numberOfWarnings;
/** How many notations were at warning level and printed to the log? */
private int numberOfFilteredWarnings;
/** How many object had no errors and no warnings? */
private int numberOfValidObjects;
/** How many objects had errors? */
private int numberOfInvalidObjects;
/** How many objects had warnings but no errors? */
private int numberOfIndeterminateObjects;
public Log4jValidationResults(Properties configProperties) {
LogManager.resetConfiguration();
if (configProperties == null || configProperties.isEmpty()) {
PropertyConfigurator.configure(DEFAULT_CONFIG_PROPERTIES);
} else {
PropertyConfigurator.configure(configProperties);
}
}
/**
* This class does not maintain a collection of the {@link ValidationResult}
* objects. Instead, it logs each as it arrives, if it is severe enough.
*/
public void record(ValidationResult rawResult) {
// If the overall log level is set to debug, dump the result to the log.
getBaseLogger().debug(rawResult.toString());
// Process the result to get the info we will need.
Log4jValidationResult result = new Log4jValidationResult(rawResult);
// Analyze this result and update the statistics accordingly.
incrementTallys(result);
// Any notes that are not filtered out get written to the log.
for (Log4jNote note : result.getNotes()) {
note.log();
}
}
/**
* Each {@link ValidationResult} was logged (or not) when it arrived, so
* just log a summary message at the end.
*/
public void closeResults() {
Logger logger = getBaseLogger();
logger.info("Validated " + numberOfObjects() + " objects: "
+ numberOfValidObjects + " valid, " + numberOfInvalidObjects
+ " invalid, " + numberOfIndeterminateObjects
+ " indeterminate.");
if (numberOfErrors == numberOfFilteredErrors
&& numberOfWarnings == numberOfFilteredWarnings) {
logger.info(numberOfErrors + " errors, " + numberOfWarnings
+ " warnings.");
} else {
logger.info(numberOfFilteredErrors + " filtered errors ("
+ numberOfErrors + " unfiltered), "
+ numberOfFilteredWarnings + " filtered warnings ("
+ numberOfWarnings + " unfiltered)");
}
}
/**
* How many object were validated?
*/
private int numberOfObjects() {
return numberOfValidObjects + numberOfIndeterminateObjects
+ numberOfInvalidObjects;
}
/**
* Adjust the running tallys to include this result.
*/
private void incrementTallys(Log4jValidationResult result) {
// For the whole record: valid? invalid? indeterminate?
switch (result.getSeverityLevel()) {
case ERROR:
numberOfInvalidObjects++;
break;
case WARN:
numberOfIndeterminateObjects++;
break;
default:
numberOfValidObjects++;
break;
}
for (Log4jNote note : result.getNotes()) {
// For each note: error? warning?
switch (note.getLevel()) {
case ERROR:
numberOfErrors++;
break;
case WARN:
numberOfWarnings++;
break;
default:
break;
}
if (note.isLoggable()) {
// For each loggable note: error? warning?
switch (note.getLevel()) {
case ERROR:
numberOfFilteredErrors++;
break;
case WARN:
numberOfFilteredWarnings++;
break;
default:
break;
}
}
}
}
private Logger getBaseLogger() {
return Logger.getLogger(LOGGING_CATEGORY_PREFIX);
}
/**
* Extracts information from a {@link ValidationResult} and then serves it
* up when we need it.
*/
private static class Log4jValidationResult {
private final String pid;
private final ValidationResult.Level severityLevel;
private final List<Log4jNote> notes;
public Log4jValidationResult(ValidationResult rawResult) {
pid = rawResult.getObject().getPid();
severityLevel = rawResult.getSeverityLevel();
List<Log4jNote> notes = new ArrayList<Log4jNote>();
for (ValidationResultNotation rawNote : rawResult.getNotes()) {
notes.add(new Log4jNote(rawNote, pid));
}
this.notes = Collections.unmodifiableList(notes);
}
public String getPid() {
return pid;
}
public ValidationResult.Level getSeverityLevel() {
return severityLevel;
}
public List<Log4jNote> getNotes() {
return notes;
}
}
/**
* Extracts information from a {@link ValidationResultNotation} and then
* serves it up when we need it.
*/
private static class Log4jNote {
private final ValidationResult.Level level;
private final Level loggingLevel;
private final Logger logger;
private final boolean loggable;
private final String message;
public Log4jNote(ValidationResultNotation rawNote, String objectPid) {
level = rawNote.getLevel();
loggingLevel = Level.toLevel(rawNote.getLevel().toString());
String category =
LOGGING_CATEGORY_PREFIX + "." + rawNote.getCategory();
logger = Logger.getLogger(category);
loggable =
loggingLevel.isGreaterOrEqual(logger.getEffectiveLevel());
message = "pid='" + objectPid + "' " + rawNote.getMessage();
}
public ValidationResult.Level getLevel() {
return level;
}
public boolean isLoggable() {
return loggable;
}
public void log() {
if (loggable) {
logger.log(loggingLevel, message);
}
}
}
}