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