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.function.FunctionUtils;
10  import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
11  import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
12  import gov.nist.secauto.metaschema.core.metapath.function.InvalidArgumentFunctionException;
13  import gov.nist.secauto.metaschema.core.metapath.function.UnidentifiedFunctionError;
14  import gov.nist.secauto.metaschema.core.metapath.function.library.FnRoot;
15  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
16  import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
17  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
18  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
19  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUuidItem;
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.OscalUtils;
24  import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
25  import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink;
26  import gov.nist.secauto.oscal.lib.model.IOscalInstance;
27  
28  import java.net.URI;
29  import java.util.List;
30  
31  import edu.umd.cs.findbugs.annotations.NonNull;
32  import edu.umd.cs.findbugs.annotations.Nullable;
33  
34  /**
35   * Supports resolving a link to a backmatter resource.
36   */
37  public final class ResolveReference {
38    private static final String NAME = "resolve-reference";
39  
40    @NonNull
41    static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
42        .name(NAME)
43        .namespace(OscalModelConstants.NS_OSCAL)
44        .argument(IArgument.builder()
45            .name("uri")
46            .type(IAnyUriItem.type())
47            .zeroOrOne()
48            .build())
49        .focusDependent()
50        .contextDependent()
51        .deterministic()
52        .returnZeroOrOne()
53        .returnOne()
54        .functionHandler(ResolveReference::executeOneArg)
55        .build();
56  
57    @NonNull
58    static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder()
59        .name(NAME)
60        .namespace(OscalModelConstants.NS_OSCAL)
61        .argument(IArgument.builder()
62            .name("uri")
63            .type(IAnyUriItem.type())
64            .zeroOrOne()
65            .build())
66        .argument(IArgument.builder()
67            .name("mediaType")
68            .type(IStringItem.type())
69            .zeroOrOne()
70            .build())
71        .focusIndependent()
72        .contextDependent()
73        .deterministic()
74        .returnType(IAnyUriItem.type())
75        .returnZeroOrOne()
76        .functionHandler(ResolveReference::executeTwoArg)
77        .build();
78  
79    private ResolveReference() {
80      // disable construction
81    }
82  
83    @SuppressWarnings({ "unused",
84        "PMD.OnlyOneReturn" // readability
85    })
86    @NonNull
87    private static ISequence<?> executeOneArg(
88        @NonNull IFunction function,
89        @NonNull List<ISequence<?>> arguments,
90        @NonNull DynamicContext dynamicContext,
91        IItem focus) {
92      IAnyUriItem uri = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));
93  
94      if (uri == null) {
95        return ISequence.empty();
96      }
97  
98      INodeItem node = checkForNodeItem(focus);
99      return ISequence.of(resolveReference(uri, null, node));
100   }
101 
102   @SuppressWarnings({ "unused",
103       "PMD.OnlyOneReturn" // readability
104   })
105   @NonNull
106   private static ISequence<?> executeTwoArg(
107       @NonNull IFunction function,
108       @NonNull List<ISequence<?>> arguments,
109       @NonNull DynamicContext dynamicContext,
110       IItem focus) {
111     IAnyUriItem uri = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(0).getFirstItem(true)));
112 
113     if (uri == null) {
114       return ISequence.empty();
115     }
116 
117     // this function is focus dependent, so the focus must be non null
118     assert focus != null;
119 
120     IStringItem mediaType = FunctionUtils.asTypeOrNull(ObjectUtils.requireNonNull(arguments.get(1).getFirstItem(true)));
121     INodeItem node = checkForNodeItem(focus);
122     return ISequence.of(resolveReference(uri, mediaType, node));
123   }
124 
125   /**
126    * Ensure the provided item is a node item.
127    *
128    * @param item
129    *          the item to check
130    * @return the item as a node item
131    * @throws InvalidArgumentFunctionException
132    *           with code
133    *           {@link InvalidArgumentFunctionException#INVALID_ARGUMENT_TYPE} if
134    *           the item is not a node item
135    */
136   private static INodeItem checkForNodeItem(@NonNull IItem item) {
137     if (!(item instanceof INodeItem)) {
138       // this is expected to be a node
139       throw new InvalidArgumentFunctionException(
140           InvalidArgumentFunctionException.INVALID_ARGUMENT_TYPE,
141           String.format("Item of type '%s' is not a node item.", item.getClass().getName()));
142     }
143     return (INodeItem) item;
144   }
145 
146   @NonNull
147   public static IAnyUriItem resolveReference(
148       @NonNull IAnyUriItem reference,
149       @Nullable IStringItem mediaType,
150       @NonNull INodeItem focusedItem) {
151     INodeItem root = FnRoot.fnRoot(focusedItem);
152     IOscalInstance oscalInstance = (IOscalInstance) INodeItem.toValue(root);
153 
154     URI referenceUri = reference.asUri();
155     String fragment = referenceUri.getFragment();
156 
157     return fragment != null
158         && (referenceUri.getPath() == null || referenceUri.getPath().isEmpty())
159             ? IAnyUriItem.valueOf(resolveReference(
160                 fragment,
161                 mediaType == null ? null : mediaType.asString(),
162                 oscalInstance))
163             : reference;
164   }
165 
166   @NonNull
167   public static URI resolveReference(
168       @NonNull String reference,
169       @Nullable String mediaType,
170       @NonNull IOscalInstance oscalInstance) {
171     Resource resource = oscalInstance.getResourceByUuid(IUuidItem.valueOf(reference).asUuid());
172     if (resource == null) {
173       throw new UnidentifiedFunctionError(
174           String.format("A backmatter resource with the id '%s' does not exist.", reference));
175     }
176 
177     Rlink rLink = OscalUtils.findMatchingRLink(resource, mediaType);
178     if (rLink == null) {
179       throw new UnidentifiedFunctionError(
180           String.format("The backmatter resource '%s' does not have an rlink entry.", reference));
181     }
182     URI retval = rLink.getHref();
183     if (retval == null) {
184       throw new UnidentifiedFunctionError(
185           String.format("The backmatter resource '%s' has an rlink with a null href value.", reference));
186     }
187     return retval;
188   }
189 }