HasOscalNamespace.java
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/
package gov.nist.secauto.oscal.lib.metapath.function.library;
import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.ISequence;
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
import gov.nist.secauto.metaschema.core.metapath.MetapathException;
import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
import gov.nist.secauto.metaschema.core.metapath.function.InvalidTypeFunctionException;
import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyUriItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBooleanItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem;
import gov.nist.secauto.metaschema.core.metapath.item.node.IFlagNodeItem;
import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
import gov.nist.secauto.metaschema.core.model.IFlagInstance;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.oscal.lib.OscalModelConstants;
import gov.nist.secauto.oscal.lib.model.metadata.AbstractProperty;
import java.net.URI;
import java.util.List;
import javax.xml.namespace.QName;
import edu.umd.cs.findbugs.annotations.NonNull;
public final class HasOscalNamespace {
@NonNull
private static final QName NS_FLAG_QNAME = new QName("ns");
@NonNull
static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
.name("has-oscal-namespace")
.namespace(OscalModelConstants.NS_OSCAL)
.argument(IArgument.builder()
.name("namespace")
.type(IStringItem.class)
.oneOrMore()
.build())
.allowUnboundedArity(true)
.returnType(IBooleanItem.class)
.focusDependent()
.contextIndependent()
.deterministic()
.returnOne()
.functionHandler(HasOscalNamespace::executeOneArg)
.build();
@NonNull
static final IFunction SIGNATURE_TWO_ARGS = IFunction.builder()
.name("has-oscal-namespace")
.namespace(OscalModelConstants.NS_OSCAL)
.argument(IArgument.builder()
.name("propOrPart")
.type(IAssemblyNodeItem.class)
.one()
.build())
.argument(IArgument.builder()
.name("namespace")
.type(IStringItem.class)
.oneOrMore()
.build())
.allowUnboundedArity(true)
.focusIndependent()
.contextIndependent()
.deterministic()
.returnType(IBooleanItem.class)
.returnOne()
.functionHandler(HasOscalNamespace::executeTwoArg)
.build();
@NonNull
static final IFunction SIGNATURE_ONE_ARG_METAPATH = IFunction.builder()
.name("has-oscal-namespace")
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.argument(IArgument.builder()
.name("namespace")
.type(IStringItem.class)
.oneOrMore()
.build())
.allowUnboundedArity(true)
.returnType(IBooleanItem.class)
.focusDependent()
.contextIndependent()
.deterministic()
.returnOne()
.functionHandler(HasOscalNamespace::executeOneArg)
.build();
@NonNull
static final IFunction SIGNATURE_TWO_ARGS_METAPATH = IFunction.builder()
.name("has-oscal-namespace")
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS)
.argument(IArgument.builder()
.name("propOrPart")
.type(IAssemblyNodeItem.class)
.one()
.build())
.argument(IArgument.builder()
.name("namespace")
.type(IStringItem.class)
.oneOrMore()
.build())
.allowUnboundedArity(true)
.focusIndependent()
.contextIndependent()
.deterministic()
.returnType(IBooleanItem.class)
.returnOne()
.functionHandler(HasOscalNamespace::executeTwoArg)
.build();
private HasOscalNamespace() {
// disable construction
}
@SuppressWarnings({ "unused",
"PMD.OnlyOneReturn" // readability
})
@NonNull
public static ISequence<?> executeOneArg(
@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
assert arguments.size() == 1;
ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType(
ObjectUtils.notNull(arguments.get(0)));
if (namespaceArgs.isEmpty()) {
return ISequence.empty();
}
IAssemblyNodeItem node = FunctionUtils.requireType(IAssemblyNodeItem.class, focus);
return ISequence.of(hasNamespace(FunctionUtils.asType(node), namespaceArgs));
}
@SuppressWarnings({ "unused",
"PMD.OnlyOneReturn" // readability
})
@NonNull
public static ISequence<?> executeTwoArg(
@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
assert arguments.size() == 2;
ISequence<? extends IStringItem> namespaceArgs = FunctionUtils.asType(
ObjectUtils.notNull(arguments.get(1)));
if (namespaceArgs.isEmpty()) {
return ISequence.empty();
}
ISequence<? extends IAssemblyNodeItem> nodeSequence = FunctionUtils.asType(
ObjectUtils.notNull(arguments.get(0)));
// always not null, since the first item is required
IAssemblyNodeItem node = FunctionUtils.asType(ObjectUtils.requireNonNull(nodeSequence.getFirstItem(true)));
return ISequence.of(hasNamespace(node, namespaceArgs));
}
@SuppressWarnings("PMD.LinguisticNaming") // false positive
@NonNull
public static IBooleanItem hasNamespace(
@NonNull IAssemblyNodeItem propOrPart,
@NonNull ISequence<? extends IStringItem> namespaces) {
Object propOrPartObject = propOrPart.getValue();
if (propOrPartObject == null) {
throw new InvalidTypeFunctionException(InvalidTypeFunctionException.NODE_HAS_NO_TYPED_VALUE, propOrPart);
}
URI nodeNamespace = null;
// get the "ns" flag value
IFlagNodeItem ns = propOrPart.getFlagByName(NS_FLAG_QNAME);
if (ns == null) {
// check if the node actually has a "ns" flag
IAssemblyDefinition definition = propOrPart.getDefinition();
IFlagInstance flag = definition.getFlagInstanceByName(NS_FLAG_QNAME);
if (flag == null) {
throw new MetapathException(
String.format(
"Node at path '%s' bound to '%s' based on the assembly definition '%s' has no OSCAL namespace",
propOrPart.getMetapath(),
propOrPart.getClass().getName(),
propOrPart.getDefinition().getName()));
}
Object defaultValue = flag.getDefinition().getDefaultValue();
if (defaultValue != null) {
nodeNamespace = IAnyUriItem.valueOf(ObjectUtils.notNull(defaultValue.toString())).asUri();
}
} else {
nodeNamespace = IAnyUriItem.cast(FnData.fnDataItem(ns)).asUri();
}
String nodeNamespaceString = AbstractProperty.normalizeNamespace(nodeNamespace).toString();
return IBooleanItem.valueOf(namespaces.stream()
.map(node -> nodeNamespaceString.equals(node.asString()))
.anyMatch(bool -> bool));
}
}