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.MetapathConstants;
010import gov.nist.secauto.metaschema.core.metapath.MetapathException;
011import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
012import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
013import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
014import gov.nist.secauto.metaschema.core.metapath.function.library.FnDoc;
015import gov.nist.secauto.metaschema.core.metapath.function.library.FnResolveUri;
016import gov.nist.secauto.metaschema.core.metapath.item.IItem;
017import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
018import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
019import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
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.model.Catalog;
024import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolutionException;
025import gov.nist.secauto.oscal.lib.profile.resolver.ProfileResolver;
026
027import java.io.IOException;
028import java.net.URI;
029import java.util.List;
030
031import edu.umd.cs.findbugs.annotations.NonNull;
032
033public final class ResolveProfile {
034
035  @NonNull
036  static final IFunction SIGNATURE_NO_ARG = IFunction.builder()
037      .name("resolve-profile")
038      .namespace(OscalModelConstants.NS_OSCAL)
039      .returnType(INodeItem.type())
040      .focusDependent()
041      .contextDependent()
042      .deterministic()
043      .returnOne()
044      .functionHandler(ResolveProfile::executeNoArg)
045      .build();
046
047  @NonNull
048  static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
049      .name("resolve-profile")
050      .namespace(OscalModelConstants.NS_OSCAL)
051      .argument(IArgument.builder()
052          .name("profile")
053          .type(INodeItem.type())
054          .zeroOrOne()
055          .build())
056      .focusIndependent()
057      .contextDependent()
058      .deterministic()
059      .returnType(INodeItem.type())
060      .returnOne()
061      .functionHandler(ResolveProfile::executeOneArg)
062      .build();
063
064  @NonNull
065  static final IFunction SIGNATURE_NO_ARG_METAPATH = IFunction.builder()
066      .name("resolve-profile")
067      .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
068      .returnType(INodeItem.type())
069      .focusDependent()
070      .contextDependent()
071      .deterministic()
072      .returnOne()
073      .functionHandler(ResolveProfile::executeNoArg)
074      .build();
075
076  @NonNull
077  static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder()
078      .name("resolve-profile")
079      .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
080      .argument(IArgument.builder()
081          .name("profile")
082          .type(INodeItem.type())
083          .zeroOrOne()
084          .build())
085      .focusIndependent()
086      .contextDependent()
087      .deterministic()
088      .returnType(INodeItem.type())
089      .returnOne()
090      .functionHandler(ResolveProfile::executeOneArg)
091      .build();
092
093  private ResolveProfile() {
094    // disable construction
095  }
096
097  @SuppressWarnings({ "unused",
098      "PMD.OnlyOneReturn" // readability
099  })
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}