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.DefaultExtraArgument; 014import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument; 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 new DefaultExtraArgument("source-file-or-URL", true), 052 new DefaultExtraArgument("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 */ 099 @NonNull 100 protected abstract IBindingContext getBindingContext() throws CommandExecutionException; 101 102 @SuppressWarnings({ 103 "PMD.OnlyOneReturn", // readability 104 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity" // reasonable 105 }) 106 @Override 107 public void execute() throws CommandExecutionException { 108 CommandLine cmdLine = getCommandLine(); 109 110 List<String> extraArgs = cmdLine.getArgList(); 111 112 Path destination = null; 113 if (extraArgs.size() > 1) { 114 destination = MetaschemaCommands.handleDestination(ObjectUtils.requireNonNull(extraArgs.get(1)), cmdLine); 115 } 116 117 URI source = MetaschemaCommands.handleSource( 118 ObjectUtils.requireNonNull(extraArgs.get(0)), 119 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri())); 120 121 Format toFormat = MetaschemaCommands.getFormat(cmdLine, MetaschemaCommands.TO_OPTION); 122 123 IBindingContext bindingContext = getBindingContext(); 124 125 try { 126 IBoundLoader loader = bindingContext.newBoundLoader(); 127 if (LOGGER.isInfoEnabled()) { 128 LOGGER.info("Converting '{}'.", source); 129 } 130 131 if (destination == null) { 132 // write to STDOUT 133 try (OutputStreamWriter writer 134 = new OutputStreamWriter(AutoCloser.preventClose(System.out), StandardCharsets.UTF_8)) { 135 handleConversion(source, toFormat, writer, loader); 136 } 137 } else { 138 try (Writer writer = Files.newBufferedWriter( 139 destination, 140 StandardCharsets.UTF_8, 141 StandardOpenOption.CREATE, 142 StandardOpenOption.WRITE, 143 StandardOpenOption.TRUNCATE_EXISTING)) { 144 assert writer != null; 145 handleConversion(source, toFormat, writer, loader); 146 } 147 } 148 } catch (IllegalArgumentException ex) { 149 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex); 150 } catch (IOException ex) { 151 throw new CommandExecutionException(ExitCode.IO_ERROR, ex); 152 } 153 if (destination != null && LOGGER.isInfoEnabled()) { 154 LOGGER.info("Generated {} file: {}", toFormat.toString(), destination); 155 } 156 } 157 158 /** 159 * Called to perform a content conversion. 160 * 161 * @param source 162 * the resource to convert 163 * @param toFormat 164 * the format to convert to 165 * @param writer 166 * the writer to use to write converted content 167 * @param loader 168 * the Metaschema loader to use to load the content to convert 169 * @throws FileNotFoundException 170 * if the requested resource was not found 171 * @throws IOException 172 * if there was an error reading or writing content 173 */ 174 protected abstract void handleConversion( 175 @NonNull URI source, 176 @NonNull Format toFormat, 177 @NonNull Writer writer, 178 @NonNull IBoundLoader loader) throws FileNotFoundException, IOException; 179 } 180}