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}