+++ /dev/null
-/*
- * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.netconf.sal.rest.doc.impl;
-
-import static org.opendaylight.netconf.sal.rest.doc.impl.BaseYangOpenApiGenerator.MODULE_NAME_SUFFIX;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.COMPONENTS_PREFIX;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.CONFIG;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.NAME_KEY;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.POST_SUFFIX;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TOP;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_KEY;
-import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_SUFFIX;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.TextNode;
-import com.google.common.collect.Range;
-import com.google.common.collect.RangeSet;
-import com.mifmif.common.regex.Generex;
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import org.opendaylight.yangtools.yang.common.Decimal64;
-import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerLike;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
-import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
-import org.opendaylight.yangtools.yang.model.api.Module;
-import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
-import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
-import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
-import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
-import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
-import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
-import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
-import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
-import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
- */
-public class DefinitionGenerator {
-
- private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
-
- private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
- private static final String MAX_ITEMS = "maxItems";
- private static final String MIN_ITEMS = "minItems";
- private static final String MAX_LENGTH_KEY = "maxLength";
- private static final String MIN_LENGTH_KEY = "minLength";
- private static final String REQUIRED_KEY = "required";
- private static final String REF_KEY = "$ref";
- private static final String ITEMS_KEY = "items";
- private static final String TYPE_KEY = "type";
- private static final String PROPERTIES_KEY = "properties";
- private static final String DESCRIPTION_KEY = "description";
- private static final String ARRAY_TYPE = "array";
- private static final String ENUM_KEY = "enum";
- private static final String TITLE_KEY = "title";
- private static final String DEFAULT_KEY = "default";
- private static final String FORMAT_KEY = "format";
- private static final String NAMESPACE_KEY = "namespace";
- public static final String INPUT = "input";
- public static final String INPUT_SUFFIX = "_input";
- public static final String OUTPUT = "output";
- public static final String OUTPUT_SUFFIX = "_output";
- private static final String STRING_TYPE = "string";
- private static final String OBJECT_TYPE = "object";
- private static final String NUMBER_TYPE = "number";
- private static final String INTEGER_TYPE = "integer";
- private static final String INT32_FORMAT = "int32";
- private static final String INT64_FORMAT = "int64";
- private static final String BOOLEAN_TYPE = "boolean";
- // Special characters used in automaton inside Generex.
- // See https://www.brics.dk/automaton/doc/dk/brics/automaton/RegExp.html
- private static final Pattern AUTOMATON_SPECIAL_CHARACTERS = Pattern.compile("[@&\"<>#~]");
-
- private Module topLevelModule;
-
- public DefinitionGenerator() {
- }
-
- /**
- * Creates Json definitions from provided module according to swagger spec.
- *
- * @param module - Yang module to be converted
- * @param schemaContext - SchemaContext of all Yang files used by Api Doc
- * @param definitionNames - Store for definition names
- * @return ObjectNode containing data used for creating examples and definitions in Api Doc
- * @throws IOException if I/O operation fails
- */
-
-
- public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
- final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isForSingleModule)
- throws IOException {
- topLevelModule = module;
-
- processIdentities(module, definitions, definitionNames, schemaContext);
- processContainersAndLists(module, definitions, definitionNames, schemaContext);
- processRPCs(module, definitions, definitionNames, schemaContext);
-
- if (isForSingleModule) {
- processModule(module, definitions, definitionNames, schemaContext);
- }
-
- return definitions;
- }
-
- public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
- final DefinitionNames definitionNames, final boolean isForSingleModule)
- throws IOException {
- final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
- if (isForSingleModule) {
- definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
- }
- return convertToJsonSchema(module, schemaContext, definitions, definitionNames, isForSingleModule);
- }
-
- private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
- final EffectiveModelContext schemaContext) {
- final ObjectNode definition = JsonNodeFactory.instance.objectNode();
- final ObjectNode properties = JsonNodeFactory.instance.objectNode();
- final ArrayNode required = JsonNodeFactory.instance.arrayNode();
- final String moduleName = module.getName();
- final String definitionName = moduleName + MODULE_NAME_SUFFIX;
- final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
- for (final DataSchemaNode node : module.getChildNodes()) {
- stack.enterSchemaTree(node.getQName());
- final String localName = node.getQName().getLocalName();
- if (node.isConfiguration()) {
- if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
- for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
- final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
-
- final String ref = COMPONENTS_PREFIX
- + moduleName + CONFIG
- + "_" + localName
- + definitionNames.getDiscriminator(node);
-
- if (node instanceof ListSchemaNode) {
- childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
- final ObjectNode items = JsonNodeFactory.instance.objectNode();
- items.put(REF_KEY, ref);
- childNodeProperties.set(ITEMS_KEY, items);
- childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
- childNodeProperties.put(TITLE_KEY, localName + CONFIG);
- } else {
- /*
- Description can't be added, because nothing allowed alongside $ref.
- allOf is not an option, because ServiceNow can't parse it.
- */
- childNodeProperties.put(REF_KEY, ref);
- }
- //add module name prefix to property name, when ServiceNow can process colons
- properties.set(localName, childNodeProperties);
- }
- } else if (node instanceof LeafSchemaNode) {
- /*
- Add module name prefix to property name, when ServiceNow can process colons(second parameter
- of processLeafNode).
- */
- processLeafNode((LeafSchemaNode) node, localName, properties, required, stack,
- definitions, definitionNames);
- }
- }
- stack.exit();
- }
- definition.put(TITLE_KEY, definitionName);
- definition.put(TYPE_KEY, OBJECT_TYPE);
- definition.set(PROPERTIES_KEY, properties);
- definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
- setRequiredIfNotEmpty(definition, required);
-
- definitions.set(definitionName, definition);
- }
-
- private void processContainersAndLists(final Module module, final ObjectNode definitions,
- final DefinitionNames definitionNames, final EffectiveModelContext schemaContext) throws IOException {
- final String moduleName = module.getName();
- final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
- for (final DataSchemaNode childNode : module.getChildNodes()) {
- stack.enterSchemaTree(childNode.getQName());
- // For every container and list in the module
- if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
- if (childNode.isConfiguration()) {
- processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
- true, stack);
- }
- processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
- false, stack);
- processActionNodeContainer(childNode, moduleName, definitions, definitionNames, stack);
- }
- stack.exit();
- }
- }
-
- private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
- final ObjectNode definitions, final DefinitionNames definitionNames, final SchemaInferenceStack stack)
- throws IOException {
- for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
- stack.enterSchemaTree(actionDef.getQName());
- processOperations(actionDef, moduleName, definitions, definitionNames, stack);
- stack.exit();
- }
- }
-
- private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
- final EffectiveModelContext schemaContext) throws IOException {
- final String moduleName = module.getName();
- final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
- for (final RpcDefinition rpcDefinition : module.getRpcs()) {
- stack.enterSchemaTree(rpcDefinition.getQName());
- processOperations(rpcDefinition, moduleName, definitions, definitionNames, stack);
- stack.exit();
- }
- }
-
- private void processOperations(final OperationDefinition operationDef, final String parentName,
- final ObjectNode definitions, final DefinitionNames definitionNames, final SchemaInferenceStack stack)
- throws IOException {
- final String operationName = operationDef.getQName().getLocalName();
- processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
- definitionNames, stack);
- processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
- definitionNames, stack);
- }
-
- private void processOperationInputOutput(final ContainerLike container, final String operationName,
- final String parentName, final boolean isInput, final ObjectNode definitions,
- final DefinitionNames definitionNames, final SchemaInferenceStack stack)
- throws IOException {
- stack.enterSchemaTree(container.getQName());
- if (!container.getChildNodes().isEmpty()) {
- final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
- final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
- processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
- false, stack);
-
- childSchema.put(TYPE_KEY, OBJECT_TYPE);
- final ObjectNode xml = JsonNodeFactory.instance.objectNode();
- xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
- childSchema.set(XML_KEY, xml);
- childSchema.put(TITLE_KEY, filename);
- final String discriminator =
- definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
- definitions.set(filename + discriminator, childSchema);
-
- processTopData(filename, discriminator, definitions, container);
- }
- stack.exit();
- }
-
- private static ObjectNode processTopData(final String filename, final String discriminator,
- final ObjectNode definitions, final SchemaNode schemaNode) {
- final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
- final String name = filename + discriminator;
- final String ref = COMPONENTS_PREFIX + name;
- final String topName = filename + TOP;
-
- if (schemaNode instanceof ListSchemaNode) {
- dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
- final ObjectNode items = JsonNodeFactory.instance.objectNode();
- items.put(REF_KEY, ref);
- dataNodeProperties.set(ITEMS_KEY, items);
- dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
- } else {
- /*
- Description can't be added, because nothing allowed alongside $ref.
- allOf is not an option, because ServiceNow can't parse it.
- */
- dataNodeProperties.put(REF_KEY, ref);
- }
-
- final ObjectNode properties = JsonNodeFactory.instance.objectNode();
- /*
- Add module name prefix to property name, when needed, when ServiceNow can process colons,
- use RestDocGenUtil#resolveNodesName for creating property name
- */
- properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
- final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
- finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
- finalChildSchema.set(PROPERTIES_KEY, properties);
- finalChildSchema.put(TITLE_KEY, topName);
-
-
- definitions.set(topName + discriminator, finalChildSchema);
-
- return dataNodeProperties;
- }
-
- /**
- * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
- * @param module The module from which the identity stmt will be processed
- * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
- * @param definitionNames Store for definition names
- */
- private static void processIdentities(final Module module, final ObjectNode definitions,
- final DefinitionNames definitionNames, final EffectiveModelContext context) {
- final String moduleName = module.getName();
- final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
- LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
-
- for (final IdentitySchemaNode idNode : idNodes) {
- final ObjectNode identityObj = buildIdentityObject(idNode, context);
- final String idName = idNode.getQName().getLocalName();
- final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
- final String name = idName + discriminator;
- definitions.set(name, identityObj);
- }
- }
-
- private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
- final ArrayNode enumPayload, final EffectiveModelContext context) {
- for (final IdentitySchemaNode derivedId : derivedIds) {
- enumPayload.add(derivedId.getQName().getLocalName());
- populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
- }
- }
-
- private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
- final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
- final SchemaInferenceStack stack) throws IOException {
- if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
- final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
- final SchemaNode schemaNode = (SchemaNode) dataNode;
- final String localName = schemaNode.getQName().getLocalName();
- final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
- final String nameAsParent = parentName + "_" + localName;
- final ObjectNode properties =
- processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
- definitionNames, isConfig, stack);
-
- final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
- final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
- final String postXmlNodeName = postNodeName + XML_SUFFIX;
- final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
-
- final String description = schemaNode.getDescription().orElse("");
- final String discriminator;
-
- if (!definitionNames.isListedNode(schemaNode)) {
- final List<String> names = List.of(parentNameConfigLocalName,
- parentNameConfigLocalName + TOP,
- nameAsParent,
- nameAsParent + TOP,
- postNodeName,
- postXmlNodeName);
- discriminator = definitionNames.pickDiscriminator(schemaNode, names);
- } else {
- discriminator = definitionNames.getDiscriminator(schemaNode);
- }
-
- if (isConfig) {
- final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
- String truePostNodeName = postNodeName + discriminator;
- definitions.set(truePostNodeName, postSchema);
-
- final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
- postXmlSchema.put(REF_KEY, COMPONENTS_PREFIX + truePostNodeName);
- definitions.set(postXmlNodeName + discriminator, postXmlSchema);
- }
-
- childSchema.put(TYPE_KEY, OBJECT_TYPE);
- childSchema.set(PROPERTIES_KEY, properties);
- childSchema.put(TITLE_KEY, nodeName);
- childSchema.put(DESCRIPTION_KEY, description);
-
- final String defName = nodeName + discriminator;
- childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
- definitions.set(defName, childSchema);
-
- return processTopData(nodeName, discriminator, definitions, schemaNode);
- }
- return null;
- }
-
- private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
- final String postNodeName, final String description) {
- final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
- final ObjectNode postItemProperties;
- if (dataNode instanceof ListSchemaNode) {
- postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
- } else {
- postItemProperties = properties.deepCopy();
- }
- postSchema.put(TYPE_KEY, OBJECT_TYPE);
- postSchema.set(PROPERTIES_KEY, postItemProperties);
- postSchema.put(TITLE_KEY, postNodeName);
- postSchema.put(DESCRIPTION_KEY, description);
- postSchema.set(XML_KEY, buildXmlParameter(dataNode));
- return postSchema;
- }
-
- private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
- final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
- final List<QName> keyDefinition = listNode.getKeyDefinition();
- final Set<String> keys = listNode.getChildNodes().stream()
- .filter(node -> keyDefinition.contains(node.getQName()))
- .map(node -> node.getQName().getLocalName())
- .collect(Collectors.toSet());
-
- Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
- while (it.hasNext()) {
- Map.Entry<String, JsonNode> property = it.next();
- if (!keys.contains(property.getKey())) {
- postListItemProperties.set(property.getKey(), property.getValue());
- }
- }
-
- return postListItemProperties;
- }
-
- /**
- * Processes the nodes.
- */
- private ObjectNode processChildren(final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes,
- final String parentName, final ObjectNode definitions, final DefinitionNames definitionNames,
- final boolean isConfig, final SchemaInferenceStack stack) throws IOException {
- final ObjectNode properties = JsonNodeFactory.instance.objectNode();
- final ArrayNode required = JsonNodeFactory.instance.arrayNode();
- for (final DataSchemaNode node : nodes) {
- if (!isConfig || node.isConfiguration()) {
- processChildNode(node, parentName, definitions, definitionNames, isConfig, stack, properties);
- }
- }
- parentNode.set(PROPERTIES_KEY, properties);
- setRequiredIfNotEmpty(parentNode, required);
- return properties;
- }
-
- private void processChildNode(final DataSchemaNode node, final String parentName, final ObjectNode definitions,
- final DefinitionNames definitionNames, final boolean isConfig, final SchemaInferenceStack stack,
- final ObjectNode properties) throws IOException {
-
- stack.enterSchemaTree(node.getQName());
-
- /*
- Add module name prefix to property name, when needed, when ServiceNow can process colons,
- use RestDocGenUtil#resolveNodesName for creating property name
- */
- final String name = node.getQName().getLocalName();
-
- if (node instanceof LeafSchemaNode leaf) {
- processLeafNode(leaf, name, properties, JsonNodeFactory.instance.arrayNode(), stack, definitions,
- definitionNames);
-
- } else if (node instanceof AnyxmlSchemaNode anyxml) {
- processAnyXMLNode(anyxml, name, properties, JsonNodeFactory.instance.arrayNode());
-
- } else if (node instanceof AnydataSchemaNode anydata) {
- processAnydataNode(anydata, name, properties, JsonNodeFactory.instance.arrayNode());
-
- } else {
-
- final ObjectNode property;
- if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
- property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
- definitionNames, isConfig, stack);
- if (!isConfig) {
- processActionNodeContainer(node, parentName, definitions, definitionNames, stack);
- }
- } else if (node instanceof LeafListSchemaNode leafList) {
- property = processLeafListNode(leafList, stack, definitions, definitionNames);
-
- } else if (node instanceof ChoiceSchemaNode choice) {
- if (!choice.getCases().isEmpty()) {
- CaseSchemaNode caseSchemaNode = choice.getDefaultCase()
- .orElse(choice.getCases().stream()
- .findFirst().orElseThrow());
- stack.enterSchemaTree(caseSchemaNode.getQName());
- for (final DataSchemaNode childNode : caseSchemaNode.getChildNodes()) {
- processChildNode(childNode, parentName, definitions, definitionNames, isConfig, stack,
- properties);
- }
- stack.exit();
- }
- property = null;
-
- } else {
- throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
- }
- if (property != null) {
- properties.set(name, property);
- }
- }
-
- stack.exit();
- }
-
- private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack,
- final ObjectNode definitions, final DefinitionNames definitionNames) {
- final ObjectNode props = JsonNodeFactory.instance.objectNode();
- props.put(TYPE_KEY, ARRAY_TYPE);
-
- final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
- final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
- processElementCount(optConstraint, props);
-
- processTypeDef(listNode.getType(), listNode, itemsVal, stack, definitions, definitionNames);
- props.set(ITEMS_KEY, itemsVal);
-
- props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
-
- return props;
- }
-
- private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
- if (constraint.isPresent()) {
- final ElementCountConstraint constr = constraint.orElseThrow();
- final Integer minElements = constr.getMinElements();
- if (minElements != null) {
- props.put(MIN_ITEMS, minElements);
- }
- final Integer maxElements = constr.getMaxElements();
- if (maxElements != null) {
- props.put(MAX_ITEMS, maxElements);
- }
- }
- }
-
- private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
- if (node.isMandatory()) {
- required.add(nodeName);
- }
- }
-
- private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
- final ObjectNode properties, final ArrayNode required, final SchemaInferenceStack stack,
- final ObjectNode definitions, final DefinitionNames definitionNames) {
- final ObjectNode property = JsonNodeFactory.instance.objectNode();
-
- final String leafDescription = leafNode.getDescription().orElse("");
- /*
- Description can't be added, because nothing allowed alongside $ref.
- allOf is not an option, because ServiceNow can't parse it.
- */
- if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
- property.put(DESCRIPTION_KEY, leafDescription);
- }
-
- processTypeDef(leafNode.getType(), leafNode, property, stack, definitions, definitionNames);
- properties.set(jsonLeafName, property);
- property.set(XML_KEY, buildXmlParameter(leafNode));
- processMandatory(leafNode, jsonLeafName, required);
-
- return property;
- }
-
- private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
- final ObjectNode properties, final ArrayNode required) {
- final ObjectNode property = JsonNodeFactory.instance.objectNode();
-
- final String leafDescription = leafNode.getDescription().orElse("");
- property.put(DESCRIPTION_KEY, leafDescription);
-
- final String localName = leafNode.getQName().getLocalName();
- setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
- property.put(TYPE_KEY, STRING_TYPE);
- property.set(XML_KEY, buildXmlParameter(leafNode));
- processMandatory(leafNode, name, required);
- properties.set(name, property);
-
- return property;
- }
-
- private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
- final ObjectNode properties, final ArrayNode required) {
- final ObjectNode property = JsonNodeFactory.instance.objectNode();
-
- final String leafDescription = leafNode.getDescription().orElse("");
- property.put(DESCRIPTION_KEY, leafDescription);
-
- final String localName = leafNode.getQName().getLocalName();
- setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
- property.put(TYPE_KEY, STRING_TYPE);
- property.set(XML_KEY, buildXmlParameter(leafNode));
- processMandatory(leafNode, name, required);
- properties.set(name, property);
-
- return property;
- }
-
- private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
- final ObjectNode property, final SchemaInferenceStack stack, final ObjectNode definitions,
- final DefinitionNames definitionNames) {
- final String jsonType;
- if (leafTypeDef instanceof BinaryTypeDefinition) {
- jsonType = processBinaryType(property);
-
- } else if (leafTypeDef instanceof BitsTypeDefinition) {
- jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
-
- } else if (leafTypeDef instanceof EnumTypeDefinition) {
- jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
-
- } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
- jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
- definitionNames, stack.getEffectiveModelContext());
-
- } else if (leafTypeDef instanceof StringTypeDefinition) {
- jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
-
- } else if (leafTypeDef instanceof UnionTypeDefinition) {
- jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
-
- } else if (leafTypeDef instanceof EmptyTypeDefinition) {
- jsonType = OBJECT_TYPE;
- } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
- return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
- stack, definitions, definitionNames);
- } else if (leafTypeDef instanceof BooleanTypeDefinition) {
- jsonType = BOOLEAN_TYPE;
- setDefaultValue(property, true);
- } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
- jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
- } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
- jsonType = processInstanceIdentifierType(node, property, stack.getEffectiveModelContext());
- } else {
- jsonType = STRING_TYPE;
- }
- if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
- putIfNonNull(property, TYPE_KEY, jsonType);
- if (leafTypeDef.getDefaultValue().isPresent()) {
- final Object defaultValue = leafTypeDef.getDefaultValue().orElseThrow();
- if (defaultValue instanceof String stringDefaultValue) {
- if (leafTypeDef instanceof BooleanTypeDefinition) {
- setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
- } else if (leafTypeDef instanceof DecimalTypeDefinition
- || leafTypeDef instanceof Uint64TypeDefinition) {
- setDefaultValue(property, new BigDecimal(stringDefaultValue));
- } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
- //uint8,16,32 int8,16,32,64
- if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
- setDefaultValue(property, stringDefaultValue);
- } else {
- setDefaultValue(property, Long.valueOf(stringDefaultValue));
- }
- } else {
- setDefaultValue(property, stringDefaultValue);
- }
- } else {
- //we should never get here. getDefaultValue always gives us string
- setDefaultValue(property, defaultValue.toString());
- }
- }
- }
- return jsonType;
- }
-
- private static String processBinaryType(final ObjectNode property) {
- property.put(FORMAT_KEY, "byte");
- return STRING_TYPE;
- }
-
- private static String processEnumType(final EnumTypeDefinition enumLeafType, final ObjectNode property) {
- final List<EnumPair> enumPairs = enumLeafType.getValues();
- final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
- for (final EnumPair enumPair : enumPairs) {
- enumNames.add(new TextNode(enumPair.getName()));
- }
-
- property.set(ENUM_KEY, enumNames);
- setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
- return STRING_TYPE;
- }
-
- private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
- final ObjectNode definitions, final DefinitionNames definitionNames,
- final EffectiveModelContext schemaContext) {
- final String definitionName;
- if (isImported(leafTypeDef)) {
- definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
- } else {
- final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
- definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
- }
- property.put(REF_KEY, COMPONENTS_PREFIX + definitionName);
- return STRING_TYPE;
- }
-
- private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
- final ObjectNode definitions, final DefinitionNames definitionNames, final EffectiveModelContext context) {
- final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
- final String identityName = idNode.getQName().getLocalName();
- if (!definitionNames.isListedNode(idNode)) {
- final ObjectNode identityObj = buildIdentityObject(idNode, context);
- final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
- final String name = identityName + discriminator;
- definitions.set(name, identityObj);
- return name;
- } else {
- return identityName + definitionNames.getDiscriminator(idNode);
- }
- }
-
- private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
- final EffectiveModelContext context) {
- final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
- final String identityName = idNode.getQName().getLocalName();
- LOG.debug("Processing Identity: {}", identityName);
-
- identityObj.put(TITLE_KEY, identityName);
- identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
-
- final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
-
- final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
- enumPayload.add(identityName);
- populateEnumWithDerived(derivedIds, enumPayload, context);
- identityObj.set(ENUM_KEY, enumPayload);
- identityObj.put(TYPE_KEY, STRING_TYPE);
- return identityObj;
- }
-
- private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
- return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
- }
-
- private static String processBitsType(final BitsTypeDefinition bitsType, final ObjectNode property) {
- property.put(MIN_ITEMS, 0);
- property.put(UNIQUE_ITEMS_KEY, true);
- final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
- final Collection<? extends Bit> bits = bitsType.getBits();
- for (final Bit bit : bits) {
- enumNames.add(new TextNode(bit.getName()));
- }
- property.set(ENUM_KEY, enumNames);
- property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
- return STRING_TYPE;
- }
-
- private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
- final String nodeName) {
- StringTypeDefinition type = (StringTypeDefinition) stringType;
- Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
- while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
- type = type.getBaseType();
- lengthConstraints = type.getLengthConstraint();
- }
-
- if (lengthConstraints.isPresent()) {
- final Range<Integer> range = lengthConstraints.orElseThrow().getAllowedRanges().span();
- putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
- putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
- }
-
- if (type.getPatternConstraints().iterator().hasNext()) {
- final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
- String regex = pattern.getJavaPatternString();
- regex = regex.substring(1, regex.length() - 1);
- // Escape special characters to prevent issues inside Generex.
- regex = AUTOMATON_SPECIAL_CHARACTERS.matcher(regex).replaceAll("\\\\$0");
- String defaultValue = "";
- try {
- final Generex generex = new Generex(regex);
- defaultValue = generex.random();
- } catch (IllegalArgumentException ex) {
- LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
- }
- setDefaultValue(property, defaultValue);
- } else {
- setDefaultValue(property, "Some " + nodeName);
- }
- return STRING_TYPE;
- }
-
- private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
- final ObjectNode property) {
- final Optional<Number> maybeLower = leafTypeDef.getRangeConstraint()
- .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
-
- if (isHexadecimalOrOctal(leafTypeDef)) {
- return STRING_TYPE;
- }
-
- if (leafTypeDef instanceof DecimalTypeDefinition) {
- maybeLower.ifPresent(number -> setDefaultValue(property, ((Decimal64) number).decimalValue()));
- return NUMBER_TYPE;
- }
- if (leafTypeDef instanceof Uint8TypeDefinition
- || leafTypeDef instanceof Uint16TypeDefinition
- || leafTypeDef instanceof Int8TypeDefinition
- || leafTypeDef instanceof Int16TypeDefinition
- || leafTypeDef instanceof Int32TypeDefinition) {
-
- property.put(FORMAT_KEY, INT32_FORMAT);
- maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
- } else if (leafTypeDef instanceof Uint32TypeDefinition
- || leafTypeDef instanceof Int64TypeDefinition) {
-
- property.put(FORMAT_KEY, INT64_FORMAT);
- maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
- } else {
- //uint64
- setDefaultValue(property, 0);
- }
- return INTEGER_TYPE;
- }
-
- private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
- final Optional<?> optDefaultValue = typeDef.getDefaultValue();
- if (optDefaultValue.isPresent()) {
- final String defaultValue = (String) optDefaultValue.orElseThrow();
- return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
- }
- return false;
- }
-
- private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
- final EffectiveModelContext schemaContext) {
- // create example instance-identifier to the first container of node's module if exists or leave it empty
- final var module = schemaContext.findModule(node.getQName().getModule());
- if (module.isPresent()) {
- final var container = module.orElseThrow().getChildNodes().stream()
- .filter(n -> n instanceof ContainerSchemaNode)
- .findFirst();
- container.ifPresent(c -> setDefaultValue(property, String.format("/%s:%s", module.orElseThrow().getPrefix(),
- c.getQName().getLocalName())));
- }
-
- return STRING_TYPE;
- }
-
- private static String processUnionType(final UnionTypeDefinition unionType) {
- boolean isStringTakePlace = false;
- boolean isNumberTakePlace = false;
- boolean isBooleanTakePlace = false;
- for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
- if (!isStringTakePlace) {
- if (typeDef instanceof StringTypeDefinition
- || typeDef instanceof BitsTypeDefinition
- || typeDef instanceof BinaryTypeDefinition
- || typeDef instanceof IdentityrefTypeDefinition
- || typeDef instanceof EnumTypeDefinition
- || typeDef instanceof LeafrefTypeDefinition
- || typeDef instanceof UnionTypeDefinition) {
- isStringTakePlace = true;
- } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
- isNumberTakePlace = true;
- } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
- isBooleanTakePlace = true;
- }
- }
- }
- if (isStringTakePlace) {
- return STRING_TYPE;
- }
- if (isBooleanTakePlace) {
- if (isNumberTakePlace) {
- return STRING_TYPE;
- }
- return BOOLEAN_TYPE;
- }
- return NUMBER_TYPE;
- }
-
- private static ObjectNode buildXmlParameter(final SchemaNode node) {
- final ObjectNode xml = JsonNodeFactory.instance.objectNode();
- final QName qName = node.getQName();
- xml.put(NAME_KEY, qName.getLocalName());
- xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
- return xml;
- }
-
- private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
- if (key != null && number != null) {
- if (number instanceof Double) {
- property.put(key, (Double) number);
- } else if (number instanceof Float) {
- property.put(key, (Float) number);
- } else if (number instanceof Integer) {
- property.put(key, (Integer) number);
- } else if (number instanceof Short) {
- property.put(key, (Short) number);
- } else if (number instanceof Long) {
- property.put(key, (Long) number);
- }
- }
- }
-
- private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
- if (key != null && value != null) {
- property.put(key, value);
- }
- }
-
- private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
- if (required.size() > 0) {
- node.set(REQUIRED_KEY, required);
- }
- }
-
- private static void setDefaultValue(final ObjectNode property, final String value) {
- property.put(DEFAULT_KEY, value);
- }
-
- private static void setDefaultValue(final ObjectNode property, final Integer value) {
- property.put(DEFAULT_KEY, value);
- }
-
- private static void setDefaultValue(final ObjectNode property, final Long value) {
- property.put(DEFAULT_KEY, value);
- }
-
- private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
- property.put(DEFAULT_KEY, value);
- }
-
- private static void setDefaultValue(final ObjectNode property, final Boolean value) {
- property.put(DEFAULT_KEY, value);
- }
-
-}