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