1
2
3
4
5
6 package gov.nist.secauto.metaschema.cli.util;
7
8 import static org.fusesource.jansi.Ansi.ansi;
9
10 import gov.nist.secauto.metaschema.core.model.constraint.ConstraintValidationFinding;
11 import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
12 import gov.nist.secauto.metaschema.core.model.validation.AbstractValidationResultProcessor;
13 import gov.nist.secauto.metaschema.core.model.validation.IValidationFinding;
14 import gov.nist.secauto.metaschema.core.model.validation.JsonSchemaContentValidator.JsonValidationFinding;
15 import gov.nist.secauto.metaschema.core.model.validation.XmlSchemaContentValidator.XmlValidationFinding;
16 import gov.nist.secauto.metaschema.modules.sarif.SarifValidationHandler;
17
18 import org.apache.logging.log4j.LogBuilder;
19 import org.apache.logging.log4j.LogManager;
20 import org.apache.logging.log4j.Logger;
21 import org.fusesource.jansi.Ansi;
22 import org.fusesource.jansi.Ansi.Color;
23 import org.xml.sax.SAXParseException;
24
25 import java.net.URI;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28
29 import edu.umd.cs.findbugs.annotations.NonNull;
30 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
31
32
33
34
35
36 public final class LoggingValidationHandler
37 extends AbstractValidationResultProcessor {
38 private static final Logger LOGGER = LogManager.getLogger(LoggingValidationHandler.class);
39
40 @NonNull
41 private static final LoggingValidationHandler NO_LOG_EXCPTION_INSTANCE = new LoggingValidationHandler(false);
42 @NonNull
43 private static final LoggingValidationHandler LOG_EXCPTION_INSTANCE = new LoggingValidationHandler(true);
44
45 private final boolean logExceptions;
46
47
48
49
50
51
52
53
54 @NonNull
55 public static LoggingValidationHandler instance() {
56 return instance(false);
57 }
58
59
60
61
62
63
64
65
66
67 @SuppressFBWarnings(value = "SING_SINGLETON_GETTER_NOT_SYNCHRONIZED",
68 justification = "both values are class initialized")
69 @NonNull
70 public static LoggingValidationHandler instance(boolean logExceptions) {
71 return logExceptions ? LOG_EXCPTION_INSTANCE : NO_LOG_EXCPTION_INSTANCE;
72 }
73
74 private LoggingValidationHandler(boolean logExceptions) {
75 this.logExceptions = logExceptions;
76 }
77
78
79
80
81
82
83 public boolean isLogExceptions() {
84 return logExceptions;
85 }
86
87 @Override
88 protected void handleJsonValidationFinding(@NonNull JsonValidationFinding finding) {
89 Ansi ansi = generatePreamble(finding.getSeverity());
90
91 ansi = ansi.a('[')
92 .fgBright(Color.WHITE)
93 .a(finding.getCause().getPointerToViolation())
94 .reset()
95 .a(']');
96
97 URI documentUri = finding.getDocumentUri();
98 ansi = documentUri == null
99 ? ansi.format(" %s", finding.getMessage())
100 : ansi.format(" %s [%s]", finding.getMessage(), documentUri.toString());
101
102 getLogger(finding).log(ansi);
103 }
104
105 @Override
106 protected void handleXmlValidationFinding(XmlValidationFinding finding) {
107 Ansi ansi = generatePreamble(finding.getSeverity());
108 SAXParseException ex = finding.getCause();
109
110 URI documentUri = finding.getDocumentUri();
111 ansi = documentUri == null
112 ? ansi.format("%s [{%d,%d}]",
113 finding.getMessage(),
114 ex.getLineNumber(),
115 ex.getColumnNumber())
116 : ansi.format("%s [%s{%d,%d}]",
117 finding.getMessage(),
118 documentUri.toString(),
119 ex.getLineNumber(),
120 ex.getColumnNumber());
121
122 getLogger(finding).log(ansi);
123 }
124
125 @Override
126 protected void handleConstraintValidationFinding(@NonNull ConstraintValidationFinding finding) {
127 Ansi ansi = generatePreamble(finding.getSeverity());
128
129 ansi.format("[%s]", finding.getTarget().getMetapath());
130
131 String id = finding.getIdentifier();
132 if (id != null) {
133 ansi.format(" %s:", id);
134 }
135
136 ansi.format(" %s", finding.getMessage());
137
138 Set<String> helpUrls = finding.getConstraints().stream()
139 .flatMap(constraint -> constraint.getPropertyValues(SarifValidationHandler.SARIF_HELP_URL_KEY).stream())
140 .collect(Collectors.toSet());
141 if (!helpUrls.isEmpty()) {
142 ansi.format(" (help: %s)",
143 helpUrls.stream().collect(Collectors.joining(", ")));
144 }
145
146 getLogger(finding).log(ansi);
147 }
148
149 @NonNull
150 private LogBuilder getLogger(@NonNull IValidationFinding finding) {
151 LogBuilder retval;
152 switch (finding.getSeverity()) {
153 case CRITICAL:
154 retval = LOGGER.atFatal();
155 break;
156 case ERROR:
157 retval = LOGGER.atError();
158 break;
159 case WARNING:
160 retval = LOGGER.atWarn();
161 break;
162 case INFORMATIONAL:
163 retval = LOGGER.atInfo();
164 break;
165 case DEBUG:
166 retval = LOGGER.isDebugEnabled() ? LOGGER.atDebug() : LOGGER.atInfo();
167 break;
168 default:
169 throw new IllegalArgumentException("Unknown level: " + finding.getSeverity().name());
170 }
171
172 assert retval != null;
173
174 if (finding.getCause() != null && isLogExceptions()) {
175 retval.withThrowable(finding.getCause());
176 }
177
178 return retval;
179 }
180
181 @SuppressWarnings("static-method")
182 @NonNull
183 private Ansi generatePreamble(@NonNull Level level) {
184 Ansi ansi = ansi().fgBright(Color.WHITE).a('[').reset();
185
186 switch (level) {
187 case CRITICAL:
188 ansi = ansi.fgRed().a("CRITICAL").reset();
189 break;
190 case ERROR:
191 ansi = ansi.fgBrightRed().a("ERROR").reset();
192 break;
193 case WARNING:
194 ansi = ansi.fgBrightYellow().a("WARNING").reset();
195 break;
196 case INFORMATIONAL:
197 ansi = ansi.fgBrightBlue().a("INFO").reset();
198 break;
199 case DEBUG:
200 ansi = ansi.fgBrightCyan().a("DEBUG").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 }