001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.cli.util; 007 008import static org.fusesource.jansi.Ansi.ansi; 009 010import gov.nist.secauto.metaschema.core.model.constraint.ConstraintValidationFinding; 011import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level; 012import gov.nist.secauto.metaschema.core.model.validation.IValidationFinding; 013import gov.nist.secauto.metaschema.core.model.validation.IValidationResult; 014import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator.JsonValidationFinding; 015import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator.XmlValidationFinding; 016 017import org.apache.logging.log4j.LogBuilder; 018import org.apache.logging.log4j.LogManager; 019import org.apache.logging.log4j.Logger; 020import org.fusesource.jansi.Ansi; 021import org.fusesource.jansi.Ansi.Color; 022import org.xml.sax.SAXParseException; 023 024import java.net.URI; 025import java.util.List; 026 027import edu.umd.cs.findbugs.annotations.NonNull; 028import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 029 030public final class LoggingValidationHandler { 031 private static final Logger LOGGER = LogManager.getLogger(LoggingValidationHandler.class); 032 033 @NonNull 034 private static final LoggingValidationHandler NO_LOG_EXCPTION_INSTANCE = new LoggingValidationHandler(false); 035 @NonNull 036 private static final LoggingValidationHandler LOG_EXCPTION_INSTANCE = new LoggingValidationHandler(true); 037 038 private final boolean logExceptions; 039 040 /** 041 * Get a singleton instance of the logging validation handler. 042 * <p> 043 * This instance will not log exceptions. 044 * 045 * @return the instance 046 */ 047 @NonNull 048 public static LoggingValidationHandler instance() { 049 return instance(false); 050 } 051 052 /** 053 * Get a singleton instance of the logging validation handler. 054 * 055 * @param logExceptions 056 * {@code true} if this instance will log exceptions or {@code false} 057 * otherwise 058 * @return the instance 059 */ 060 @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED", 061 justification = "both values are class initialized") 062 @NonNull 063 public static LoggingValidationHandler instance(boolean logExceptions) { 064 return logExceptions ? LOG_EXCPTION_INSTANCE : NO_LOG_EXCPTION_INSTANCE; 065 } 066 067 private LoggingValidationHandler(boolean logExceptions) { 068 this.logExceptions = logExceptions; 069 } 070 071 /** 072 * Determine if exceptions should be logged. 073 * 074 * @return {@code true} if exceptions are logged or {@code false} otherwise 075 */ 076 public boolean isLogExceptions() { 077 return logExceptions; 078 } 079 080 /** 081 * Handle the provided collection of validation results. 082 * 083 * @param result 084 * the validation results 085 * @return {@code true} if the result is passing or {@code false} otherwise 086 */ 087 public boolean handleValidationResults(IValidationResult result) { 088 handleValidationFindings(result.getFindings()); 089 return result.isPassing(); 090 } 091 092 /** 093 * Handle the provided collection of validation findings. 094 * 095 * @param findings 096 * the findings to process 097 */ 098 public void handleValidationFindings(@NonNull List<? extends IValidationFinding> findings) { 099 for (IValidationFinding finding : findings) { 100 if (finding instanceof JsonValidationFinding) { 101 handleJsonValidationFinding((JsonValidationFinding) finding); 102 } else if (finding instanceof XmlValidationFinding) { 103 handleXmlValidationFinding((XmlValidationFinding) finding); 104 } else if (finding instanceof ConstraintValidationFinding) { 105 handleConstraintValidationFinding((ConstraintValidationFinding) finding); 106 } else { 107 throw new IllegalStateException(); 108 } 109 } 110 } 111 112 private void handleJsonValidationFinding(@NonNull JsonValidationFinding finding) { 113 Ansi ansi = generatePreamble(finding.getSeverity()); 114 115 ansi = ansi.a('[') 116 .fgBright(Color.WHITE) 117 .a(finding.getCause().getPointerToViolation()) 118 .reset() 119 .a(']'); 120 121 URI documentUri = finding.getDocumentUri(); 122 ansi = documentUri == null 123 ? ansi.format(" %s", finding.getMessage()) 124 : ansi.format(" %s [%s]", finding.getMessage(), documentUri.toString()); 125 126 getLogger(finding).log(ansi); 127 } 128 129 private void handleXmlValidationFinding(XmlValidationFinding finding) { 130 Ansi ansi = generatePreamble(finding.getSeverity()); 131 SAXParseException ex = finding.getCause(); 132 133 URI documentUri = finding.getDocumentUri(); 134 ansi = documentUri == null 135 ? ansi.format("%s [{%d,%d}]", 136 finding.getMessage(), 137 ex.getLineNumber(), 138 ex.getColumnNumber()) 139 : ansi.format("%s [%s{%d,%d}]", 140 finding.getMessage(), 141 documentUri.toString(), 142 ex.getLineNumber(), 143 ex.getColumnNumber()); 144 145 getLogger(finding).log(ansi); 146 } 147 148 private void handleConstraintValidationFinding(@NonNull ConstraintValidationFinding finding) { 149 Ansi ansi = generatePreamble(finding.getSeverity()); 150 151 getLogger(finding).log( 152 ansi.format("[%s] %s", finding.getTarget().getMetapath(), finding.getMessage())); 153 } 154 155 @NonNull 156 private LogBuilder getLogger(@NonNull IValidationFinding finding) { 157 LogBuilder retval; 158 switch (finding.getSeverity()) { 159 case CRITICAL: 160 retval = LOGGER.atFatal(); 161 break; 162 case ERROR: 163 retval = LOGGER.atError(); 164 break; 165 case WARNING: 166 retval = LOGGER.atWarn(); 167 break; 168 case INFORMATIONAL: 169 retval = LOGGER.atInfo(); 170 break; 171 default: 172 throw new IllegalArgumentException("Unknown level: " + finding.getSeverity().name()); 173 } 174 175 assert retval != null; 176 177 if (finding.getCause() != null && isLogExceptions()) { 178 retval.withThrowable(finding.getCause()); 179 } 180 181 return retval; 182 } 183 184 @SuppressWarnings("static-method") 185 @NonNull 186 private Ansi generatePreamble(@NonNull Level level) { 187 Ansi ansi = ansi().fgBright(Color.WHITE).a('[').reset(); 188 189 switch (level) { 190 case CRITICAL: 191 ansi = ansi.fgRed().a("CRITICAL").reset(); 192 break; 193 case ERROR: 194 ansi = ansi.fgBrightRed().a("ERROR").reset(); 195 break; 196 case WARNING: 197 ansi = ansi.fgBrightYellow().a("WARNING").reset(); 198 break; 199 case INFORMATIONAL: 200 ansi = ansi.fgBrightBlue().a("INFO").reset(); 201 break; 202 default: 203 ansi = ansi().fgBright(Color.MAGENTA).a(level.name()).reset(); 204 break; 205 } 206 ansi = ansi.fgBright(Color.WHITE).a("] ").reset(); 207 208 assert ansi != null; 209 return ansi; 210 } 211}