001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.metapath.function.library;
028
029import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
030import gov.nist.secauto.metaschema.core.metapath.ISequence;
031import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
032import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
033import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
034import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
035import gov.nist.secauto.metaschema.core.metapath.function.InvalidArgumentFunctionException;
036import gov.nist.secauto.metaschema.core.metapath.function.UriFunctionException;
037import gov.nist.secauto.metaschema.core.metapath.item.IItem;
038import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
039import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
040import gov.nist.secauto.metaschema.core.util.ObjectUtils;
041
042import java.net.URI;
043import java.util.List;
044
045import edu.umd.cs.findbugs.annotations.NonNull;
046import edu.umd.cs.findbugs.annotations.Nullable;
047
048public final class FnResolveUri {
049  @NonNull
050  static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
051      .name("resolve-uri")
052      .namespace(MetapathConstants.NS_XPATH_FUNCTIONS)
053      .deterministic()
054      .contextDependent()
055      .focusIndependent()
056      .argument(IArgument.newBuilder()
057          .name("relative")
058          .type(IStringItem.class)
059          .zeroOrOne()
060          .build())
061      .returnType(IAnyUriItem.class)
062      .returnZeroOrOne()
063      .functionHandler(FnResolveUri::executeOneArg)
064      .build();
065
066  @NonNull
067  static final IFunction SIGNATURE_TWO_ARG = IFunction.builder()
068      .name("resolve-uri")
069      .namespace(MetapathConstants.NS_XPATH_FUNCTIONS)
070      .deterministic()
071      .contextIndependent()
072      .focusIndependent()
073      .argument(IArgument.newBuilder()
074          .name("relative")
075          .type(IStringItem.class)
076          .zeroOrOne()
077          .build())
078      .argument(IArgument.newBuilder()
079          .name("base")
080          .type(IStringItem.class)
081          .one()
082          .build())
083      .returnType(IAnyUriItem.class)
084      .returnZeroOrOne()
085      .functionHandler(FnResolveUri::executeTwoArg)
086      .build();
087
088  private FnResolveUri() {
089    // disable construction
090  }
091
092  @SuppressWarnings("unused")
093  @NonNull
094  private static ISequence<IAnyUriItem> executeOneArg(
095      @NonNull IFunction function,
096      @NonNull List<ISequence<?>> arguments,
097      @NonNull DynamicContext dynamicContext,
098      IItem focus) {
099
100    ISequence<? extends IStringItem> relativeSequence
101        = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0)));
102    if (relativeSequence.isEmpty()) {
103      return ISequence.empty(); // NOPMD - readability
104    }
105
106    IAnyUriItem baseUri = FnStaticBaseUri.fnStaticBaseUri(dynamicContext);
107    if (baseUri == null) {
108      throw new UriFunctionException(UriFunctionException.BASE_URI_NOT_DEFINED_IN_STATIC_CONTEXT,
109          "The base-uri is not defined in the static context");
110    }
111
112    IStringItem relativeString = FunctionUtils.getFirstItem(relativeSequence, true);
113
114    IAnyUriItem resolvedUri = fnResolveUri(relativeString, baseUri);
115    return resolvedUri == null ? ISequence.empty() : ISequence.of(resolvedUri);
116  }
117
118  /**
119   * Implements the two argument version of the XPath 3.1 function <a href=
120   * "https://www.w3.org/TR/xpath-functions-31/#func-resolve-uri">resolve-uri</a>.
121   *
122   * @param function
123   *          the function definition
124   * @param arguments
125   *          a list of sequence arguments with an expected size of 2
126   * @param dynamicContext
127   *          the evaluation context
128   * @param focus
129   *          the current focus item
130   * @return a sequence containing the resolved URI or and empty sequence if
131   *         either the base or relative URI is {@code null}
132   */
133  @SuppressWarnings("PMD.UnusedPrivateMethod") // used in lambda
134  @NonNull
135  private static ISequence<IAnyUriItem> executeTwoArg(
136      @NonNull IFunction function, // NOPMD - ok
137      @NonNull List<ISequence<?>> arguments,
138      @NonNull DynamicContext dynamicContext, // NOPMD - ok
139      IItem focus) { // NOPMD - ok
140
141    /* there will always be two arguments */
142    assert arguments.size() == 2;
143
144    ISequence<? extends IStringItem> relativeSequence = FunctionUtils.asType(
145        ObjectUtils.requireNonNull(arguments.get(0)));
146    if (relativeSequence.isEmpty()) {
147      return ISequence.empty(); // NOPMD - readability
148    }
149
150    ISequence<? extends IStringItem> baseSequence = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(1)));
151    IStringItem baseString = FunctionUtils.getFirstItem(baseSequence, true);
152
153    if (baseString == null) {
154      throw new InvalidArgumentFunctionException(
155          InvalidArgumentFunctionException.INVALID_ARGUMENT_TO_RESOLVE_URI,
156          "Invalid argument to fn:resolve-uri().");
157    }
158    IAnyUriItem baseUri = IAnyUriItem.cast(baseString);
159
160    IStringItem relativeString = FunctionUtils.getFirstItem(relativeSequence, true);
161
162    IAnyUriItem resolvedUri = fnResolveUri(relativeString, baseUri);
163    return resolvedUri == null ? ISequence.empty() : ISequence.of(resolvedUri);
164  }
165
166  /**
167   * Resolve the {@code relative} URI against the provided {@code base} URI.
168   *
169   * @param relative
170   *          the relative URI to resolve
171   * @param base
172   *          the base URI to resolve against
173   * @return the resolved URI or {@code null} if the {@code relative} URI in
174   *         {@code null}
175   */
176  @Nullable
177  public static IAnyUriItem fnResolveUri(@Nullable IStringItem relative, @NonNull IAnyUriItem base) {
178    IAnyUriItem relativeUri = relative == null ? null : IAnyUriItem.cast(relative);
179
180    return fnResolveUri(relativeUri, base);
181  }
182
183  /**
184   * Resolve the {@code relative} URI against the provided {@code base} URI.
185   *
186   * @param relative
187   *          the relative URI to resolve
188   * @param base
189   *          the base URI to resolve against
190   * @return the resolved URI or {@code null} if the {@code relative} URI in
191   *         {@code null}
192   */
193  @Nullable
194  public static IAnyUriItem fnResolveUri(@Nullable IAnyUriItem relative, @NonNull IAnyUriItem base) {
195    if (relative == null) {
196      return null; // NOPMD - readability
197    }
198
199    @SuppressWarnings("null")
200    @NonNull URI resolvedUri = base.getValue().resolve(relative.getValue());
201    return IAnyUriItem.valueOf(resolvedUri);
202  }
203}