1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.metapath.function.library;
7   
8   import java.io.IOException;
9   import java.net.URI;
10  import java.util.List;
11  
12  import dev.metaschema.core.metapath.DynamicContext;
13  import dev.metaschema.core.metapath.MetapathConstants;
14  import dev.metaschema.core.metapath.function.DocumentFunctionException;
15  import dev.metaschema.core.metapath.function.FunctionUtils;
16  import dev.metaschema.core.metapath.function.IArgument;
17  import dev.metaschema.core.metapath.function.IFunction;
18  import dev.metaschema.core.metapath.function.library.FnDoc;
19  import dev.metaschema.core.metapath.function.library.FnResolveUri;
20  import dev.metaschema.core.metapath.item.IItem;
21  import dev.metaschema.core.metapath.item.ISequence;
22  import dev.metaschema.core.metapath.item.atomic.IAnyUriItem;
23  import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
24  import dev.metaschema.core.metapath.item.node.INodeItem;
25  import dev.metaschema.core.util.ObjectUtils;
26  import dev.metaschema.oscal.lib.OscalModelConstants;
27  import dev.metaschema.oscal.lib.model.Catalog;
28  import dev.metaschema.oscal.lib.profile.resolver.ProfileResolutionException;
29  import dev.metaschema.oscal.lib.profile.resolver.ProfileResolver;
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  
32  public final class ResolveProfile {
33  
34    @NonNull
35    static final IFunction SIGNATURE_NO_ARG = IFunction.builder()
36        .name("resolve-profile")
37        .namespace(OscalModelConstants.NS_OSCAL)
38        .returnType(INodeItem.type())
39        .focusDependent()
40        .contextDependent()
41        .deterministic()
42        .returnOne()
43        .functionHandler(ResolveProfile::executeNoArg)
44        .build();
45  
46    @NonNull
47    static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
48        .name("resolve-profile")
49        .namespace(OscalModelConstants.NS_OSCAL)
50        .argument(IArgument.builder()
51            .name("profile")
52            .type(INodeItem.type())
53            .zeroOrOne()
54            .build())
55        .focusIndependent()
56        .contextDependent()
57        .deterministic()
58        .returnType(INodeItem.type())
59        .returnOne()
60        .functionHandler(ResolveProfile::executeOneArg)
61        .build();
62  
63    @NonNull
64    static final IFunction SIGNATURE_NO_ARG_METAPATH = IFunction.builder()
65        .name("resolve-profile")
66        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
67        .returnType(INodeItem.type())
68        .focusDependent()
69        .contextDependent()
70        .deterministic()
71        .returnOne()
72        .functionHandler(ResolveProfile::executeNoArg)
73        .build();
74  
75    @NonNull
76    static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder()
77        .name("resolve-profile")
78        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
79        .argument(IArgument.builder()
80            .name("profile")
81            .type(INodeItem.type())
82            .zeroOrOne()
83            .build())
84        .focusIndependent()
85        .contextDependent()
86        .deterministic()
87        .returnType(INodeItem.type())
88        .returnOne()
89        .functionHandler(ResolveProfile::executeOneArg)
90        .build();
91  
92    private ResolveProfile() {
93      // disable construction
94    }
95  
96    @SuppressWarnings({ "unused",
97        "PMD.OnlyOneReturn" // readability
98    })
99    @NonNull
100   public static ISequence<?> executeNoArg(
101       @NonNull IFunction function,
102       @NonNull List<ISequence<?>> arguments,
103       @NonNull DynamicContext dynamicContext,
104       IItem focus) {
105 
106     if (focus == null) {
107       return ISequence.empty();
108     }
109     return ISequence.of(resolveProfile(FunctionUtils.asType(focus), dynamicContext));
110   }
111 
112   @SuppressWarnings({ "unused",
113       "PMD.OnlyOneReturn" // readability
114   })
115   @NonNull
116   public static ISequence<?> executeOneArg(
117       @NonNull IFunction function,
118       @NonNull List<ISequence<?>> arguments,
119       @NonNull DynamicContext dynamicContext,
120       IItem focus) {
121     ISequence<? extends IDocumentNodeItem> arg = FunctionUtils.asType(
122         ObjectUtils.notNull(arguments.get(0)));
123 
124     IItem item = arg.getFirstItem(true);
125     if (item == null) {
126       return ISequence.empty();
127     }
128 
129     return ISequence.of(resolveProfile(FunctionUtils.asType(item), dynamicContext));
130   }
131 
132   @NonNull
133   public static IDocumentNodeItem resolveProfile(
134       @NonNull IDocumentNodeItem document,
135       @NonNull DynamicContext dynamicContext) {
136 
137     // make this work with unresolved fragments
138     URI documentUri = document.getBaseUri();
139     String fragment = documentUri.getFragment();
140 
141     IDocumentNodeItem profile;
142     if (fragment == null) {
143       profile = document;
144     } else {
145       IAnyUriItem referenceUri = ResolveReference.resolveReference(IAnyUriItem.valueOf(documentUri), null, document);
146       IAnyUriItem resolvedUri = FnResolveUri.fnResolveUri(referenceUri, null, dynamicContext);
147       profile = FnDoc.fnDoc(resolvedUri, dynamicContext);
148     }
149 
150     Object profileObject = INodeItem.toValue(profile);
151 
152     IDocumentNodeItem retval;
153     if (profileObject instanceof Catalog) {
154       retval = profile;
155     } else {
156       // this is a profile
157       ProfileResolver resolver
158           = new ProfileResolver(dynamicContext, (uri, source) -> profile.getDocumentUri().resolve(uri));
159       try {
160         retval = resolver.resolve(profile);
161       } catch (IOException | ProfileResolutionException ex) {
162         throw new DocumentFunctionException(
163             DocumentFunctionException.ERROR_RETRIEVING_RESOURCE,
164             String.format("Unable to resolve profile '%s'. %s",
165                 profile.getBaseUri(),
166                 ex.getLocalizedMessage()),
167             ex);
168       }
169     }
170     return retval;
171   }
172 }