1
2
3
4
5
6 package gov.nist.secauto.metaschema.cli.commands.metapath;
7
8 import gov.nist.secauto.metaschema.cli.commands.MetaschemaCommands;
9 import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
10 import gov.nist.secauto.metaschema.cli.processor.ExitCode;
11 import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
12 import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException;
13 import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
14 import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
15 import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
16 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
17 import gov.nist.secauto.metaschema.core.metapath.ISequence;
18 import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
19 import gov.nist.secauto.metaschema.core.metapath.StaticContext;
20 import gov.nist.secauto.metaschema.core.metapath.item.DefaultItemWriter;
21 import gov.nist.secauto.metaschema.core.metapath.item.IItemWriter;
22 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
23 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
24 import gov.nist.secauto.metaschema.core.model.IModule;
25 import gov.nist.secauto.metaschema.core.model.MetaschemaException;
26 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
27 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
28 import gov.nist.secauto.metaschema.core.util.UriUtils;
29 import gov.nist.secauto.metaschema.databind.DefaultBindingContext;
30 import gov.nist.secauto.metaschema.databind.IBindingContext;
31 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
32
33 import org.apache.commons.cli.CommandLine;
34 import org.apache.commons.cli.Option;
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37
38 import java.io.IOException;
39 import java.io.PrintWriter;
40 import java.io.StringWriter;
41 import java.io.Writer;
42 import java.net.URI;
43 import java.net.URISyntaxException;
44 import java.nio.file.Files;
45 import java.nio.file.Path;
46 import java.nio.file.Paths;
47 import java.util.Collection;
48 import java.util.List;
49
50 import edu.umd.cs.findbugs.annotations.NonNull;
51
52 public class EvaluateMetapathCommand
53 extends AbstractTerminalCommand {
54 private static final Logger LOGGER = LogManager.getLogger(EvaluateMetapathCommand.class);
55
56 @NonNull
57 private static final String COMMAND = "eval";
58 @NonNull
59 private static final Option EXPRESSION_OPTION = ObjectUtils.notNull(
60 Option.builder("e")
61 .longOpt("expression")
62 .required()
63 .hasArg()
64 .argName("EXPRESSION")
65 .desc("Metapath expression to execute")
66 .build());
67 @NonNull
68 public static final Option CONTENT_OPTION = ObjectUtils.notNull(
69 Option.builder("i")
70 .hasArg()
71 .argName("FILE_OR_URL")
72 .desc("Metaschema content instance resource")
73 .build());
74
75 @NonNull
76 public static final Option METASCHEMA_OPTION = ObjectUtils.notNull(
77 Option.builder("m")
78 .hasArg()
79 .argName("FILE_OR_URL")
80 .desc("metaschema resource")
81 .build());
82
83 @Override
84 public String getName() {
85 return COMMAND;
86 }
87
88 @Override
89 public String getDescription() {
90 return "Execute a Metapath expression against a document";
91 }
92
93 @SuppressWarnings("null")
94 @Override
95 public Collection<? extends Option> gatherOptions() {
96 return List.of(
97 METASCHEMA_OPTION,
98 CONTENT_OPTION,
99 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",
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
147 if (cmdLine.hasOption(CONTENT_OPTION)) {
148
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
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
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 }