1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.oscal.lib.metapath.function.library;
7   
8   import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9   import gov.nist.secauto.metaschema.core.metapath.ISequence;
10  import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
11  import gov.nist.secauto.metaschema.core.metapath.MetapathException;
12  import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
13  import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
14  import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
15  import gov.nist.secauto.metaschema.core.metapath.function.InvalidTypeFunctionException;
16  import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
17  import gov.nist.secauto.metaschema.core.metapath.item.IItem;
18  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
19  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
20  import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
21  import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
22  import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem;
23  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
24  import gov.nist.secauto.metaschema.core.model.IFlagInstance;
25  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
26  import gov.nist.secauto.oscal.lib.OscalModelConstants;
27  import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty;
28  
29  import java.net.URI;
30  import java.util.List;
31  
32  import javax.xml.namespace.QName;
33  
34  import edu.umd.cs.findbugs.annotations.NonNull;
35  
36  public final class HasOscalNamespace {
37    @NonNull
38    private static final QName NS_FLAG_QNAME = new QName("ns");
39    @NonNull
40    static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
41        .name("has-oscal-namespace")
42        .namespace(OscalModelConstants.NS_OSCAL)
43        .argument(IArgument.builder()
44            .name("namespace")
45            .type(IStringItem.class)
46            .oneOrMore()
47            .build())
48        .allowUnboundedArity(true)
49        .returnType(IBooleanItem.class)
50        .focusDependent()
51        .contextIndependent()
52        .deterministic()
53        .returnOne()
54        .functionHandler(HasOscalNamespace::executeOneArg)
55        .build();
56  
57    @NonNull
58    static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder()
59        .name("has-oscal-namespace")
60        .namespace(OscalModelConstants.NS_OSCAL)
61        .argument(IArgument.builder()
62            .name("propOrPart")
63            .type(IAssemblyNodeItem.class)
64            .one()
65            .build())
66        .argument(IArgument.builder()
67            .name("namespace")
68            .type(IStringItem.class)
69            .oneOrMore()
70            .build())
71        .allowUnboundedArity(true)
72        .focusIndependent()
73        .contextIndependent()
74        .deterministic()
75        .returnType(IBooleanItem.class)
76        .returnOne()
77        .functionHandler(HasOscalNamespace::executeTwoArg)
78        .build();
79  
80    @NonNull
81    static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder()
82        .name("has-oscal-namespace")
83        .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
84        .argument(IArgument.builder()
85            .name("namespace")
86            .type(IStringItem.class)
87            .oneOrMore()
88            .build())
89        .allowUnboundedArity(true)
90        .returnType(IBooleanItem.class)
91        .focusDependent()
92        .contextIndependent()
93        .deterministic()
94        .returnOne()
95        .functionHandler(HasOscalNamespace::executeOneArg)
96        .build();
97  
98    @NonNull
99    static final IFunction SIGNATURE_TWO_ARGS_METAPATH = IFunction.builder()
100       .name("has-oscal-namespace")
101       .namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
102       .argument(IArgument.builder()
103           .name("propOrPart")
104           .type(IAssemblyNodeItem.class)
105           .one()
106           .build())
107       .argument(IArgument.builder()
108           .name("namespace")
109           .type(IStringItem.class)
110           .oneOrMore()
111           .build())
112       .allowUnboundedArity(true)
113       .focusIndependent()
114       .contextIndependent()
115       .deterministic()
116       .returnType(IBooleanItem.class)
117       .returnOne()
118       .functionHandler(HasOscalNamespace::executeTwoArg)
119       .build();
120 
121   private HasOscalNamespace() {
122     // disable construction
123   }
124 
125   @SuppressWarnings({ "unused",
126       "PMD.OnlyOneReturn" // readability
127   })
128   @NonNull
129   public static ISequence<?> executeOneArg(
130       @NonNull IFunction function,
131       @NonNull List<ISequence<?>> arguments,
132       @NonNull DynamicContext dynamicContext,
133       IItem focus) {
134     assert arguments.size() == 1;
135     ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType(
136         ObjectUtils.notNull(arguments.get(0)));
137 
138     if (namespaceArgs.isEmpty()) {
139       return ISequence.empty();
140     }
141 
142     IAssemblyNodeItem node = FunctionUtils.requireType(IAssemblyNodeItem.class, focus);
143     return ISequence.of(hasNamespace(FunctionUtils.asType(node), namespaceArgs));
144   }
145 
146   @SuppressWarnings({ "unused",
147       "PMD.OnlyOneReturn" // readability
148   })
149   @NonNull
150   public static ISequence<?> executeTwoArg(
151       @NonNull IFunction function,
152       @NonNull List<ISequence<?>> arguments,
153       @NonNull DynamicContext dynamicContext,
154       IItem focus) {
155     assert arguments.size() == 2;
156 
157     ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType(
158         ObjectUtils.notNull(arguments.get(1)));
159     if (namespaceArgs.isEmpty()) {
160       return ISequence.empty();
161     }
162 
163     ISequence<? extends IAssemblyNodeItem> nodeSequence = FunctionUtils.asType(
164         ObjectUtils.notNull(arguments.get(0)));
165 
166     // always not null, since the first item is required
167     IAssemblyNodeItem node = FunctionUtils.asType(ObjectUtils.requireNonNull(nodeSequence.getFirstItem(true)));
168     return ISequence.of(hasNamespace(node, namespaceArgs));
169   }
170 
171   @SuppressWarnings("PMD.LinguisticNaming") // false positive
172   @NonNull
173   public static IBooleanItem hasNamespace(
174       @NonNull IAssemblyNodeItem propOrPart,
175       @NonNull ISequence<? extends IStringItem> namespaces) {
176     Object propOrPartObject = propOrPart.getValue();
177     if (propOrPartObject == null) {
178       throw new InvalidTypeFunctionException(InvalidTypeFunctionException.NODE_HAS_NO_TYPED_VALUE, propOrPart);
179     }
180 
181     URI nodeNamespace = null;
182     // get the "ns" flag value
183     IFlagNodeItem ns = propOrPart.getFlagByName(NS_FLAG_QNAME);
184     if (ns == null) {
185       // check if the node actually has a "ns" flag
186       IAssemblyDefinition definition = propOrPart.getDefinition();
187       IFlagInstance flag = definition.getFlagInstanceByName(NS_FLAG_QNAME);
188       if (flag == null) {
189         throw new MetapathException(
190             String.format(
191                 "Node at path '%s' bound to '%s' based on the assembly definition '%s' has no OSCAL namespace",
192                 propOrPart.getMetapath(),
193                 propOrPart.getClass().getName(),
194                 propOrPart.getDefinition().getName()));
195 
196       }
197 
198       Object defaultValue = flag.getDefinition().getDefaultValue();
199       if (defaultValue != null) {
200         nodeNamespace = IAnyUriItem.valueOf(ObjectUtils.notNull(defaultValue.toString())).asUri();
201       }
202     } else {
203       nodeNamespace = IAnyUriItem.cast(FnData.fnDataItem(ns)).asUri();
204     }
205 
206     String nodeNamespaceString = AbstractProperty.normalizeNamespace(nodeNamespace).toString();
207     return IBooleanItem.valueOf(namespaces.stream()
208         .map(node -> nodeNamespaceString.equals(node.asString()))
209         .anyMatch(bool -> bool));
210   }
211 }