001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.oscal.tools.cli.core.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.ExitStatus; 011import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException; 012import gov.nist.secauto.metaschema.cli.processor.OptionUtils; 013import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand; 014import gov.nist.secauto.metaschema.cli.processor.command.DefaultExtraArgument; 015import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument; 016import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor; 017import gov.nist.secauto.metaschema.core.util.ObjectUtils; 018 019import org.apache.commons.cli.CommandLine; 020import org.apache.commons.cli.Option; 021import org.apache.logging.log4j.LogManager; 022import org.apache.logging.log4j.Logger; 023 024import java.io.File; 025import java.io.IOException; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.nio.file.Paths; 029import java.util.Collection; 030import java.util.List; 031 032import javax.xml.transform.TransformerException; 033 034import edu.umd.cs.findbugs.annotations.NonNull; 035import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 036 037public abstract class AbstractRenderCommand 038 extends AbstractTerminalCommand { 039 private static final Logger LOGGER = LogManager.getLogger(AbstractRenderCommand.class); 040 041 @NonNull 042 private static final String COMMAND = "render"; 043 @NonNull 044 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of( 045 new DefaultExtraArgument("source file", true), 046 new DefaultExtraArgument("destination file", false))); 047 048 @NonNull 049 private static final Option OVERWRITE_OPTION = ObjectUtils.notNull( 050 Option.builder() 051 .longOpt("overwrite") 052 .desc("overwrite the destination if it exists") 053 .build()); 054 055 @Override 056 public String getName() { 057 return COMMAND; 058 } 059 060 @SuppressWarnings("null") 061 @Override 062 public Collection<? extends Option> gatherOptions() { 063 return List.of(OVERWRITE_OPTION); 064 } 065 066 @Override 067 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "unmodifiable collection and immutable item") 068 public List<ExtraArgument> getExtraArguments() { 069 return EXTRA_ARGUMENTS; 070 } 071 072 @Override 073 public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException { 074 List<String> extraArgs = cmdLine.getArgList(); 075 if (extraArgs.size() != 2) { 076 throw new InvalidArgumentException("Both a source and destination argument must be provided."); 077 } 078 079 File source = new File(extraArgs.get(0)); 080 if (!source.exists()) { 081 throw new InvalidArgumentException("The provided source '" + source.getPath() + "' does not exist."); 082 } 083 if (!source.canRead()) { 084 throw new InvalidArgumentException("The provided source '" + source.getPath() + "' is not readable."); 085 } 086 } 087 088 @Override 089 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) { 090 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand); 091 } 092 093 @SuppressWarnings({ 094 "PMD.OnlyOneReturn", // readability 095 "unused" 096 }) 097 protected ExitStatus executeCommand( 098 @NonNull CallingContext callingContext, 099 @NonNull CommandLine cmdLine) { 100 List<String> extraArgs = cmdLine.getArgList(); 101 Path destination = resolvePathAgainstCWD(ObjectUtils.notNull(Paths.get(extraArgs.get(1)))); // .toAbsolutePath(); 102 103 if (Files.exists(destination)) { 104 if (!cmdLine.hasOption(OVERWRITE_OPTION)) { 105 return ExitCode.INVALID_ARGUMENTS.exitMessage( 106 String.format("The provided destination '%s' already exists and the '%s' option was not provided.", 107 destination, 108 OptionUtils.toArgument(OVERWRITE_OPTION))); 109 } 110 if (!Files.isWritable(destination)) { 111 return ExitCode.IO_ERROR.exitMessage("The provided destination '" + destination + "' is not writable."); 112 } 113 } 114 115 Path input = resolvePathAgainstCWD(ObjectUtils.notNull(Paths.get(extraArgs.get(0)))); 116 try { 117 performRender(input, destination); 118 } catch (IOException | TransformerException ex) { 119 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex); 120 } 121 122 if (LOGGER.isInfoEnabled()) { 123 LOGGER.info("Generated HTML file: " + destination.toString()); 124 } 125 return ExitCode.OK.exit(); 126 } 127 128 protected abstract void performRender(Path input, Path result) throws IOException, TransformerException; 129}