001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.cli.commands.metapath; 007 008import gov.nist.secauto.metaschema.cli.commands.MetaschemaCommands; 009import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext; 010import gov.nist.secauto.metaschema.cli.processor.ExitCode; 011import gov.nist.secauto.metaschema.cli.processor.ExitStatus; 012import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException; 013import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand; 014import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument; 015import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor; 016import gov.nist.secauto.metaschema.core.metapath.DynamicContext; 017import gov.nist.secauto.metaschema.core.metapath.ISequence; 018import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; 019import gov.nist.secauto.metaschema.core.metapath.StaticContext; 020import gov.nist.secauto.metaschema.core.metapath.item.DefaultItemWriter; 021import gov.nist.secauto.metaschema.core.metapath.item.IItemWriter; 022import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem; 023import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory; 024import gov.nist.secauto.metaschema.core.model.IModule; 025import gov.nist.secauto.metaschema.core.model.MetaschemaException; 026import gov.nist.secauto.metaschema.core.util.CollectionUtil; 027import gov.nist.secauto.metaschema.core.util.ObjectUtils; 028import gov.nist.secauto.metaschema.core.util.UriUtils; 029import gov.nist.secauto.metaschema.databind.DefaultBindingContext; 030import gov.nist.secauto.metaschema.databind.IBindingContext; 031import gov.nist.secauto.metaschema.databind.io.IBoundLoader; 032 033import org.apache.commons.cli.CommandLine; 034import org.apache.commons.cli.Option; 035import org.apache.logging.log4j.LogManager; 036import org.apache.logging.log4j.Logger; 037 038import java.io.IOException; 039import java.io.PrintWriter; 040import java.io.StringWriter; 041import java.io.Writer; 042import java.net.URI; 043import java.net.URISyntaxException; 044import java.nio.file.Files; 045import java.nio.file.Path; 046import java.nio.file.Paths; 047import java.util.Collection; 048import java.util.List; 049 050import edu.umd.cs.findbugs.annotations.NonNull; 051 052public class EvaluateMetapathCommand 053 extends AbstractTerminalCommand { 054 private static final Logger LOGGER = LogManager.getLogger(EvaluateMetapathCommand.class); 055 056 @NonNull 057 private static final String COMMAND = "eval"; 058 @NonNull 059 private static final Option EXPRESSION_OPTION = ObjectUtils.notNull( 060 Option.builder("e") 061 .longOpt("expression") 062 .required() 063 .hasArg() 064 .argName("EXPRESSION") 065 .desc("Metapath expression to execute") 066 .build()); 067 @NonNull 068 public static final Option CONTENT_OPTION = ObjectUtils.notNull( 069 Option.builder("i") 070 .hasArg() 071 .argName("FILE_OR_URL") 072 .desc("Metaschema content instance resource") 073 .build()); 074 075 @NonNull 076 public static final Option METASCHEMA_OPTION = ObjectUtils.notNull( 077 Option.builder("m") 078 .hasArg() 079 .argName("FILE_OR_URL") 080 .desc("metaschema resource") 081 .build()); 082 083 @Override 084 public String getName() { 085 return COMMAND; 086 } 087 088 @Override 089 public String getDescription() { 090 return "Execute a Metapath expression against a document"; 091 } 092 093 @SuppressWarnings("null") 094 @Override 095 public Collection<? extends Option> gatherOptions() { 096 return List.of( 097 METASCHEMA_OPTION, 098 CONTENT_OPTION, 099 EXPRESSION_OPTION); 100 } 101 102 @Override 103 public List<ExtraArgument> getExtraArguments() { 104 return CollectionUtil.emptyList(); 105 } 106 107 @Override 108 public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException { 109 List<String> extraArgs = cmdLine.getArgList(); 110 if (!extraArgs.isEmpty()) { 111 throw new InvalidArgumentException("Illegal number of extra arguments."); 112 } 113 } 114 115 @Override 116 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) { 117 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand); 118 } 119 120 @SuppressWarnings({ 121 "PMD.OnlyOneReturn", // readability 122 }) 123 124 protected ExitStatus executeCommand( 125 @NonNull CallingContext callingContext, 126 @NonNull CommandLine cmdLine) { 127 URI cwd = ObjectUtils.notNull(Paths.get("").toAbsolutePath().toUri()); 128 129 IModule module; 130 INodeItem item; 131 if (cmdLine.hasOption(METASCHEMA_OPTION)) { 132 try { 133 String moduleName 134 = ObjectUtils.requireNonNull(cmdLine.getOptionValue(METASCHEMA_OPTION)); 135 URI moduleUri = UriUtils.toUri(moduleName, cwd); 136 module = MetaschemaCommands.handleModule(moduleUri, CollectionUtil.emptyList()); 137 } catch (URISyntaxException ex) { 138 return ExitCode.INVALID_ARGUMENTS 139 .exitMessage( 140 String.format("Cannot load module as '%s' is not a valid file or URL.", ex.getInput())) 141 .withThrowable(ex); 142 } catch (IOException | MetaschemaException ex) { 143 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex); 144 } 145 146 // determine if the query is evaluated against the module or the instance 147 if (cmdLine.hasOption(CONTENT_OPTION)) { 148 // load the content 149 IBindingContext bindingContext = new DefaultBindingContext(); 150 151 try { 152 Path compilePath = Files.createTempDirectory("validation-"); 153 compilePath.toFile().deleteOnExit(); 154 155 bindingContext.registerModule(module, compilePath); 156 } catch (IOException ex) { 157 return ExitCode.PROCESSING_ERROR 158 .exitMessage("Unable to get binding context. " + ex.getMessage()) 159 .withThrowable(ex); 160 } 161 162 IBoundLoader loader = bindingContext.newBoundLoader(); 163 164 URI contentResource; 165 try { 166 contentResource = MetaschemaCommands.handleResource(cmdLine.getOptionValue(CONTENT_OPTION), cwd); 167 } catch (IOException ex) { 168 return ExitCode.INVALID_ARGUMENTS 169 .exitMessage("Unable to resolve content location. " + ex.getMessage()) 170 .withThrowable(ex); 171 } 172 173 try { 174 item = loader.loadAsNodeItem(contentResource); 175 } catch (IOException ex) { 176 return ExitCode.INVALID_ARGUMENTS 177 .exitMessage("Unable to resolve content location. " + ex.getMessage()) 178 .withThrowable(ex); 179 } 180 } else { 181 item = INodeItemFactory.instance().newModuleNodeItem(module); 182 } 183 } else if (cmdLine.hasOption(CONTENT_OPTION)) { 184 return ExitCode.INVALID_ARGUMENTS.exitMessage( 185 String.format("Must use '%s' to specify the Metaschema module.", CONTENT_OPTION.getArgName())); 186 } else { 187 module = null; 188 item = null; 189 } 190 191 String expression = cmdLine.getOptionValue(EXPRESSION_OPTION); 192 193 StaticContext.Builder builder = StaticContext.builder(); 194 if (module != null) { 195 builder.defaultModelNamespace(module.getXmlNamespace()); 196 } 197 StaticContext staticContext = builder.build(); 198 199 try { 200 // Parse and compile the Metapath expression 201 MetapathExpression compiledMetapath = MetapathExpression.compile(expression, staticContext); 202 ISequence<?> sequence = compiledMetapath.evaluate(item, new DynamicContext(staticContext)); 203 204 Writer stringWriter = new StringWriter(); 205 try (PrintWriter writer = new PrintWriter(stringWriter)) { 206 IItemWriter itemWriter = new DefaultItemWriter(writer); 207 itemWriter.writeSequence(sequence); 208 } 209 210 // Print the result 211 if (LOGGER.isInfoEnabled()) { 212 LOGGER.info(stringWriter.toString()); 213 } 214 215 return ExitCode.OK.exit(); 216 } catch (Exception ex) { 217 return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex); 218 } 219 } 220}