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}