1
2
3
4
5
6 package dev.metaschema.cli.commands.metapath;
7
8 import org.apache.commons.cli.CommandLine;
9 import org.apache.commons.cli.Option;
10 import org.apache.logging.log4j.LogManager;
11 import org.apache.logging.log4j.Logger;
12
13 import java.io.IOException;
14 import java.io.PrintWriter;
15 import java.io.StringWriter;
16 import java.io.Writer;
17 import java.net.URI;
18 import java.net.URISyntaxException;
19 import java.util.Collection;
20 import java.util.List;
21
22 import dev.metaschema.cli.commands.MetaschemaCommands;
23 import dev.metaschema.cli.processor.CallingContext;
24 import dev.metaschema.cli.processor.ExitCode;
25 import dev.metaschema.cli.processor.command.AbstractTerminalCommand;
26 import dev.metaschema.cli.processor.command.CommandExecutionException;
27 import dev.metaschema.cli.processor.command.ExtraArgument;
28 import dev.metaschema.cli.processor.command.ICommandExecutor;
29 import dev.metaschema.core.metapath.DynamicContext;
30 import dev.metaschema.core.metapath.IMetapathExpression;
31 import dev.metaschema.core.metapath.StaticContext;
32 import dev.metaschema.core.metapath.item.DefaultItemWriter;
33 import dev.metaschema.core.metapath.item.IItemWriter;
34 import dev.metaschema.core.metapath.item.ISequence;
35 import dev.metaschema.core.metapath.item.node.INodeItem;
36 import dev.metaschema.core.metapath.item.node.INodeItemFactory;
37 import dev.metaschema.core.model.IModule;
38 import dev.metaschema.core.model.MetaschemaException;
39 import dev.metaschema.core.util.CollectionUtil;
40 import dev.metaschema.core.util.ObjectUtils;
41 import dev.metaschema.databind.IBindingContext;
42 import dev.metaschema.databind.io.IBoundLoader;
43 import edu.umd.cs.findbugs.annotations.NonNull;
44 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 class EvaluateMetapathCommand
61 extends AbstractTerminalCommand {
62 private static final Logger LOGGER = LogManager.getLogger(EvaluateMetapathCommand.class);
63
64 @NonNull
65 private static final String COMMAND = "eval";
66 @NonNull
67 private static final Option EXPRESSION_OPTION = ObjectUtils.notNull(
68 Option.builder("e")
69 .longOpt("expression")
70 .required()
71 .hasArg()
72 .argName("EXPRESSION")
73 .desc("Metapath expression to execute")
74 .get());
75 @NonNull
76 private static final Option CONTENT_OPTION = ObjectUtils.notNull(
77 Option.builder("i")
78 .hasArg()
79 .argName("FILE_OR_URL")
80 .type(URI.class)
81 .desc("Metaschema content instance resource")
82 .get());
83
84 @Override
85 public String getName() {
86 return COMMAND;
87 }
88
89 @Override
90 public String getDescription() {
91 return "Execute a Metapath expression against a document";
92 }
93
94 @SuppressWarnings("null")
95 @Override
96 public Collection<? extends Option> gatherOptions() {
97 return List.of(
98 MetaschemaCommands.METASCHEMA_OPTIONAL_OPTION,
99 CONTENT_OPTION,
100 EXPRESSION_OPTION);
101 }
102
103 @Override
104 public List<ExtraArgument> getExtraArguments() {
105 return CollectionUtil.emptyList();
106 }
107
108 @Override
109 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
110 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
111 }
112
113 @SuppressFBWarnings(value = "REC_CATCH_EXCEPTION",
114 justification = "Catching generic exception for CLI error handling")
115 private void executeCommand(
116 @SuppressWarnings("unused") @NonNull CallingContext callingContext,
117 @NonNull CommandLine cmdLine) throws CommandExecutionException {
118
119 IModule module = null;
120 INodeItem item = null;
121 IBoundLoader loader = null;
122 if (cmdLine.hasOption(MetaschemaCommands.METASCHEMA_OPTIONAL_OPTION)) {
123 IBindingContext bindingContext = MetaschemaCommands.newBindingContextWithDynamicCompilation();
124
125
126 loader = bindingContext.newPermissiveBoundLoader();
127
128 try {
129 module = bindingContext.registerModule(MetaschemaCommands.loadModule(
130 cmdLine,
131 MetaschemaCommands.METASCHEMA_OPTIONAL_OPTION,
132 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()),
133 bindingContext));
134 } catch (MetaschemaException ex) {
135 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex);
136 }
137
138
139 if (cmdLine.hasOption(CONTENT_OPTION)) {
140
141 String contentLocation = ObjectUtils.requireNonNull(cmdLine.getOptionValue(CONTENT_OPTION));
142 URI contentResource;
143 try {
144 contentResource = MetaschemaCommands.getResourceUri(
145 contentLocation,
146 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()));
147 } catch (URISyntaxException ex) {
148 throw new CommandExecutionException(
149 ExitCode.INVALID_ARGUMENTS,
150 String.format("Unable to load content '%s'. %s",
151 contentLocation,
152 ex.getMessage()),
153 ex);
154 }
155
156 try {
157 item = loader.loadAsNodeItem(contentResource);
158 } catch (IOException ex) {
159 throw new CommandExecutionException(
160 ExitCode.INVALID_ARGUMENTS,
161 String.format("Unable to load content '%s'. %s",
162 contentLocation,
163 ex.getMessage()),
164 ex);
165 }
166 } else {
167
168 item = INodeItemFactory.instance().newModuleNodeItem(module);
169 }
170 } else if (cmdLine.hasOption(CONTENT_OPTION)) {
171
172 throw new CommandExecutionException(
173 ExitCode.INVALID_ARGUMENTS,
174 String.format("Must use '%s' to specify the Metaschema module.",
175 CONTENT_OPTION.getArgName()));
176 }
177
178
179 StaticContext.Builder builder = StaticContext.builder();
180 if (module != null) {
181 builder.defaultModelNamespace(module.getXmlNamespace());
182 }
183 StaticContext staticContext = builder.build();
184
185 String expression = cmdLine.getOptionValue(EXPRESSION_OPTION);
186 if (expression == null) {
187 throw new CommandExecutionException(
188 ExitCode.INVALID_ARGUMENTS,
189 String.format("Must use '%s' to specify the Metapath expression.", EXPRESSION_OPTION.getArgName()));
190 }
191
192
193 DynamicContext dynamicContext = new DynamicContext(staticContext);
194 if (loader != null) {
195 dynamicContext.setDocumentLoader(loader);
196 }
197
198 try {
199
200 ISequence<?> sequence = IMetapathExpression.compile(expression, staticContext)
201 .evaluate(item, dynamicContext);
202
203
204 try (Writer stringWriter = new StringWriter()) {
205 try (PrintWriter writer = new PrintWriter(stringWriter)) {
206 try (IItemWriter itemWriter = new DefaultItemWriter(writer)) {
207 itemWriter.writeSequence(sequence);
208 } catch (IOException ex) {
209 throw new CommandExecutionException(ExitCode.IO_ERROR, ex);
210 } catch (Exception ex) {
211 throw new CommandExecutionException(ExitCode.RUNTIME_ERROR, ex);
212 }
213 }
214
215
216 if (LOGGER.isInfoEnabled()) {
217 LOGGER.info(stringWriter.toString());
218 }
219 } catch (IOException ex) {
220 throw new CommandExecutionException(ExitCode.IO_ERROR, ex);
221 }
222 } catch (RuntimeException ex) {
223 throw new CommandExecutionException(ExitCode.PROCESSING_ERROR, ex);
224 }
225 }
226 }