001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.cli.commands; 007 008import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext; 009import gov.nist.secauto.metaschema.cli.processor.ExitCode; 010import gov.nist.secauto.metaschema.cli.processor.command.AbstractCommandExecutor; 011import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand; 012import gov.nist.secauto.metaschema.cli.processor.command.CommandExecutionException; 013import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument; 014import gov.nist.secauto.metaschema.core.model.MetaschemaException; 015import gov.nist.secauto.metaschema.core.util.AutoCloser; 016import gov.nist.secauto.metaschema.core.util.ObjectUtils; 017import gov.nist.secauto.metaschema.databind.IBindingContext; 018import gov.nist.secauto.metaschema.databind.io.Format; 019import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 020 021import org.apache.commons.cli.CommandLine; 022import org.apache.commons.cli.Option; 023import org.apache.logging.log4j.LogManager; 024import org.apache.logging.log4j.Logger; 025 026import java.io.FileNotFoundException; 027import java.io.IOException; 028import java.io.OutputStreamWriter; 029import java.io.Writer; 030import java.net.URI; 031import java.nio.charset.StandardCharsets; 032import java.nio.file.Files; 033import java.nio.file.Path; 034import java.nio.file.StandardOpenOption; 035import java.util.Collection; 036import java.util.List; 037 038import edu.umd.cs.findbugs.annotations.NonNull; 039 040/** 041 * Used by implementing classes to provide a content conversion command. 042 */ 043public abstract class AbstractConvertSubcommand 044 extends AbstractTerminalCommand { 045 private static final Logger LOGGER = LogManager.getLogger(AbstractConvertSubcommand.class); 046 047 @NonNull 048 private static final String COMMAND = "convert"; 049 @NonNull 050 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of( 051 ExtraArgument.newInstance("source-file-or-URL", true), 052 ExtraArgument.newInstance("destination-file", false))); 053 054 @Override 055 public String getName() { 056 return COMMAND; 057 } 058 059 @Override 060 public Collection<? extends Option> gatherOptions() { 061 return ObjectUtils.notNull(List.of( 062 MetaschemaCommands.OVERWRITE_OPTION, 063 MetaschemaCommands.TO_OPTION)); 064 } 065 066 @Override 067 public List<ExtraArgument> getExtraArguments() { 068 return EXTRA_ARGUMENTS; 069 } 070 071 /** 072 * Used by implementing classes to provide for execution of a conversion 073 * command. 074 */ 075 protected abstract static class AbstractConversionCommandExecutor 076 extends AbstractCommandExecutor { 077 078 /** 079 * Construct a new command executor. 080 * 081 * @param callingContext 082 * the context of the command execution 083 * @param commandLine 084 * the parsed command line details 085 */ 086 protected AbstractConversionCommandExecutor( 087 @NonNull CallingContext callingContext, 088 @NonNull CommandLine commandLine) { 089 super(callingContext, commandLine); 090 } 091 092 /** 093 * Get the binding context to use for data processing. 094 * 095 * @return the context 096 * @throws CommandExecutionException 097 * if an error occurred getting the binding context 098 * @throws MetaschemaException 099 * if an error occurred while setting up the binding context, such as 100 * pre-loading any needed modules 101 */ 102 @NonNull 103 protected abstract IBindingContext getBindingContext() throws CommandExecutionException, MetaschemaException; 104 105 @SuppressWarnings({ 106 "PMD.OnlyOneReturn", // readability 107 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity" // reasonable 108 }) 109 @Override 110 public void execute() throws CommandExecutionException { 111 CommandLine cmdLine = getCommandLine(); 112 113 List<String> extraArgs = cmdLine.getArgList(); 114 115 Path destination = null; 116 if (extraArgs.size() > 1) { 117 destination = MetaschemaCommands.handleDestination(ObjectUtils.requireNonNull(extraArgs.get(1)), cmdLine); 118 } 119 120 @SuppressWarnings("synthetic-access") 121 URI source = MetaschemaCommands.handleSource( 122 ObjectUtils.requireNonNull(extraArgs.get(0)), 123 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri())); 124 125 Format toFormat = MetaschemaCommands.getFormat(cmdLine, MetaschemaCommands.TO_OPTION); 126 127 try { 128 IBindingContext bindingContext = getBindingContext(); 129 IBoundLoader loader = bindingContext.newBoundLoader(); 130 if (LOGGER.isInfoEnabled()) { 131 LOGGER.info("Converting '{}'.", source); 132 } 133 134 if (destination == null) { 135 // write to STDOUT 136 try (OutputStreamWriter writer 137 = new OutputStreamWriter(AutoCloser.preventClose(System.out), StandardCharsets.UTF_8)) { 138 handleConversion(source, toFormat, writer, loader); 139 } 140 } else { 141 try (Writer writer = Files.newBufferedWriter( 142 destination, 143 StandardCharsets.UTF_8, 144 StandardOpenOption.CREATE, 145 StandardOpenOption.WRITE, 146 StandardOpenOption.TRUNCATE_EXISTING)) { 147 assert writer != null; 148 handleConversion(source, toFormat, writer, loader); 149 } 150 } 151 } catch (IllegalArgumentException | MetaschemaException ex) { 152 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex); 153 } catch (IOException ex) { 154 throw new CommandExecutionException(ExitCode.IO_ERROR, ex); 155 } 156 if (destination != null && LOGGER.isInfoEnabled()) { 157 LOGGER.info("Generated {} file: {}", toFormat.toString(), destination); 158 } 159 } 160 161 /** 162 * Called to perform a content conversion. 163 * 164 * @param source 165 * the resource to convert 166 * @param toFormat 167 * the format to convert to 168 * @param writer 169 * the writer to use to write converted content 170 * @param loader 171 * the Metaschema loader to use to load the content to convert 172 * @throws FileNotFoundException 173 * if the requested resource was not found 174 * @throws IOException 175 * if there was an error reading or writing content 176 */ 177 protected abstract void handleConversion( 178 @NonNull URI source, 179 @NonNull Format toFormat, 180 @NonNull Writer writer, 181 @NonNull IBoundLoader loader) throws FileNotFoundException, IOException; 182 } 183}