001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.oscal.lib.metapath.function.library;
007
008import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
009import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
010import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
011import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
012import gov.nist.secauto.metaschema.core.metapath.function.InvalidArgumentFunctionException;
013import gov.nist.secauto.metaschema.core.metapath.function.UnidentifiedFunctionError;
014import gov.nist.secauto.metaschema.core.metapath.function.library.FnRoot;
015import gov.nist.secauto.metaschema.core.metapath.item.IItem;
016import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
017import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
018import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
019import gov.nist.secauto.metaschema.core.metapath.item.atomic.IUuidItem;
020import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
021import gov.nist.secauto.metaschema.core.util.ObjectUtils;
022import gov.nist.secauto.oscal.lib.OscalModelConstants;
023import gov.nist.secauto.oscal.lib.OscalUtils;
024import gov.nist.secauto.oscal.lib.model.BackMatter.Resource;
025import gov.nist.secauto.oscal.lib.model.BackMatter.Resource.Rlink;
026import gov.nist.secauto.oscal.lib.model.IOscalInstance;
027
028import java.net.URI;
029import java.util.List;
030
031import edu.umd.cs.findbugs.annotations.NonNull;
032import edu.umd.cs.findbugs.annotations.Nullable;
033
034/**
035 * Supports resolving a link to a backmatter resource.
036 */
037public final class ResolveReference {
038  private static final String NAME = "resolve-reference";
039
040  @NonNull
041  static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
042      .name(NAME)
043      .namespace(OscalModelConstants.NS_OSCAL)
044      .argument(IArgument.builder()
045          .name("uri")
046          .type(IAnyUriItem.type())
047          .zeroOrOne()
048          .build())
049      .focusDependent()
050      .contextDependent()
051      .deterministic()
052      .returnZeroOrOne()
053      .returnOne()
054      .functionHandler(ResolveReference::executeOneArg)
055      .build();
056
057  @NonNull
058  static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder()
059      .name(NAME)
060      .namespace(OscalModelConstants.NS_OSCAL)
061      .argument(IArgument.builder()
062          .name("uri")
063          .type(IAnyUriItem.type())
064          .zeroOrOne()
065          .build())
066      .argument(IArgument.builder()
067          .name("mediaType")
068          .type(IStringItem.type())
069          .zeroOrOne()
070          .build())
071      .focusIndependent()
072      .contextDependent()
073      .deterministic()
074      .returnType(IAnyUriItem.type())
075      .returnZeroOrOne()
076      .functionHandler(ResolveReference::executeTwoArg)
077      .build();
078
079  private ResolveReference() {
080    // disable construction
081  }
082
083  @SuppressWarnings({ "unused",
084      "PMD.OnlyOneReturn" // readability
085  })
086  @NonNull
087  private static ISequence<?> executeOneArg(
088      @NonNull IFunction function,
089      @NonNull List<ISequence<?>> arguments,
090      @NonNull DynamicContext dynamicContext,
091      IItem focus) {
092    IAnyUriItem uri = FunctionUtils.asTypeOrNull(arguments.get(0).getFirstItem(true));
093
094    if (uri == null) {
095      return ISequence.empty();
096    }
097
098    INodeItem node = checkForNodeItem(focus);
099    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}