1
2
3
4
5
6 package gov.nist.secauto.oscal.tools.cli.core.commands;
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.command.AbstractTerminalCommand;
12 import gov.nist.secauto.metaschema.cli.processor.command.CommandExecutionException;
13 import gov.nist.secauto.metaschema.cli.processor.command.ExtraArgument;
14 import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
15 import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
16 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
17 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
18 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
19 import gov.nist.secauto.metaschema.core.util.UriUtils;
20 import gov.nist.secauto.metaschema.databind.IBindingContext;
21 import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
22 import gov.nist.secauto.metaschema.databind.io.Format;
23 import gov.nist.secauto.metaschema.databind.io.IBoundLoader;
24 import gov.nist.secauto.metaschema.databind.io.ISerializer;
25 import gov.nist.secauto.oscal.lib.OscalBindingContext;
26 import gov.nist.secauto.oscal.lib.model.Catalog;
27 import gov.nist.secauto.oscal.lib.model.Profile;
28 import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionException;
29 import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolver;
30
31 import org.apache.commons.cli.CommandLine;
32 import org.apache.commons.cli.Option;
33
34 import java.io.IOException;
35 import java.io.PrintStream;
36 import java.net.URI;
37 import java.net.URISyntaxException;
38 import java.nio.file.Path;
39 import java.util.Collection;
40 import java.util.List;
41
42 import edu.umd.cs.findbugs.annotations.NonNull;
43
44
45
46
47 public abstract class AbstractResolveCommand
48 extends AbstractTerminalCommand {
49 @NonNull
50 private static final List<ExtraArgument> EXTRA_ARGUMENTS = ObjectUtils.notNull(List.of(
51 ExtraArgument.newInstance("URI to resolve", true),
52 ExtraArgument.newInstance("destination file", false)));
53 private static final Option RELATIVE_TO = Option.builder()
54 .longOpt("relative-to")
55 .desc("Generate URI references relative to this resource")
56 .hasArg()
57 .build();
58
59 @NonNull
60 private static final List<Option> OPTIONS = ObjectUtils.notNull(
61 List.of(
62 MetaschemaCommands.AS_FORMAT_OPTION,
63 MetaschemaCommands.TO_OPTION,
64 MetaschemaCommands.OVERWRITE_OPTION,
65 RELATIVE_TO));
66
67 @Override
68 public String getDescription() {
69 return "Resolve the specified OSCAL Profile";
70 }
71
72 @Override
73 public Collection<? extends Option> gatherOptions() {
74 return OPTIONS;
75 }
76
77 @Override
78 public List<ExtraArgument> getExtraArguments() {
79 return EXTRA_ARGUMENTS;
80 }
81
82 @SuppressWarnings({
83 "PMD.CyclomaticComplexity", "PMD.CognitiveComplexity",
84 "PMD.PreserveStackTrace"
85 })
86
87 @Override
88 public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
89 return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
90 }
91
92
93
94
95
96
97
98
99
100
101
102
103 @SuppressWarnings({
104 "PMD.OnlyOneReturn",
105 "PMD.CyclomaticComplexity"
106 })
107 protected void executeCommand(
108 @NonNull CallingContext callingContext,
109 @NonNull CommandLine cmdLine) throws CommandExecutionException {
110 List<String> extraArgs = cmdLine.getArgList();
111
112 URI source = MetaschemaCommands.handleSource(
113 ObjectUtils.requireNonNull(extraArgs.get(0)),
114 ObjectUtils.notNull(getCurrentWorkingDirectory().toUri()));
115
116 IBindingContext bindingContext = OscalBindingContext.instance();
117 IBoundLoader loader = bindingContext.newBoundLoader();
118 loader.disableFeature(DeserializationFeature.DESERIALIZE_VALIDATE_CONSTRAINTS);
119
120
121 Format asFormat = MetaschemaCommands.determineSourceFormat(
122 cmdLine,
123 MetaschemaCommands.AS_FORMAT_OPTION,
124 loader,
125 source);
126
127 IDocumentNodeItem document;
128 try {
129 document = loader.loadAsNodeItem(asFormat, source);
130 } catch (IOException ex) {
131 throw new CommandExecutionException(
132 ExitCode.IO_ERROR,
133 String.format("Unable to load content '%s'. %s",
134 source,
135 ex.getMessage()),
136 ex);
137 }
138
139 Object object = document.getValue();
140 if (object == null) {
141 throw new CommandExecutionException(
142 ExitCode.INVALID_ARGUMENTS,
143 String.format("The source document '%s' contained no data.", source));
144 }
145
146 if (object instanceof Catalog) {
147
148 throw new CommandExecutionException(
149 ExitCode.OK,
150 String.format("The source '%s' is already a catalog.", source));
151 }
152
153 if (!(object instanceof Profile)) {
154
155 throw new CommandExecutionException(
156 ExitCode.INVALID_ARGUMENTS,
157 String.format("The source '%s' is not a profile.", source));
158 }
159
160 Path destination = null;
161 if (extraArgs.size() > 1) {
162 destination = MetaschemaCommands.handleDestination(ObjectUtils.requireNonNull(extraArgs.get(1)), cmdLine);
163 }
164
165 URI relativeTo;
166 if (cmdLine.hasOption(RELATIVE_TO)) {
167 relativeTo = getCurrentWorkingDirectory().toUri().resolve(cmdLine.getOptionValue(RELATIVE_TO));
168 } else {
169 relativeTo = document.getDocumentUri();
170 }
171
172
173 DynamicContext dynamicContext = new DynamicContext(document.getStaticContext());
174 dynamicContext.setDocumentLoader(loader);
175 ProfileResolver resolver = new ProfileResolver(
176 dynamicContext,
177 (uri, src) -> {
178 try {
179 return UriUtils.relativize(relativeTo, src.resolve(uri), true);
180 } catch (URISyntaxException ex) {
181 throw new IllegalArgumentException(ex);
182 }
183 });
184
185 IDocumentNodeItem resolvedProfile;
186 try {
187 resolvedProfile = resolver.resolve(document);
188 } catch (IOException | ProfileResolutionException ex) {
189 throw new CommandExecutionException(
190 ExitCode.PROCESSING_ERROR,
191 String.format("Cmd: Unable to resolve profile '%s'. %s", document.getDocumentUri(), ex.getMessage()),
192 ex);
193 }
194
195
196
197
198
199
200 Format toFormat = MetaschemaCommands.getFormat(cmdLine, MetaschemaCommands.TO_OPTION);
201 ISerializer<Catalog> serializer = bindingContext.newSerializer(toFormat, Catalog.class);
202 try {
203 if (destination == null) {
204 @SuppressWarnings({ "resource", "PMD.CloseResource" })
205 PrintStream stdOut = ObjectUtils.notNull(System.out);
206 serializer.serialize((Catalog) INodeItem.toValue(resolvedProfile), stdOut);
207 } else {
208 serializer.serialize((Catalog) INodeItem.toValue(resolvedProfile), destination);
209 }
210 } catch (IOException ex) {
211 throw new CommandExecutionException(ExitCode.IO_ERROR, ex);
212 }
213 }
214 }