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.DefaultExtraArgument;
13  import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
14  import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
15  import gov.nist.secauto.metaschema.core.configuration.DefaultConfiguration;
16  import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
17  import gov.nist.secauto.metaschema.core.model.IModule;
18  import gov.nist.secauto.metaschema.core.util.AutoCloser;
19  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
20  import gov.nist.secauto.metaschema.databind.IBindingContext;
21  import gov.nist.secauto.metaschema.schemagen.ISchemaGenerator;
22  import gov.nist.secauto.metaschema.schemagen.ISchemaGenerator.SchemaFormat;
23  import gov.nist.secauto.metaschema.schemagen.SchemaGenerationFeature;
24  
25  import org.apache.commons.cli.CommandLine;
26  import org.apache.commons.cli.Option;
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  
30  import java.io.IOException;
31  import java.io.OutputStream;
32  import java.io.OutputStreamWriter;
33  import java.nio.charset.StandardCharsets;
34  import java.nio.file.Path;
35  import java.util.Collection;
36  import java.util.List;
37  
38  import edu.umd.cs.findbugs.annotations.NonNull;
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        new DefaultExtraArgument("metaschema-module-file-or-URL", true),
53        new DefaultExtraArgument("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   @SuppressWarnings({
101       "PMD.OnlyOneReturn", // readability
102       "PMD.CyclomaticComplexity"
103   })
104   protected void executeCommand(
105       @NonNull CallingContext callingContext,
106       @NonNull CommandLine cmdLine) throws CommandExecutionException {
107     List<String> extraArgs = cmdLine.getArgList();
108 
109     Path destination = extraArgs.size() > 1
110         ? MetaschemaCommands.handleDestination(
111             ObjectUtils.requireNonNull(extraArgs.get(1)),
112             cmdLine)
113         : null;
114 
115     SchemaFormat asFormat = MetaschemaCommands.getSchemaFormat(cmdLine, MetaschemaCommands.AS_SCHEMA_FORMAT_OPTION);
116 
117     IMutableConfiguration<SchemaGenerationFeature<?>> configuration = new DefaultConfiguration<>();
118     if (cmdLine.hasOption(INLINE_TYPES_OPTION)) {
119       configuration.enableFeature(SchemaGenerationFeature.INLINE_DEFINITIONS);
120       if (SchemaFormat.JSON.equals(asFormat)) {
121         configuration.disableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
122       } else {
123         configuration.enableFeature(SchemaGenerationFeature.INLINE_CHOICE_DEFINITIONS);
124       }
125     }
126 
127     IBindingContext bindingContext = MetaschemaCommands.newBindingContextWithDynamicCompilation();
128     IModule module = MetaschemaCommands.loadModule(
129         ObjectUtils.requireNonNull(extraArgs.get(0)),
130         ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()),
131         bindingContext);
132     bindingContext.registerModule(module);
133 
134     try {
135       if (LOGGER.isInfoEnabled()) {
136         LOGGER.info("Generating {} schema for '{}'.", asFormat.name(), extraArgs.get(0));
137       }
138       if (destination == null) {
139         @SuppressWarnings({ "resource", "PMD.CloseResource" }) // not owned
140         OutputStream os = ObjectUtils.notNull(System.out);
141 
142         try (OutputStream out = AutoCloser.preventClose(os)) {
143           try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
144             ISchemaGenerator.generateSchema(module, writer, asFormat, configuration);
145           }
146         }
147       } else {
148         ISchemaGenerator.generateSchema(module, destination, asFormat, configuration);
149       }
150     } catch (IOException ex) {
151       throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex);
152     }
153     if (destination != null && LOGGER.isInfoEnabled()) {
154       LOGGER.info("Generated {} schema file: {}", asFormat.toString(), destination);
155     }
156   }
157 }