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