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