Skip to content

Commit 9babb01

Browse files
Set up initial query command for #241
Co-Authored-By: David Waltermire <[email protected]>
1 parent f3f3d49 commit 9babb01

File tree

4 files changed

+222
-5
lines changed

4 files changed

+222
-5
lines changed

metaschema-cli/pom.xml

+14
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@
4545
<groupId>com.github.spotbugs</groupId>
4646
<artifactId>spotbugs-annotations</artifactId>
4747
</dependency>
48+
49+
<dependency>
50+
<groupId>io.github.hakky54</groupId>
51+
<artifactId>logcaptor</artifactId>
52+
<version>2.9.2</version>
53+
<scope>test</scope>
54+
</dependency>
55+
56+
<dependency>
57+
<groupId>org.assertj</groupId>
58+
<artifactId>assertj-core</artifactId>
59+
<version>3.24.2</version>
60+
<scope>test</scope>
61+
</dependency>
4862
</dependencies>
4963

5064
<build>

metaschema-cli/src/main/java/gov/nist/secauto/metaschema/cli/CLI.java

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
package gov.nist.secauto.metaschema.cli;
2828

2929
import gov.nist.secauto.metaschema.cli.commands.GenerateSchemaCommand;
30+
import gov.nist.secauto.metaschema.cli.commands.QueryCommand;
3031
import gov.nist.secauto.metaschema.cli.commands.ValidateContentUsingModuleCommand;
3132
import gov.nist.secauto.metaschema.cli.commands.ValidateModuleCommand;
3233
import gov.nist.secauto.metaschema.cli.processor.CLIProcessor;
@@ -59,6 +60,7 @@ public static ExitStatus runCli(String... args) {
5960
processor.addCommandHandler(new ValidateModuleCommand());
6061
processor.addCommandHandler(new GenerateSchemaCommand());
6162
processor.addCommandHandler(new ValidateContentUsingModuleCommand());
63+
processor.addCommandHandler(new QueryCommand());
6264

6365
CommandService.getInstance().getCommands().stream().forEach(command -> {
6466
assert command != null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Portions of this software was developed by employees of the National Institute
3+
* of Standards and Technology (NIST), an agency of the Federal Government and is
4+
* being made available as a public service. Pursuant to title 17 United States
5+
* Code Section 105, works of NIST employees are not subject to copyright
6+
* protection in the United States. This software may be subject to foreign
7+
* copyright. Permission in the United States and in foreign countries, to the
8+
* extent that NIST may hold copyright, to use, copy, modify, create derivative
9+
* works, and distribute this software and its documentation without fee is hereby
10+
* granted on a non-exclusive basis, provided that this notice and disclaimer
11+
* of warranty appears in all copies.
12+
*
13+
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14+
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15+
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17+
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18+
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
19+
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20+
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21+
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22+
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23+
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24+
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25+
*/
26+
27+
package gov.nist.secauto.metaschema.cli.commands;
28+
29+
import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
30+
import gov.nist.secauto.metaschema.cli.processor.ExitCode;
31+
import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
32+
import gov.nist.secauto.metaschema.cli.processor.InvalidArgumentException;
33+
import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
34+
import gov.nist.secauto.metaschema.cli.processor.command.DefaultExtraArgument;
35+
import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
36+
import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
37+
import gov.nist.secauto.metaschema.core.metapath.ISequence;
38+
import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
39+
import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
40+
import gov.nist.secauto.metaschema.core.model.IModule;
41+
import gov.nist.secauto.metaschema.core.model.MetaschemaException;
42+
import gov.nist.secauto.metaschema.core.model.xml.ModuleLoader;
43+
import gov.nist.secauto.metaschema.core.util.CollectionUtil;
44+
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
45+
import gov.nist.secauto.metaschema.databind.IBindingContext;
46+
import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
47+
48+
import org.apache.commons.cli.CommandLine;
49+
import org.apache.commons.cli.Option;
50+
import org.apache.logging.log4j.LogManager;
51+
import org.apache.logging.log4j.Logger;
52+
53+
import java.io.IOException;
54+
import java.nio.file.Files;
55+
import java.nio.file.Path;
56+
import java.nio.file.Paths;
57+
import java.util.ArrayList;
58+
import java.util.Collection;
59+
import java.util.List;
60+
61+
import edu.umd.cs.findbugs.annotations.NonNull;
62+
63+
public class QueryCommand
64+
extends AbstractTerminalCommand {
65+
private static final Logger LOGGER = LogManager.getLogger(QueryCommand.class);
66+
@NonNull
67+
private static final String COMMAND = "query";
68+
@NonNull
69+
public static final Option CONTENT_OPTION = ObjectUtils.notNull(
70+
Option.builder("i")
71+
.hasArg()
72+
.argName("FILE")
73+
//.required()
74+
.desc("metaschema content instance resource")
75+
.build());
76+
@NonNull
77+
private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
78+
new DefaultExtraArgument("metapath", true)));
79+
80+
@Override
81+
public String getName() {
82+
return COMMAND;
83+
}
84+
85+
@Override
86+
public String getDescription() {
87+
return "Execute a Metapath query, optionally with a module and a related instance.";
88+
}
89+
90+
@Override
91+
public Collection<? extends Option> gatherOptions() {
92+
Collection<? extends Option> orig = super.gatherOptions();
93+
94+
List<Option> retval = new ArrayList<>(orig.size() + 1);
95+
retval.addAll(orig);
96+
retval.add(MetaschemaCommandSupport.METASCHEMA_OPTION);
97+
retval.add(CONTENT_OPTION);
98+
99+
return CollectionUtil.unmodifiableCollection(retval);
100+
}
101+
102+
@Override
103+
public List<ExtraArgument> getExtraArguments() {
104+
return EXTRA_ARGUMENTS;
105+
}
106+
107+
@Override
108+
public void validateOptions(CallingContext callingContext, CommandLine cmdLine) throws InvalidArgumentException {
109+
super.validateOptions(callingContext, cmdLine);
110+
111+
String metaschemaName = cmdLine.getOptionValue(MetaschemaCommandSupport.METASCHEMA_OPTION);
112+
Path metaschema = Paths.get(metaschemaName);
113+
if (!Files.exists(metaschema)) {
114+
throw new InvalidArgumentException("The provided module '" + metaschema + "' does not exist.");
115+
}
116+
if (!Files.isReadable(metaschema)) {
117+
throw new InvalidArgumentException("The provided module '" + metaschema + "' is not readable.");
118+
}
119+
120+
String contentInstanceName = cmdLine.getOptionValue(CONTENT_OPTION);
121+
Path contentInstance = Paths.get(contentInstanceName);
122+
if (!Files.exists(contentInstance)) {
123+
throw new InvalidArgumentException("The provided content instance '" + metaschema + "' does not exist.");
124+
}
125+
if (!Files.isReadable(contentInstance)) {
126+
throw new InvalidArgumentException("The provided content instance '" + metaschema + "' is not readable.");
127+
}
128+
}
129+
130+
@Override
131+
public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine commandLine) {
132+
return ICommandExecutor.using(callingContext, commandLine, this::executeCommand);
133+
}
134+
135+
@SuppressWarnings({
136+
"PMD.OnlyOneReturn", // readability
137+
"unused"
138+
})
139+
protected ExitStatus executeCommand(
140+
@NonNull CallingContext callingContext,
141+
@NonNull CommandLine cmdLine) {
142+
List<String> extraArgs = cmdLine.getArgList();
143+
144+
String metpath = extraArgs.get(0);
145+
146+
String metaschemaName = cmdLine.getOptionValue(MetaschemaCommandSupport.METASCHEMA_OPTION);
147+
Path metaschema = Paths.get(metaschemaName);
148+
149+
String contentInstanceName = cmdLine.getOptionValue(CONTENT_OPTION);
150+
Path contentInstance = Paths.get(contentInstanceName);
151+
152+
IBindingContext bindingContext = IBindingContext.instance();
153+
154+
try {
155+
// load the metaschema module
156+
ModuleLoader moduleLoader = new ModuleLoader();
157+
moduleLoader.allowEntityResolution();
158+
IModule module = moduleLoader.load(metaschema);
159+
160+
// generate class bindings and register them with the binding context
161+
Path classPath = Files.createTempDirectory("metaschema-classes-");
162+
bindingContext.registerModule(module, classPath);
163+
} catch (IOException | MetaschemaException ex) {
164+
return ExitCode.PROCESSING_ERROR.exit().withThrowable(ex); // NOPMD readability
165+
}
166+
167+
// load the content
168+
IBoundLoader contentLoader = bindingContext.newBoundLoader();
169+
170+
IDocumentNodeItem contentObject;
171+
try {
172+
contentObject = contentLoader.loadAsNodeItem(contentInstance);
173+
} catch (IOException ex) {
174+
return ExitCode.IO_ERROR.exit().withThrowable(ex);
175+
}
176+
177+
// executing the query against the content
178+
ISequence<?> sequence = MetapathExpression.compile(metpath).evaluate(contentObject);
179+
LOGGER.info(sequence);
180+
181+
return ExitCode.OK.exit();
182+
}
183+
}

metaschema-cli/src/test/java/gov/nist/secauto/metaschema/cli/CLITest.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626

2727
package gov.nist.secauto.metaschema.cli;
2828

29+
import static org.assertj.core.api.Assertions.assertThat;
2930
import static org.junit.jupiter.api.Assertions.assertAll;
3031
import static org.junit.jupiter.api.Assertions.assertEquals;
3132
import static org.junit.jupiter.api.Assertions.assertNull;
3233

34+
import gov.nist.secauto.metaschema.cli.commands.QueryCommand;
3335
import gov.nist.secauto.metaschema.cli.processor.ExitCode;
3436
import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
3537

@@ -42,6 +44,7 @@
4244
import java.util.stream.Stream;
4345

4446
import edu.umd.cs.findbugs.annotations.NonNull;
47+
import nl.altindag.log.LogCaptor;
4548

4649
/**
4750
* Unit test for simple CLI.
@@ -67,14 +70,17 @@ private static Stream<Arguments> providesValues() {
6770
List<Arguments> values = new ArrayList<>();
6871
values.add(Arguments.of(new String[] {}, ExitCode.INVALID_COMMAND, noExpectedExceptionClass));
6972
values.add(Arguments.of(new String[] { "-h" }, ExitCode.OK, noExpectedExceptionClass));
70-
values.add(Arguments.of(new String[] { "generate-schema", "--help" }, ExitCode.INVALID_COMMAND,
71-
noExpectedExceptionClass));
72-
values.add(Arguments.of(new String[] { "validate", "--help" }, ExitCode.OK, noExpectedExceptionClass));
73-
values.add(Arguments.of(new String[] { "validate-content", "--help" }, ExitCode.INVALID_COMMAND,
73+
values
74+
.add(Arguments.of(new String[] { "generate-schema", "--help", "--show-stack-trace" }, ExitCode.INVALID_COMMAND,
75+
noExpectedExceptionClass));
76+
values.add(Arguments.of(new String[] { "validate", "--help", "--show-stack-trace" }, ExitCode.OK,
7477
noExpectedExceptionClass));
78+
values
79+
.add(Arguments.of(new String[] { "validate-content", "--help", "--show-stack-trace" }, ExitCode.INVALID_COMMAND,
80+
noExpectedExceptionClass));
7581
values.add(Arguments.of(
7682
new String[] { "validate",
77-
"../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml" },
83+
"../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml", "--show-stack-trace" },
7884
ExitCode.OK, noExpectedExceptionClass));
7985
values.add(Arguments.of(new String[] { "generate-schema", "--overwrite", "--as", "JSON",
8086
"../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml",
@@ -103,4 +109,16 @@ void testAllCommands(@NonNull String[] args, @NonNull ExitCode expectedExitCode,
103109
evaluateResult(CLI.runCli(args), expectedExitCode, expectedThrownClass);
104110
}
105111
}
112+
113+
@Test
114+
void testQueryCommand() {
115+
LogCaptor logCaptor = LogCaptor.forClass(QueryCommand.class);
116+
String[] args
117+
= new String[] { "query", "-m", "../databind/src/test/resources/metaschema/fields_with_flags/metaschema.xml",
118+
"-i",
119+
"../databind/src/test/resources/metaschema/fields_with_flags/example.json", "3 + 4 + 5",
120+
"--show-stack-trace" };
121+
CLI.runCli(args);
122+
assertThat(logCaptor.getInfoLogs()).containsExactly("[12]");
123+
};
106124
}

0 commit comments

Comments
 (0)