2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.sal.rest.doc.impl;
10 import static org.opendaylight.netconf.sal.rest.doc.impl.BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX;
11 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.CONFIG;
12 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.NAME_KEY;
13 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.POST_SUFFIX;
14 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TOP;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_KEY;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.XML_SUFFIX;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getAppropriateModelPrefix;
19 import com.fasterxml.jackson.databind.JsonNode;
20 import com.fasterxml.jackson.databind.node.ArrayNode;
21 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
22 import com.fasterxml.jackson.databind.node.ObjectNode;
23 import com.fasterxml.jackson.databind.node.TextNode;
24 import com.google.common.collect.Range;
25 import com.google.common.collect.RangeSet;
26 import com.mifmif.common.regex.Generex;
27 import java.io.IOException;
28 import java.math.BigDecimal;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.List;
33 import java.util.Optional;
35 import java.util.stream.Collectors;
36 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
39 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
40 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
45 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
47 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
48 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
49 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
50 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
55 import org.opendaylight.yangtools.yang.model.api.Module;
56 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
57 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
58 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
63 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
68 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
71 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
73 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
74 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
76 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
77 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
78 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
79 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
80 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
81 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
82 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
83 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
84 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
85 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
90 * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
92 public class DefinitionGenerator {
94 private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
96 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
97 private static final String MAX_ITEMS = "maxItems";
98 private static final String MIN_ITEMS = "minItems";
99 private static final String MAX_LENGTH_KEY = "maxLength";
100 private static final String MIN_LENGTH_KEY = "minLength";
101 private static final String REQUIRED_KEY = "required";
102 private static final String REF_KEY = "$ref";
103 private static final String ITEMS_KEY = "items";
104 private static final String TYPE_KEY = "type";
105 private static final String PROPERTIES_KEY = "properties";
106 private static final String DESCRIPTION_KEY = "description";
107 private static final String ARRAY_TYPE = "array";
108 private static final String ENUM_KEY = "enum";
109 private static final String TITLE_KEY = "title";
110 private static final String DEFAULT_KEY = "default";
111 private static final String FORMAT_KEY = "format";
112 private static final String NAMESPACE_KEY = "namespace";
113 public static final String INPUT = "input";
114 public static final String INPUT_SUFFIX = "_input";
115 public static final String OUTPUT = "output";
116 public static final String OUTPUT_SUFFIX = "_output";
117 private static final String STRING_TYPE = "string";
118 private static final String OBJECT_TYPE = "object";
119 private static final String NUMBER_TYPE = "number";
120 private static final String INTEGER_TYPE = "integer";
121 private static final String INT32_FORMAT = "int32";
122 private static final String INT64_FORMAT = "int64";
123 private static final String BOOLEAN_TYPE = "boolean";
125 private Module topLevelModule;
127 public DefinitionGenerator() {
131 * Creates Json definitions from provided module according to swagger spec.
133 * @param module - Yang module to be converted
134 * @param schemaContext - SchemaContext of all Yang files used by Api Doc
135 * @param definitionNames - Store for definition names
136 * @return ObjectNode containing data used for creating examples and definitions in Api Doc
137 * @throws IOException if I/O operation fails
141 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
142 final ObjectNode definitions, final DefinitionNames definitionNames,
143 final OAversion oaversion, final boolean isForSingleModule)
145 topLevelModule = module;
147 processIdentities(module, definitions, definitionNames, schemaContext);
148 processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
149 processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
151 if (isForSingleModule) {
152 processModule(module, definitions, definitionNames, schemaContext, oaversion);
158 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
159 final DefinitionNames definitionNames, final OAversion oaversion,
160 final boolean isForSingleModule)
162 final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
163 if (isForSingleModule) {
164 definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
166 return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
169 private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
170 final EffectiveModelContext schemaContext, final OAversion oaversion) {
171 final ObjectNode definition = JsonNodeFactory.instance.objectNode();
172 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
173 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
174 final String moduleName = module.getName();
175 final String definitionName = moduleName + MODULE_NAME_SUFFIX;
176 for (final DataSchemaNode node : module.getChildNodes()) {
177 final String localName = node.getQName().getLocalName();
178 if (node.isConfiguration()) {
179 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
180 for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
181 final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
183 final String ref = getAppropriateModelPrefix(oaversion)
184 + moduleName + CONFIG
186 + definitionNames.getDiscriminator(node);
188 if (node instanceof ListSchemaNode) {
189 childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
190 final ObjectNode items = JsonNodeFactory.instance.objectNode();
191 items.put(REF_KEY, ref);
192 childNodeProperties.set(ITEMS_KEY, items);
193 childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
194 childNodeProperties.put(TITLE_KEY, localName + CONFIG);
197 Description can't be added, because nothing allowed alongside $ref.
198 allOf is not an option, because ServiceNow can't parse it.
200 childNodeProperties.put(REF_KEY, ref);
202 //add module name prefix to property name, when ServiceNow can process colons
203 properties.set(localName, childNodeProperties);
206 if (node instanceof LeafSchemaNode) {
208 Add module name prefix to property name, when ServiceNow can process colons(second parameter
211 processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
212 definitions, definitionNames, oaversion);
217 definition.put(TITLE_KEY, definitionName);
218 definition.put(TYPE_KEY, OBJECT_TYPE);
219 definition.set(PROPERTIES_KEY, properties);
220 definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
221 setRequiredIfNotEmpty(definition, required);
223 definitions.set(definitionName, definition);
226 private void processContainersAndLists(final Module module, final ObjectNode definitions,
227 final DefinitionNames definitionNames, final EffectiveModelContext schemaContext, final OAversion oaversion)
229 final String moduleName = module.getName();
231 for (final DataSchemaNode childNode : module.getChildNodes()) {
232 // For every container and list in the module
233 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
234 if (childNode.isConfiguration()) {
235 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
236 true, schemaContext, oaversion);
238 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
239 false, schemaContext, oaversion);
240 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
246 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
247 final ObjectNode definitions, final DefinitionNames definitionNames,
248 final EffectiveModelContext schemaContext, final OAversion oaversion)
250 for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
251 processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
255 private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
256 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
257 final String moduleName = module.getName();
258 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
259 processOperations(rpcDefinition, moduleName, definitions, definitionNames, schemaContext, oaversion);
264 private void processOperations(final OperationDefinition operationDef, final String parentName,
265 final ObjectNode definitions, final DefinitionNames definitionNames,
266 final EffectiveModelContext schemaContext, final OAversion oaversion)
268 final String operationName = operationDef.getQName().getLocalName();
269 processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
270 definitionNames, schemaContext, oaversion);
271 processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
272 definitionNames, schemaContext, oaversion);
275 private void processOperationInputOutput(final ContainerLike container, final String operationName,
276 final String parentName, final boolean isInput,
277 final ObjectNode definitions, final DefinitionNames definitionNames,
278 final EffectiveModelContext schemaContext, final OAversion oaversion)
280 if (!container.getChildNodes().isEmpty()) {
281 final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
282 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
283 processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
284 false, schemaContext, oaversion);
286 childSchema.put(TYPE_KEY, OBJECT_TYPE);
287 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
288 xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
289 childSchema.set(XML_KEY, xml);
290 childSchema.put(TITLE_KEY, filename);
291 final String discriminator =
292 definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
293 definitions.set(filename + discriminator, childSchema);
295 processTopData(filename, discriminator, definitions, container, oaversion);
299 private static ObjectNode processTopData(final String filename, final String discriminator,
300 final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
301 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
302 final String name = filename + discriminator;
303 final String ref = getAppropriateModelPrefix(oaversion) + name;
304 final String topName = filename + TOP;
306 if (schemaNode instanceof ListSchemaNode) {
307 dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
308 final ObjectNode items = JsonNodeFactory.instance.objectNode();
309 items.put(REF_KEY, ref);
310 dataNodeProperties.set(ITEMS_KEY, items);
311 dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
314 Description can't be added, because nothing allowed alongside $ref.
315 allOf is not an option, because ServiceNow can't parse it.
317 dataNodeProperties.put(REF_KEY, ref);
320 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
322 Add module name prefix to property name, when needed, when ServiceNow can process colons,
323 use RestDocGenUtil#resolveNodesName for creating property name
325 properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
326 final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
327 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
328 finalChildSchema.set(PROPERTIES_KEY, properties);
329 finalChildSchema.put(TITLE_KEY, topName);
332 definitions.set(topName + discriminator, finalChildSchema);
334 return dataNodeProperties;
338 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
339 * @param module The module from which the identity stmt will be processed
340 * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
341 * @param definitionNames Store for definition names
343 private static void processIdentities(final Module module, final ObjectNode definitions,
344 final DefinitionNames definitionNames, final EffectiveModelContext context) {
346 final String moduleName = module.getName();
347 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
348 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
350 for (final IdentitySchemaNode idNode : idNodes) {
351 final ObjectNode identityObj = buildIdentityObject(idNode, context);
352 final String idName = idNode.getQName().getLocalName();
353 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
354 final String name = idName + discriminator;
355 definitions.set(name, identityObj);
359 private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
360 final ArrayNode enumPayload, final EffectiveModelContext context) {
361 for (final IdentitySchemaNode derivedId : derivedIds) {
362 enumPayload.add(derivedId.getQName().getLocalName());
363 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
367 private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
368 final ObjectNode definitions, final DefinitionNames definitionNames,
369 final boolean isConfig, final EffectiveModelContext schemaContext,
370 final OAversion oaversion) throws IOException {
371 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
372 final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
373 final SchemaNode schemaNode = (SchemaNode) dataNode;
374 final String localName = schemaNode.getQName().getLocalName();
375 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
376 final String nameAsParent = parentName + "_" + localName;
377 final ObjectNode properties =
378 processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
379 definitionNames, isConfig, schemaContext, oaversion);
381 final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
382 final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
383 final String postXmlNodeName = postNodeName + XML_SUFFIX;
384 final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
386 final String description = schemaNode.getDescription().orElse("");
387 final String discriminator;
389 if (!definitionNames.isListedNode(schemaNode)) {
390 final List<String> names = List.of(parentNameConfigLocalName,
391 parentNameConfigLocalName + TOP,
396 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
398 discriminator = definitionNames.getDiscriminator(schemaNode);
402 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
403 String truePostNodeName = postNodeName + discriminator;
404 definitions.set(truePostNodeName, postSchema);
406 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
407 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
408 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
411 childSchema.put(TYPE_KEY, OBJECT_TYPE);
412 childSchema.set(PROPERTIES_KEY, properties);
413 childSchema.put(TITLE_KEY, nodeName);
414 childSchema.put(DESCRIPTION_KEY, description);
416 final String defName = nodeName + discriminator;
417 childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
418 definitions.set(defName, childSchema);
420 return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
425 private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
426 final String postNodeName, final String description) {
427 final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
428 final ObjectNode postItemProperties;
429 if (dataNode instanceof ListSchemaNode) {
430 postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
432 postItemProperties = properties.deepCopy();
434 postSchema.put(TYPE_KEY, OBJECT_TYPE);
435 postSchema.set(PROPERTIES_KEY, postItemProperties);
436 postSchema.put(TITLE_KEY, postNodeName);
437 postSchema.put(DESCRIPTION_KEY, description);
438 postSchema.set(XML_KEY, buildXmlParameter(dataNode));
442 private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
443 final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
444 final List<QName> keyDefinition = listNode.getKeyDefinition();
445 final Set<String> keys = listNode.getChildNodes().stream()
446 .filter(node -> keyDefinition.contains(node.getQName()))
447 .map(node -> node.getQName().getLocalName())
448 .collect(Collectors.toSet());
450 Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
451 while (it.hasNext()) {
452 Map.Entry<String, JsonNode> property = it.next();
453 if (!keys.contains(property.getKey())) {
454 postListItemProperties.set(property.getKey(), property.getValue());
458 return postListItemProperties;
462 * Processes the nodes.
464 private ObjectNode processChildren(
465 final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
466 final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
467 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
468 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
469 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
470 for (final DataSchemaNode node : nodes) {
471 if (!isConfig || node.isConfiguration()) {
473 Add module name prefix to property name, when needed, when ServiceNow can process colons,
474 use RestDocGenUtil#resolveNodesName for creating property name
476 final String propertyName = node.getQName().getLocalName();
477 final ObjectNode property;
478 if (node instanceof LeafSchemaNode) {
479 processLeafNode((LeafSchemaNode) node, propertyName, properties,
480 required, schemaContext, definitions, definitionNames, oaversion);
481 } else if (node instanceof AnyxmlSchemaNode) {
482 processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
484 } else if (node instanceof AnydataSchemaNode) {
485 processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
488 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
489 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
490 definitionNames, isConfig, schemaContext, oaversion);
492 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
495 } else if (node instanceof LeafListSchemaNode) {
496 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
497 definitionNames, oaversion);
499 } else if (node instanceof ChoiceSchemaNode) {
500 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
501 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
502 isConfig, schemaContext, properties, oaversion);
507 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
509 properties.set(propertyName, property);
513 parentNode.set(PROPERTIES_KEY, properties);
514 setRequiredIfNotEmpty(parentNode, required);
518 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final EffectiveModelContext schemaContext,
519 final ObjectNode definitions, final DefinitionNames definitionNames,
520 final OAversion oaversion) {
521 final ObjectNode props = JsonNodeFactory.instance.objectNode();
522 props.put(TYPE_KEY, ARRAY_TYPE);
524 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
525 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
526 processElementCount(optConstraint, props);
528 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
529 props.set(ITEMS_KEY, itemsVal);
531 props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
536 private void processChoiceNode(
537 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
538 final DefinitionNames definitionNames, final boolean isConfig,
539 final EffectiveModelContext schemaContext, final ObjectNode properties, final OAversion oaversion)
541 for (final DataSchemaNode node : nodes) {
543 Add module name prefix to property name, when needed, when ServiceNow can process colons,
544 use RestDocGenUtil#resolveNodesName for creating property name
546 final String name = node.getQName().getLocalName();
547 final ObjectNode property;
550 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
553 if (node instanceof LeafSchemaNode) {
554 processLeafNode((LeafSchemaNode) node, name, properties,
555 JsonNodeFactory.instance.arrayNode(), schemaContext, definitions, definitionNames, oaversion);
556 } else if (node instanceof AnyxmlSchemaNode) {
557 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
558 JsonNodeFactory.instance.arrayNode());
559 } else if (node instanceof AnydataSchemaNode) {
560 processAnydataNode((AnydataSchemaNode) node, name, properties,
561 JsonNodeFactory.instance.arrayNode());
563 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
564 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
565 definitionNames, isConfig, schemaContext, oaversion);
567 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
570 } else if (node instanceof LeafListSchemaNode) {
571 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
572 definitionNames, oaversion);
574 } else if (node instanceof ChoiceSchemaNode) {
575 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
576 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
577 schemaContext, properties, oaversion);
581 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
583 properties.set(name, property);
588 private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
589 if (constraint.isPresent()) {
590 final ElementCountConstraint constr = constraint.get();
591 final Integer minElements = constr.getMinElements();
592 if (minElements != null) {
593 props.put(MIN_ITEMS, minElements);
595 final Integer maxElements = constr.getMaxElements();
596 if (maxElements != null) {
597 props.put(MAX_ITEMS, maxElements);
602 private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
603 if (node.isMandatory()) {
604 required.add(nodeName);
608 private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
609 final ObjectNode properties, final ArrayNode required,
610 final EffectiveModelContext schemaContext, final ObjectNode definitions,
611 final DefinitionNames definitionNames, final OAversion oaversion) {
612 final ObjectNode property = JsonNodeFactory.instance.objectNode();
614 final String leafDescription = leafNode.getDescription().orElse("");
616 Description can't be added, because nothing allowed alongside $ref.
617 allOf is not an option, because ServiceNow can't parse it.
619 if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
620 property.put(DESCRIPTION_KEY, leafDescription);
623 processTypeDef(leafNode.getType(), leafNode, property, schemaContext, definitions, definitionNames, oaversion);
624 properties.set(jsonLeafName, property);
625 property.set(XML_KEY, buildXmlParameter(leafNode));
626 processMandatory(leafNode, jsonLeafName, required);
631 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
632 final ObjectNode properties, final ArrayNode required) {
633 final ObjectNode property = JsonNodeFactory.instance.objectNode();
635 final String leafDescription = leafNode.getDescription().orElse("");
636 property.put(DESCRIPTION_KEY, leafDescription);
638 final String localName = leafNode.getQName().getLocalName();
639 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
640 property.put(TYPE_KEY, STRING_TYPE);
641 property.set(XML_KEY, buildXmlParameter(leafNode));
642 processMandatory(leafNode, name, required);
643 properties.set(name, property);
648 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
649 final ObjectNode properties, final ArrayNode required) {
650 final ObjectNode property = JsonNodeFactory.instance.objectNode();
652 final String leafDescription = leafNode.getDescription().orElse("");
653 property.put(DESCRIPTION_KEY, leafDescription);
655 final String localName = leafNode.getQName().getLocalName();
656 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
657 property.put(TYPE_KEY, STRING_TYPE);
658 property.set(XML_KEY, buildXmlParameter(leafNode));
659 processMandatory(leafNode, name, required);
660 properties.set(name, property);
665 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
666 final ObjectNode property, final EffectiveModelContext schemaContext,
667 final ObjectNode definitions, final DefinitionNames definitionNames,
668 final OAversion oaversion) {
669 final String jsonType;
670 if (leafTypeDef instanceof BinaryTypeDefinition) {
671 jsonType = processBinaryType(property);
673 } else if (leafTypeDef instanceof BitsTypeDefinition) {
674 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
676 } else if (leafTypeDef instanceof EnumTypeDefinition) {
677 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
679 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
680 jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
681 definitionNames, oaversion, schemaContext);
683 } else if (leafTypeDef instanceof StringTypeDefinition) {
684 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
686 } else if (leafTypeDef instanceof UnionTypeDefinition) {
687 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
689 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
690 jsonType = OBJECT_TYPE;
691 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
692 final SchemaInferenceStack stack = SchemaInferenceStack.ofSchemaPath(schemaContext, node.getPath());
693 return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
694 schemaContext, definitions, definitionNames, oaversion);
695 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
696 jsonType = BOOLEAN_TYPE;
697 setDefaultValue(property, true);
698 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
699 jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
700 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
701 jsonType = processInstanceIdentifierType(node, property, schemaContext);
703 jsonType = STRING_TYPE;
705 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
706 putIfNonNull(property, TYPE_KEY, jsonType);
707 if (leafTypeDef.getDefaultValue().isPresent()) {
708 final Object defaultValue = leafTypeDef.getDefaultValue().get();
709 if (defaultValue instanceof String) {
710 final String stringDefaultValue = (String) defaultValue;
711 if (leafTypeDef instanceof BooleanTypeDefinition) {
712 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
713 } else if (leafTypeDef instanceof DecimalTypeDefinition
714 || leafTypeDef instanceof Uint64TypeDefinition) {
715 setDefaultValue(property, new BigDecimal(stringDefaultValue));
716 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
717 //uint8,16,32 int8,16,32,64
718 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
719 setDefaultValue(property, stringDefaultValue);
721 setDefaultValue(property, Long.valueOf(stringDefaultValue));
724 setDefaultValue(property, stringDefaultValue);
727 //we should never get here. getDefaultValue always gives us string
728 setDefaultValue(property, defaultValue.toString());
735 private static String processBinaryType(final ObjectNode property) {
736 property.put(FORMAT_KEY, "byte");
740 private static String processEnumType(final EnumTypeDefinition enumLeafType,
741 final ObjectNode property) {
742 final List<EnumPair> enumPairs = enumLeafType.getValues();
743 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
744 for (final EnumPair enumPair : enumPairs) {
745 enumNames.add(new TextNode(enumPair.getName()));
748 property.set(ENUM_KEY, enumNames);
749 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
753 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
754 final ObjectNode definitions, final DefinitionNames definitionNames,
755 final OAversion oaversion, final EffectiveModelContext schemaContext) {
756 final String definitionName;
757 if (isImported(leafTypeDef)) {
758 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
760 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
761 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
763 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
767 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
768 final ObjectNode definitions, final DefinitionNames definitionNames,
769 final EffectiveModelContext context) {
770 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
771 final String identityName = idNode.getQName().getLocalName();
772 if (!definitionNames.isListedNode(idNode)) {
773 final ObjectNode identityObj = buildIdentityObject(idNode, context);
774 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
775 final String name = identityName + discriminator;
776 definitions.set(name, identityObj);
779 return identityName + definitionNames.getDiscriminator(idNode);
783 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
784 final EffectiveModelContext context) {
785 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
786 final String identityName = idNode.getQName().getLocalName();
787 LOG.debug("Processing Identity: {}", identityName);
789 identityObj.put(TITLE_KEY, identityName);
790 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
792 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
794 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
795 enumPayload.add(identityName);
796 populateEnumWithDerived(derivedIds, enumPayload, context);
797 identityObj.set(ENUM_KEY, enumPayload);
798 identityObj.put(TYPE_KEY, STRING_TYPE);
802 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
803 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
806 private static String processBitsType(final BitsTypeDefinition bitsType,
807 final ObjectNode property) {
808 property.put(MIN_ITEMS, 0);
809 property.put(UNIQUE_ITEMS_KEY, true);
810 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
811 final Collection<? extends Bit> bits = bitsType.getBits();
812 for (final Bit bit : bits) {
813 enumNames.add(new TextNode(bit.getName()));
815 property.set(ENUM_KEY, enumNames);
816 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
820 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
821 final String nodeName) {
822 StringTypeDefinition type = (StringTypeDefinition) stringType;
823 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
824 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
825 type = type.getBaseType();
826 lengthConstraints = type.getLengthConstraint();
829 if (lengthConstraints.isPresent()) {
830 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
831 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
832 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
835 if (type.getPatternConstraints().iterator().hasNext()) {
836 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
837 String regex = pattern.getJavaPatternString();
838 regex = regex.substring(1, regex.length() - 1);
839 String defaultValue = "";
841 final Generex generex = new Generex(regex);
842 defaultValue = generex.random();
843 } catch (IllegalArgumentException ex) {
844 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
846 setDefaultValue(property, defaultValue);
848 setDefaultValue(property, "Some " + nodeName);
853 private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
854 final ObjectNode property) {
855 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
856 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
858 if (isHexadecimalOrOctal(leafTypeDef)) {
862 if (leafTypeDef instanceof DecimalTypeDefinition) {
863 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
866 if (leafTypeDef instanceof Uint8TypeDefinition
867 || leafTypeDef instanceof Uint16TypeDefinition
868 || leafTypeDef instanceof Int8TypeDefinition
869 || leafTypeDef instanceof Int16TypeDefinition
870 || leafTypeDef instanceof Int32TypeDefinition) {
872 property.put(FORMAT_KEY, INT32_FORMAT);
873 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
874 } else if (leafTypeDef instanceof Uint32TypeDefinition
875 || leafTypeDef instanceof Int64TypeDefinition) {
877 property.put(FORMAT_KEY, INT64_FORMAT);
878 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
881 setDefaultValue(property, 0);
886 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
887 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
888 if (optDefaultValue.isPresent()) {
889 final String defaultValue = (String)optDefaultValue.get();
890 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
895 private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
896 final EffectiveModelContext schemaContext) {
897 // create example instance-identifier to the first container of node's module if exists or leave it empty
898 final var module = schemaContext.findModule(node.getQName().getModule());
899 if (module.isPresent()) {
900 final var container = module.get().getChildNodes().stream()
901 .filter(n -> n instanceof ContainerSchemaNode)
903 container.ifPresent(c -> setDefaultValue(property, String.format("/%s:%s", module.get().getPrefix(),
904 c.getQName().getLocalName())));
910 private static String processUnionType(final UnionTypeDefinition unionType) {
911 boolean isStringTakePlace = false;
912 boolean isNumberTakePlace = false;
913 boolean isBooleanTakePlace = false;
914 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
915 if (!isStringTakePlace) {
916 if (typeDef instanceof StringTypeDefinition
917 || typeDef instanceof BitsTypeDefinition
918 || typeDef instanceof BinaryTypeDefinition
919 || typeDef instanceof IdentityrefTypeDefinition
920 || typeDef instanceof EnumTypeDefinition
921 || typeDef instanceof LeafrefTypeDefinition
922 || typeDef instanceof UnionTypeDefinition) {
923 isStringTakePlace = true;
924 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
925 isNumberTakePlace = true;
926 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
927 isBooleanTakePlace = true;
931 if (isStringTakePlace) {
934 if (isBooleanTakePlace) {
935 if (isNumberTakePlace) {
943 private static ObjectNode buildXmlParameter(final SchemaNode node) {
944 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
945 final QName qName = node.getQName();
946 xml.put(NAME_KEY, qName.getLocalName());
947 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
951 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
952 if (key != null && number != null) {
953 if (number instanceof Double) {
954 property.put(key, (Double) number);
955 } else if (number instanceof Float) {
956 property.put(key, (Float) number);
957 } else if (number instanceof Integer) {
958 property.put(key, (Integer) number);
959 } else if (number instanceof Short) {
960 property.put(key, (Short) number);
961 } else if (number instanceof Long) {
962 property.put(key, (Long) number);
967 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
968 if (key != null && value != null) {
969 property.put(key, value);
973 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
974 if (required.size() > 0) {
975 node.set(REQUIRED_KEY, required);
979 private static void setDefaultValue(final ObjectNode property, final String value) {
980 property.put(DEFAULT_KEY, value);
983 private static void setDefaultValue(final ObjectNode property, final Integer value) {
984 property.put(DEFAULT_KEY, value);
987 private static void setDefaultValue(final ObjectNode property, final Long value) {
988 property.put(DEFAULT_KEY, value);
991 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
992 property.put(DEFAULT_KEY, value);
995 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
996 property.put(DEFAULT_KEY, value);