001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.cli.commands; 007 008import org.apache.commons.cli.CommandLine; 009import org.apache.commons.cli.Option; 010import org.apache.logging.log4j.LogManager; 011import org.apache.logging.log4j.Logger; 012 013import java.io.File; 014import java.io.IOException; 015import java.io.OutputStream; 016import java.io.OutputStreamWriter; 017import java.net.URI; 018import java.nio.charset.StandardCharsets; 019import java.nio.file.Path; 020import java.util.Collection; 021import java.util.List; 022 023import dev.metaschema.cli.processor.CallingContext; 024import dev.metaschema.cli.processor.ExitCode; 025import dev.metaschema.cli.processor.command.AbstractTerminalCommand; 026import dev.metaschema.cli.processor.command.CommandExecutionException; 027import dev.metaschema.cli.processor.command.ExtraArgument; 028import dev.metaschema.cli.processor.command.ICommandExecutor; 029import dev.metaschema.core.configuration.DefaultConfiguration; 030import dev.metaschema.core.configuration.IMutableConfiguration; 031import dev.metaschema.core.model.IModule; 032import dev.metaschema.core.model.MetaschemaException; 033import dev.metaschema.core.util.AutoCloser; 034import dev.metaschema.core.util.ObjectUtils; 035import dev.metaschema.databind.IBindingContext; 036import dev.metaschema.schemagen.ISchemaGenerator; 037import dev.metaschema.schemagen.ISchemaGenerator.SchemaFormat; 038import dev.metaschema.schemagen.SchemaGenerationFeature; 039import edu.umd.cs.findbugs.annotations.NonNull; 040import edu.umd.cs.findbugs.annotations.Nullable; 041 042/** 043 * This command implementation supports generation of schemas in a variety of 044 * formats based on a provided Metaschema module. 045 */ 046public class GenerateSchemaCommand 047 extends AbstractTerminalCommand { 048 private static final Logger LOGGER = LogManager.getLogger(GenerateSchemaCommand.class); 049 050 @NonNull 051 private static final String COMMAND = "generate-schema"; 052 @NonNull 053 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of( 054 ExtraArgument.newInstance("metaschema-module-file-or-URL", true, URI.class), 055 ExtraArgument.newInstance("destination-schema-file", false, File.class))); 056 057 private static final Option INLINE_TYPES_OPTION = ObjectUtils.notNull( 058 Option.builder() 059 .longOpt("inline-types") 060 .desc("definitions declared inline will be generated as inline types") 061 .get()); 062 063 @Override 064 public String getName() { 065 return COMMAND; 066 } 067 068 @Override 069 public String getDescription() { 070 return "Generate a schema for the specified Module module"; 071 } 072 073 @SuppressWarnings("null") 074 @Override 075 public Collection<? extends Option> gatherOptions() { 076 return List.of( 077 MetaschemaCommands.OVERWRITE_OPTION, 078 MetaschemaCommands.AS_SCHEMA_FORMAT_OPTION, 079 INLINE_TYPES_OPTION); 080 } 081 082 @Override 083 public List<ExtraArgument> getExtraArguments() { 084 return EXTRA_ARGUMENTS; 085 } 086 087 @Override 088 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) { 089 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand); 090 } 091 092 /** 093 * Execute the schema generation operation. 094 * 095 * @param callingContext 096 * the context information for the execution 097 * @param cmdLine 098 * the parsed command line details 099 * @throws CommandExecutionException 100 * if an error occurred while determining the source format 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") // not owned 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}