1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.cli.commands;
7   
8   import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
9   import gov.nist.secauto.metaschema.cli.processor.ExitCode;
10  import gov.nist.secauto.metaschema.cli.processor.command.AbstractCommandExecutor;
11  import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
12  import gov.nist.secauto.metaschema.cli.processor.command.CommandExecutionException;
13  import gov.nist.secauto.metaschema.cli.processor.command.DefaultExtraArgument;
14  import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
15  import gov.nist.secauto.metaschema.core.util.AutoCloser;
16  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
17  import gov.nist.secauto.metaschema.databind.IBindingContext;
18  import gov.nist.secauto.metaschema.databind.io.Format;
19  import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
20  
21  import org.apache.commons.cli.CommandLine;
22  import org.apache.commons.cli.Option;
23  import org.apache.logging.log4j.LogManager;
24  import org.apache.logging.log4j.Logger;
25  
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.OutputStreamWriter;
29  import java.io.Writer;
30  import java.net.URI;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.nio.file.StandardOpenOption;
35  import java.util.Collection;
36  import java.util.List;
37  
38  import edu.umd.cs.findbugs.annotations.NonNull;
39  
40  /**
41   * Used by implementing classes to provide a content conversion command.
42   */
43  public abstract class AbstractConvertSubcommand
44      extends AbstractTerminalCommand {
45    private static final Logger LOGGER = LogManager.getLogger(AbstractConvertSubcommand.class);
46  
47    @NonNull
48    private static final String COMMAND = "convert";
49    @NonNull
50    private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
51        new DefaultExtraArgument("source-file-or-URL", true),
52        new DefaultExtraArgument("destination-file", false)));
53  
54    @Override
55    public String getName() {
56      return COMMAND;
57    }
58  
59    @Override
60    public Collection<? extends Option> gatherOptions() {
61      return ObjectUtils.notNull(List.of(
62          MetaschemaCommands.OVERWRITE_OPTION,
63          MetaschemaCommands.TO_OPTION));
64    }
65  
66    @Override
67    public List<ExtraArgument> getExtraArguments() {
68      return EXTRA_ARGUMENTS;
69    }
70  
71    /**
72     * Used by implementing classes to provide for execution of a conversion
73     * command.
74     */
75    protected abstract static class AbstractConversionCommandExecutor
76        extends AbstractCommandExecutor {
77  
78      /**
79       * Construct a new command executor.
80       *
81       * @param callingContext
82       *          the context of the command execution
83       * @param commandLine
84       *          the parsed command line details
85       */
86      protected AbstractConversionCommandExecutor(
87          @NonNull CallingContext callingContext,
88          @NonNull CommandLine commandLine) {
89        super(callingContext, commandLine);
90      }
91  
92      /**
93       * Get the binding context to use for data processing.
94       *
95       * @return the context
96       * @throws CommandExecutionException
97       *           if an error occurred getting the binding context
98       */
99      @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 }