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