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