1
2
3
4
5
6 package gov.nist.secauto.oscal.tools.cli.core.commands;
7
8 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
9 import gov.nist.secauto.metaschema.cli.processor.ExitCode;
10 import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
11 import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException;
12 import gov.nist.secauto.metaschema.cli.processor.OptionUtils;
13 import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
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.processor.command.ICommandExecutor;
17 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
18
19 import org.apache.commons.cli.CommandLine;
20 import org.apache.commons.cli.Option;
21 import org.apache.logging.log4j.LogManager;
22 import org.apache.logging.log4j.Logger;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.Collection;
30 import java.util.List;
31
32 import javax.xml.transform.TransformerException;
33
34 import edu.umd.cs.findbugs.annotations.NonNull;
35 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36
37 public abstract class AbstractRenderCommand
38 extends AbstractTerminalCommand {
39 private static final Logger LOGGER = LogManager.getLogger(AbstractRenderCommand.class);
40
41 @NonNull
42 private static final String COMMAND = "render";
43 @NonNull
44 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
45 new DefaultExtraArgument("source file", true),
46 new DefaultExtraArgument("destination file", false)));
47
48 @NonNull
49 private static final Option OVERWRITE_OPTION = ObjectUtils.notNull(
50 Option.builder()
51 .longOpt("overwrite")
52 .desc("overwrite the destination if it exists")
53 .build());
54
55 @Override
56 public String getName() {
57 return COMMAND;
58 }
59
60 @SuppressWarnings("null")
61 @Override
62 public Collection<? extends Option> gatherOptions() {
63 return List.of(OVERWRITE_OPTION);
64 }
65
66 @Override
67 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "unmodifiable collection and immutable item")
68 public List<ExtraArgument> getExtraArguments() {
69 return EXTRA_ARGUMENTS;
70 }
71
72 @Override
73 public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException {
74 List<String> extraArgs = cmdLine.getArgList();
75 if (extraArgs.size() != 2) {
76 throw new InvalidArgumentException("Both a source and destination argument must be provided.");
77 }
78
79 File source = new File(extraArgs.get(0));
80 if (!source.exists()) {
81 throw new InvalidArgumentException("The provided source '" + source.getPath() + "' does not exist.");
82 }
83 if (!source.canRead()) {
84 throw new InvalidArgumentException("The provided source '" + source.getPath() + "' is not readable.");
85 }
86 }
87
88 @Override
89 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
90 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
91 }
92
93 @SuppressWarnings({
94 "PMD.OnlyOneReturn",
95 "unused"
96 })
97 protected ExitStatus executeCommand(
98 @NonNull CallingContext callingContext,
99 @NonNull CommandLine cmdLine) {
100 List<String> extraArgs = cmdLine.getArgList();
101 Path destination = resolvePathAgainstCWD(ObjectUtils.notNull(Paths.get(extraArgs.get(1))));
102
103 if (Files.exists(destination)) {
104 if (!cmdLine.hasOption(OVERWRITE_OPTION)) {
105 return ExitCode.INVALID_ARGUMENTS.exitMessage(
106 String.format("The provided destination '%s' already exists and the '%s' option was not provided.",
107 destination,
108 OptionUtils.toArgument(OVERWRITE_OPTION)));
109 }
110 if (!Files.isWritable(destination)) {
111 return ExitCode.IO_ERROR.exitMessage("The provided destination '" + destination + "' is not writable.");
112 }
113 }
114
115 Path input = resolvePathAgainstCWD(ObjectUtils.notNull(Paths.get(extraArgs.get(0))));
116 try {
117 performRender(input, destination);
118 } catch (IOException | TransformerException ex) {
119 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex);
120 }
121
122 if (LOGGER.isInfoEnabled()) {
123 LOGGER.info("Generated HTML file: " + destination.toString());
124 }
125 return ExitCode.OK.exit();
126 }
127
128 protected abstract void performRender(Path input, Path result) throws IOException, TransformerException;
129 }