EvaluateMetapathCommand.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.metaschema.cli.commands.metapath;
import gov.nist.secauto.metaschema.cli.commands.MetaschemaCommands;
import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
import gov.nist.secauto.metaschema.cli.processor.ExitCode;
import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException;
import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.ISequence;
import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
import gov.nist.secauto.metaschema.core.metapath.StaticContext;
import gov.nist.secauto.metaschema.core.metapath.item.DefaultItemWriter;
import gov.nist.secauto.metaschema.core.metapath.item.IItemWriter;
import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
import gov.nist.secauto.metaschema.core.model.IModule;
import gov.nist.secauto.metaschema.core.model.MetaschemaException;
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.core.util.UriUtils;
import gov.nist.secauto.metaschema.databind.DefaultBindingContext;
import gov.nist.secauto.metaschema.databind.IBindingContext;
import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.List;
import edu.umd.cs.findbugs.annotations.NonNull;
public class EvaluateMetapathCommand
extends AbstractTerminalCommand {
private static final Logger LOGGER = LogManager.getLogger(EvaluateMetapathCommand.class);
@NonNull
private static final String COMMAND = "eval";
@NonNull
private static final Option EXPRESSION_OPTION = ObjectUtils.notNull(
Option.builder("e")
.longOpt("expression")
.required()
.hasArg()
.argName("EXPRESSION")
.desc("Metapath expression to execute")
.build());
@NonNull
public static final Option CONTENT_OPTION = ObjectUtils.notNull(
Option.builder("i")
.hasArg()
.argName("FILE_OR_URL")
.desc("Metaschema content instance resource")
.build());
@NonNull
public static final Option METASCHEMA_OPTION = ObjectUtils.notNull(
Option.builder("m")
.hasArg()
.argName("FILE_OR_URL")
.desc("metaschema resource")
.build());
@Override
public String getName() {
return COMMAND;
}
@Override
public String getDescription() {
return "Execute a Metapath expression against a document";
}
@SuppressWarnings("null")
@Override
public Collection<? extends Option> gatherOptions() {
return List.of(
METASCHEMA_OPTION,
CONTENT_OPTION,
EXPRESSION_OPTION);
}
@Override
public List<ExtraArgument> getExtraArguments() {
return CollectionUtil.emptyList();
}
@Override
public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException {
List<String> extraArgs = cmdLine.getArgList();
if (!extraArgs.isEmpty()) {
throw new InvalidArgumentException("Illegal number of extra arguments.");
}
}
@Override
public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
}
@SuppressWarnings({
"PMD.OnlyOneReturn", // readability
})
protected ExitStatus executeCommand(
@NonNull CallingContext callingContext,
@NonNull CommandLine cmdLine) {
URI cwd = ObjectUtils.notNull(Paths.get("").toAbsolutePath().toUri());
IModule module;
INodeItem item;
if (cmdLine.hasOption(METASCHEMA_OPTION)) {
try {
String moduleName
= ObjectUtils.requireNonNull(cmdLine.getOptionValue(METASCHEMA_OPTION));
URI moduleUri = UriUtils.toUri(moduleName, cwd);
module = MetaschemaCommands.handleModule(moduleUri, CollectionUtil.emptyList());
} catch (URISyntaxException ex) {
return ExitCode.INVALID_ARGUMENTS
.exitMessage(
String.format("Cannot load module as '%s' is not a valid file or URL.", ex.getInput()))
.withThrowable(ex);
} catch (IOException | MetaschemaException ex) {
return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex);
}
// determine if the query is evaluated against the module or the instance
if (cmdLine.hasOption(CONTENT_OPTION)) {
// load the content
IBindingContext bindingContext = new DefaultBindingContext();
try {
Path compilePath = Files.createTempDirectory("validation-");
compilePath.toFile().deleteOnExit();
bindingContext.registerModule(module, compilePath);
} catch (IOException ex) {
return ExitCode.PROCESSING_ERROR
.exitMessage("Unable to get binding context. " + ex.getMessage())
.withThrowable(ex);
}
IBoundLoader loader = bindingContext.newBoundLoader();
URI contentResource;
try {
contentResource = MetaschemaCommands.handleResource(cmdLine.getOptionValue(CONTENT_OPTION), cwd);
} catch (IOException ex) {
return ExitCode.INVALID_ARGUMENTS
.exitMessage("Unable to resolve content location. " + ex.getMessage())
.withThrowable(ex);
}
try {
item = loader.loadAsNodeItem(contentResource);
} catch (IOException ex) {
return ExitCode.INVALID_ARGUMENTS
.exitMessage("Unable to resolve content location. " + ex.getMessage())
.withThrowable(ex);
}
} else {
item = INodeItemFactory.instance().newModuleNodeItem(module);
}
} else if (cmdLine.hasOption(CONTENT_OPTION)) {
return ExitCode.INVALID_ARGUMENTS.exitMessage(
String.format("Must use '%s' to specify the Metaschema module.", CONTENT_OPTION.getArgName()));
} else {
module = null;
item = null;
}
String expression = cmdLine.getOptionValue(EXPRESSION_OPTION);
StaticContext.Builder builder = StaticContext.builder();
if (module != null) {
builder.defaultModelNamespace(module.getXmlNamespace());
}
StaticContext staticContext = builder.build();
try {
// Parse and compile the Metapath expression
MetapathExpression compiledMetapath = MetapathExpression.compile(expression, staticContext);
ISequence<?> sequence = compiledMetapath.evaluate(item, new DynamicContext(staticContext));
Writer stringWriter = new StringWriter();
try (PrintWriter writer = new PrintWriter(stringWriter)) {
IItemWriter itemWriter = new DefaultItemWriter(writer);
itemWriter.writeSequence(sequence);
}
// Print the result
if (LOGGER.isInfoEnabled()) {
LOGGER.info(stringWriter.toString());
}
return ExitCode.OK.exit();
} catch (Exception ex) {
return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex);
}
}
}