1
2
3
4
5
6 package gov.nist.secauto.metaschema.cli.commands;
7
8 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor;
9 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
10 import gov.nist.secauto.metaschema.cli.processor.ExitCode;
11 import gov.nist.secauto.metaschema.cli.processor.command.AbstractCommandExecutor;
12 import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
13 import gov.nist.secauto.metaschema.cli.processor.command.CommandExecutionException;
14 import gov.nist.secauto.metaschema.cli.processor.command.DefaultExtraArgument;
15 import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
16 import gov.nist.secauto.metaschema.cli.util.LoggingValidationHandler;
17 import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
18 import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
19 import gov.nist.secauto.metaschema.core.metapath.MetapathException;
20 import gov.nist.secauto.metaschema.core.model.IModule;
21 import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet;
22 import gov.nist.secauto.metaschema.core.model.constraint.ValidationFeature;
23 import gov.nist.secauto.metaschema.core.model.validation.AggregateValidationResult;
24 import gov.nist.secauto.metaschema.core.model.validation.IValidationResult;
25 import gov.nist.secauto.metaschema.core.util.IVersionInfo;
26 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
27 import gov.nist.secauto.metaschema.databind.IBindingContext;
28 import gov.nist.secauto.metaschema.databind.IBindingContext.ISchemaValidationProvider;
29 import gov.nist.secauto.metaschema.databind.io.Format;
30 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
31 import gov.nist.secauto.metaschema.modules.sarif.SarifValidationHandler;
32
33 import org.apache.commons.cli.CommandLine;
34 import org.apache.commons.cli.Option;
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37
38 import java.io.FileNotFoundException;
39 import java.io.IOException;
40 import java.net.URI;
41 import java.net.UnknownHostException;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.util.Collection;
45 import java.util.List;
46 import java.util.Set;
47
48 import edu.umd.cs.findbugs.annotations.NonNull;
49 import edu.umd.cs.findbugs.annotations.Nullable;
50
51
52
53
54 public abstract class AbstractValidateContentCommand
55 extends AbstractTerminalCommand {
56 private static final Logger LOGGER = LogManager.getLogger(AbstractValidateContentCommand.class);
57 @NonNull
58 private static final String COMMAND = "validate";
59 @NonNull
60 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
61 new DefaultExtraArgument("file-or-URI-to-validate", true)));
62
63 @NonNull
64 private static final Option CONSTRAINTS_OPTION = ObjectUtils.notNull(
65 Option.builder("c")
66 .hasArgs()
67 .argName("URL")
68 .desc("additional constraint definitions")
69 .build());
70 @NonNull
71 private static final Option SARIF_OUTPUT_FILE_OPTION = ObjectUtils.notNull(
72 Option.builder("o")
73 .hasArg()
74 .argName("FILE")
75 .desc("write SARIF results to the provided FILE")
76 .numberOfArgs(1)
77 .build());
78 @NonNull
79 private static final Option SARIF_INCLUDE_PASS_OPTION = ObjectUtils.notNull(
80 Option.builder()
81 .longOpt("sarif-include-pass")
82 .desc("include pass results in SARIF")
83 .build());
84 @NonNull
85 private static final Option NO_SCHEMA_VALIDATION_OPTION = ObjectUtils.notNull(
86 Option.builder()
87 .longOpt("disable-schema-validation")
88 .desc("do not perform schema validation")
89 .build());
90 @NonNull
91 private static final Option NO_CONSTRAINT_VALIDATION_OPTION = ObjectUtils.notNull(
92 Option.builder()
93 .longOpt("disable-constraint-validation")
94 .desc("do not perform constraint validation")
95 .build());
96
97 @Override
98 public String getName() {
99 return COMMAND;
100 }
101
102 @SuppressWarnings("null")
103 @Override
104 public Collection<? extends Option> gatherOptions() {
105 return List.of(
106 MetaschemaCommands.AS_FORMAT_OPTION,
107 CONSTRAINTS_OPTION,
108 SARIF_OUTPUT_FILE_OPTION,
109 SARIF_INCLUDE_PASS_OPTION,
110 NO_SCHEMA_VALIDATION_OPTION,
111 NO_CONSTRAINT_VALIDATION_OPTION);
112 }
113
114 @Override
115 public List<ExtraArgument> getExtraArguments() {
116 return EXTRA_ARGUMENTS;
117 }
118
119
120
121
122 protected abstract class AbstractValidationCommandExecutor
123 extends AbstractCommandExecutor {
124
125
126
127
128
129
130
131
132
133 public AbstractValidationCommandExecutor(
134 @NonNull CallingContext callingContext,
135 @NonNull CommandLine commandLine) {
136 super(callingContext, commandLine);
137 }
138
139
140
141
142
143
144
145
146
147
148 @NonNull
149 protected abstract IBindingContext getBindingContext(@NonNull Set<IConstraintSet> constraintSets)
150 throws CommandExecutionException;
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167 @NonNull
168 protected abstract IModule getModule(
169 @NonNull CommandLine commandLine,
170 @NonNull IBindingContext bindingContext)
171 throws CommandExecutionException;
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189 @NonNull
190 protected abstract ISchemaValidationProvider getSchemaValidationProvider(
191 @NonNull IModule module,
192 @NonNull CommandLine commandLine,
193 @NonNull IBindingContext bindingContext);
194
195
196
197
198 @SuppressWarnings("PMD.OnlyOneReturn")
199 @Override
200 public void execute() throws CommandExecutionException {
201 CommandLine cmdLine = getCommandLine();
202 URI currentWorkingDirectory = ObjectUtils.notNull(getCurrentWorkingDirectory().toUri());
203
204 Set<IConstraintSet> constraintSets = MetaschemaCommands.loadConstraintSets(
205 cmdLine,
206 CONSTRAINTS_OPTION,
207 currentWorkingDirectory);
208
209 List<String> extraArgs = cmdLine.getArgList();
210
211 URI source = MetaschemaCommands.handleSource(
212 ObjectUtils.requireNonNull(extraArgs.get(0)),
213 currentWorkingDirectory);
214
215 IBindingContext bindingContext = getBindingContext(constraintSets);
216 IBoundLoader loader = bindingContext.newBoundLoader();
217 Format asFormat = MetaschemaCommands.determineSourceFormat(
218 cmdLine,
219 MetaschemaCommands.AS_FORMAT_OPTION,
220 loader,
221 source);
222
223 IValidationResult validationResult = validate(source, asFormat, cmdLine, bindingContext);
224 handleOutput(source, validationResult, cmdLine, bindingContext);
225
226 if (validationResult == null || validationResult.isPassing()) {
227 if (LOGGER.isInfoEnabled()) {
228 LOGGER.info("The file '{}' is valid.", source);
229 }
230 } else if (LOGGER.isErrorEnabled()) {
231 LOGGER.error("The file '{}' is invalid.", source);
232 }
233
234 if (validationResult != null && !validationResult.isPassing()) {
235 throw new CommandExecutionException(ExitCode.FAIL);
236 }
237 }
238
239 @SuppressWarnings("PMD.CyclomaticComplexity")
240 @Nullable
241 private IValidationResult validate(
242 @NonNull URI source,
243 @NonNull Format asFormat,
244 @NonNull CommandLine commandLine,
245 @NonNull IBindingContext bindingContext) throws CommandExecutionException {
246
247 if (LOGGER.isInfoEnabled()) {
248 LOGGER.info("Validating '{}' as {}.", source, asFormat.name());
249 }
250
251 IValidationResult validationResult = null;
252 try {
253 IModule module = bindingContext.registerModule(getModule(commandLine, bindingContext));
254 if (!commandLine.hasOption(NO_SCHEMA_VALIDATION_OPTION)) {
255
256 validationResult = getSchemaValidationProvider(module, commandLine, bindingContext)
257 .validateWithSchema(source, asFormat, bindingContext);
258 }
259
260 if (!commandLine.hasOption(NO_CONSTRAINT_VALIDATION_OPTION)) {
261 IMutableConfiguration<ValidationFeature<?>> configuration = new DefaultConfiguration<>();
262 if (commandLine.hasOption(SARIF_OUTPUT_FILE_OPTION) && commandLine.hasOption(SARIF_INCLUDE_PASS_OPTION)) {
263 configuration.enableFeature(ValidationFeature.VALIDATE_GENERATE_PASS_FINDINGS);
264 }
265
266
267 IValidationResult constraintValidationResult = bindingContext.validateWithConstraints(source, configuration);
268 validationResult = validationResult == null
269 ? constraintValidationResult
270 : AggregateValidationResult.aggregate(validationResult, constraintValidationResult);
271 }
272 } catch (FileNotFoundException ex) {
273 throw new CommandExecutionException(
274 ExitCode.IO_ERROR,
275 String.format("Resource not found at '%s'", source),
276 ex);
277 } catch (UnknownHostException ex) {
278 throw new CommandExecutionException(
279 ExitCode.IO_ERROR,
280 String.format("Unknown host for '%s'.", source),
281 ex);
282 } catch (IOException ex) {
283 throw new CommandExecutionException(ExitCode.IO_ERROR, ex.getLocalizedMessage(), ex);
284 } catch (MetapathException ex) {
285 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex.getLocalizedMessage(), ex);
286 }
287 return validationResult;
288 }
289
290 private void handleOutput(
291 @NonNull URI source,
292 @Nullable IValidationResult validationResult,
293 @NonNull CommandLine commandLine,
294 @NonNull IBindingContext bindingContext) throws CommandExecutionException {
295 if (commandLine.hasOption(SARIF_OUTPUT_FILE_OPTION) && LOGGER.isInfoEnabled()) {
296 Path sarifFile = ObjectUtils.notNull(Paths.get(commandLine.getOptionValue(SARIF_OUTPUT_FILE_OPTION)));
297
298 IVersionInfo version
299 = getCallingContext().getCLIProcessor().getVersionInfos().get(CLIProcessor.COMMAND_VERSION);
300
301 try {
302 SarifValidationHandler sarifHandler = new SarifValidationHandler(source, version);
303 if (validationResult != null) {
304 sarifHandler.addFindings(validationResult.getFindings());
305 }
306 sarifHandler.write(sarifFile, bindingContext);
307 } catch (IOException ex) {
308 throw new CommandExecutionException(ExitCode.IO_ERROR, ex.getLocalizedMessage(), ex);
309 }
310 } else if (validationResult != null && !validationResult.getFindings().isEmpty()) {
311 LOGGER.info("Validation identified the following issues:");
312 LoggingValidationHandler.instance().handleResults(validationResult);
313 }
314
315 }
316 }
317 }