1
2
3
4
5
6 package gov.nist.secauto.metaschema.cli.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.model.IModule;
18 import gov.nist.secauto.metaschema.core.model.MetaschemaException;
19 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
20 import gov.nist.secauto.metaschema.core.util.MermaidErDiagramGenerator;
21 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
22 import gov.nist.secauto.metaschema.core.util.UriUtils;
23
24 import org.apache.commons.cli.CommandLine;
25 import org.apache.commons.cli.Option;
26 import org.apache.logging.log4j.LogManager;
27 import org.apache.logging.log4j.Logger;
28
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.io.StringWriter;
32 import java.io.Writer;
33 import java.net.URI;
34 import java.net.URISyntaxException;
35 import java.nio.charset.StandardCharsets;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.nio.file.StandardOpenOption;
40 import java.util.Collection;
41 import java.util.List;
42
43 import edu.umd.cs.findbugs.annotations.NonNull;
44
45 public class GenerateDiagramCommand
46 extends AbstractTerminalCommand {
47 private static final Logger LOGGER = LogManager.getLogger(GenerateDiagramCommand.class);
48
49 @NonNull
50 private static final String COMMAND = "generate-diagram";
51 @NonNull
52 private static final List<ExtraArgument> EXTRA_ARGUMENTS;
53
54 static {
55 EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
56 new DefaultExtraArgument("metaschema-module-file-or-URL", true),
57 new DefaultExtraArgument("destination-diagram-file", false)));
58 }
59
60 @Override
61 public String getName() {
62 return COMMAND;
63 }
64
65 @Override
66 public String getDescription() {
67 return "Generate a diagram for the provided Metaschema module";
68 }
69
70 @SuppressWarnings("null")
71 @Override
72 public Collection<? extends Option> gatherOptions() {
73 return List.of(
74 MetaschemaCommands.OVERWRITE_OPTION);
75 }
76
77 @Override
78 public List<ExtraArgument> getExtraArguments() {
79 return EXTRA_ARGUMENTS;
80 }
81
82 @Override
83 public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException {
84 List<String> extraArgs = cmdLine.getArgList();
85 if (extraArgs.isEmpty() || extraArgs.size() > 2) {
86 throw new InvalidArgumentException("Illegal number of arguments.");
87 }
88 }
89
90 @Override
91 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
92 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
93 }
94
95
96
97
98
99
100
101
102
103
104 @SuppressWarnings({
105 "PMD.OnlyOneReturn",
106 })
107 protected ExitStatus executeCommand(
108 @NonNull CallingContext callingContext,
109 @NonNull CommandLine cmdLine) {
110
111 List<String> extraArgs = cmdLine.getArgList();
112
113 Path destination = null;
114 if (extraArgs.size() > 1) {
115 destination = Paths.get(extraArgs.get(1)).toAbsolutePath();
116 }
117
118 if (destination != null) {
119 if (Files.exists(destination)) {
120 if (!cmdLine.hasOption(MetaschemaCommands.OVERWRITE_OPTION)) {
121 return ExitCode.INVALID_ARGUMENTS.exitMessage(
122 String.format("The provided destination '%s' already exists and the '%s' option was not provided.",
123 destination,
124 OptionUtils.toArgument(MetaschemaCommands.OVERWRITE_OPTION)));
125 }
126 if (!Files.isWritable(destination)) {
127 return ExitCode.IO_ERROR.exitMessage(
128 "The provided destination '" + destination + "' is not writable.");
129 }
130 } else {
131 Path parent = destination.getParent();
132 if (parent != null) {
133 try {
134 Files.createDirectories(parent);
135 } catch (IOException ex) {
136 return ExitCode.INVALID_TARGET.exit().withThrowable(ex);
137 }
138 }
139 }
140 }
141
142 URI cwd = ObjectUtils.notNull(Paths.get("").toAbsolutePath().toUri());
143
144 IModule module;
145 try {
146 URI moduleUri = UriUtils.toUri(ObjectUtils.requireNonNull(extraArgs.get(0)), cwd);
147 module = MetaschemaCommands.handleModule(moduleUri, CollectionUtil.emptyList());
148 } catch (URISyntaxException ex) {
149 return ExitCode.INVALID_ARGUMENTS
150 .exitMessage(
151 String.format("Cannot load module as '%s' is not a valid file or URL.", ex.getInput()))
152 .withThrowable(ex);
153 } catch (IOException | MetaschemaException ex) {
154 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex);
155 }
156
157 try {
158 if (destination == null) {
159 Writer stringWriter = new StringWriter();
160 try (PrintWriter writer = new PrintWriter(stringWriter)) {
161 MermaidErDiagramGenerator.generate(module, writer);
162 }
163
164
165 if (LOGGER.isInfoEnabled()) {
166 LOGGER.info(stringWriter.toString());
167 }
168 } else {
169 try (Writer writer = Files.newBufferedWriter(
170 destination,
171 StandardCharsets.UTF_8,
172 StandardOpenOption.CREATE,
173 StandardOpenOption.WRITE,
174 StandardOpenOption.TRUNCATE_EXISTING)) {
175 try (PrintWriter printWriter = new PrintWriter(writer)) {
176 MermaidErDiagramGenerator.generate(module, printWriter);
177 }
178 }
179 }
180
181 return ExitCode.OK.exit();
182 } catch (Exception ex) {
183 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex);
184 }
185 }
186 }