1
2
3
4
5
6 package dev.metaschema.cli.commands;
7
8 import org.apache.commons.cli.CommandLine;
9 import org.apache.commons.cli.Option;
10 import org.apache.logging.log4j.LogManager;
11 import org.apache.logging.log4j.Logger;
12
13 import java.io.File;
14 import java.io.IOException;
15 import java.io.OutputStream;
16 import java.io.OutputStreamWriter;
17 import java.net.URI;
18 import java.nio.charset.StandardCharsets;
19 import java.nio.file.Path;
20 import java.util.Collection;
21 import java.util.List;
22
23 import dev.metaschema.cli.processor.CallingContext;
24 import dev.metaschema.cli.processor.ExitCode;
25 import dev.metaschema.cli.processor.command.AbstractTerminalCommand;
26 import dev.metaschema.cli.processor.command.CommandExecutionException;
27 import dev.metaschema.cli.processor.command.ExtraArgument;
28 import dev.metaschema.cli.processor.command.ICommandExecutor;
29 import dev.metaschema.core.configuration.DefaultConfiguration;
30 import dev.metaschema.core.configuration.IMutableConfiguration;
31 import dev.metaschema.core.model.IModule;
32 import dev.metaschema.core.model.MetaschemaException;
33 import dev.metaschema.core.util.AutoCloser;
34 import dev.metaschema.core.util.ObjectUtils;
35 import dev.metaschema.databind.IBindingContext;
36 import dev.metaschema.schemagen.ISchemaGenerator;
37 import dev.metaschema.schemagen.ISchemaGenerator.SchemaFormat;
38 import dev.metaschema.schemagen.SchemaGenerationFeature;
39 import edu.umd.cs.findbugs.annotations.NonNull;
40 import edu.umd.cs.findbugs.annotations.Nullable;
41
42
43
44
45
46 public class GenerateSchemaCommand
47 extends AbstractTerminalCommand {
48 private static final Logger LOGGER = LogManager.getLogger(GenerateSchemaCommand.class);
49
50 @NonNull
51 private static final String COMMAND = "generate-schema";
52 @NonNull
53 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
54 ExtraArgument.newInstance("metaschema-module-file-or-URL", true, URI.class),
55 ExtraArgument.newInstance("destination-schema-file", false, File.class)));
56
57 private static final Option INLINE_TYPES_OPTION = ObjectUtils.notNull(
58 Option.builder()
59 .longOpt("inline-types")
60 .desc("definitions declared inline will be generated as inline types")
61 .get());
62
63 @Override
64 public String getName() {
65 return COMMAND;
66 }
67
68 @Override
69 public String getDescription() {
70 return "Generate a schema for the specified Module module";
71 }
72
73 @SuppressWarnings("null")
74 @Override
75 public Collection<? extends Option> gatherOptions() {
76 return List.of(
77 MetaschemaCommands.OVERWRITE_OPTION,
78 MetaschemaCommands.AS_SCHEMA_FORMAT_OPTION,
79 INLINE_TYPES_OPTION);
80 }
81
82 @Override
83 public List<ExtraArgument> getExtraArguments() {
84 return EXTRA_ARGUMENTS;
85 }
86
87 @Override
88 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
89 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
90 }
91
92
93
94
95
96
97
98
99
100
101
102 protected void executeCommand(
103 @NonNull CallingContext callingContext,
104 @NonNull CommandLine cmdLine) throws CommandExecutionException {
105 List<String> extraArgs = cmdLine.getArgList();
106
107 Path destination = extraArgs.size() > 1
108 ? MetaschemaCommands.handleDestination(
109 ObjectUtils.requireNonNull(extraArgs.get(1)),
110 cmdLine)
111 : null;
112
113 SchemaFormat asFormat = MetaschemaCommands.getSchemaFormat(cmdLine, MetaschemaCommands.AS_SCHEMA_FORMAT_OPTION);
114
115 IMutableConfiguration<SchemaGenerationFeature<?>> configuration = createConfiguration(cmdLine, asFormat);
116 generateSchema(extraArgs, destination, asFormat, configuration);
117 }
118
119 @NonNull
120 private static IMutableConfiguration<SchemaGenerationFeature<?>> createConfiguration(
121 @NonNull CommandLine cmdLine,
122 @NonNull SchemaFormat asFormat) {
123 IMutableConfiguration<SchemaGenerationFeature<?>> configuration = new DefaultConfiguration<>();
124 if (cmdLine.hasOption(INLINE_TYPES_OPTION)) {
125 configuration.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
126 if (SchemaFormat.JSON.equals(asFormat)) {
127 configuration.disableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
128 } else {
129 configuration.enableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
130 }
131 }
132 return configuration;
133 }
134
135 private static void generateSchema(
136 @NonNull List<String> extraArgs,
137 @Nullable Path destination,
138 @NonNull SchemaFormat asFormat,
139 @NonNull IMutableConfiguration<SchemaGenerationFeature<?>> configuration) throws CommandExecutionException {
140 IBindingContext bindingContext = MetaschemaCommands.newBindingContextWithDynamicCompilation();
141 IModule module = MetaschemaCommands.loadModule(
142 ObjectUtils.requireNonNull(extraArgs.get(0)),
143 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()),
144 bindingContext);
145
146 try {
147 bindingContext.registerModule(module);
148 if (LOGGER.isInfoEnabled()) {
149 LOGGER.info("Generating {} schema for '{}'.", asFormat.name(), extraArgs.get(0));
150 }
151 if (destination == null) {
152 @SuppressWarnings("resource")
153 OutputStream os = ObjectUtils.notNull(System.out);
154
155 try (OutputStream out = AutoCloser.preventClose(os)) {
156 try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
157 ISchemaGenerator.generateSchema(module, writer, asFormat, configuration);
158 }
159 }
160 } else {
161 ISchemaGenerator.generateSchema(module, destination, asFormat, configuration);
162 }
163 } catch (IOException | MetaschemaException ex) {
164 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex);
165 }
166 if (destination != null && LOGGER.isInfoEnabled()) {
167 LOGGER.info("Generated {} schema file: {}", asFormat.toString(), destination);
168 }
169 }
170 }