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.ElementCountConstraint;
49 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
54 import org.opendaylight.yangtools.yang.model.api.Module;
55 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
56 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
57 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
58 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
59 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
60 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
64 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
69 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
71 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
73 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
74 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
76 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
77 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
78 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
79 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
80 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
81 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
82 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
83 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
84 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
85 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
86 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
91 * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
93 public class DefinitionGenerator {
95 private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
97 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
98 private static final String MAX_ITEMS = "maxItems";
99 private static final String MIN_ITEMS = "minItems";
100 private static final String MAX_LENGTH_KEY = "maxLength";
101 private static final String MIN_LENGTH_KEY = "minLength";
102 private static final String REQUIRED_KEY = "required";
103 private static final String REF_KEY = "$ref";
104 private static final String ITEMS_KEY = "items";
105 private static final String TYPE_KEY = "type";
106 private static final String PROPERTIES_KEY = "properties";
107 private static final String DESCRIPTION_KEY = "description";
108 private static final String ARRAY_TYPE = "array";
109 private static final String ENUM_KEY = "enum";
110 private static final String TITLE_KEY = "title";
111 private static final String DEFAULT_KEY = "default";
112 private static final String FORMAT_KEY = "format";
113 private static final String NAMESPACE_KEY = "namespace";
114 public static final String INPUT = "input";
115 public static final String INPUT_SUFFIX = "_input";
116 public static final String OUTPUT = "output";
117 public static final String OUTPUT_SUFFIX = "_output";
118 private static final String STRING_TYPE = "string";
119 private static final String OBJECT_TYPE = "object";
120 private static final String NUMBER_TYPE = "number";
121 private static final String INTEGER_TYPE = "integer";
122 private static final String INT32_FORMAT = "int32";
123 private static final String INT64_FORMAT = "int64";
124 private static final String BOOLEAN_TYPE = "boolean";
126 private Module topLevelModule;
128 public DefinitionGenerator() {
132 * Creates Json definitions from provided module according to swagger spec.
134 * @param module - Yang module to be converted
135 * @param schemaContext - SchemaContext of all Yang files used by Api Doc
136 * @param definitionNames - Store for definition names
137 * @return ObjectNode containing data used for creating examples and definitions in Api Doc
138 * @throws IOException if I/O operation fails
142 public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
143 final ObjectNode definitions, final DefinitionNames definitionNames,
144 final OAversion oaversion, final boolean isForSingleModule)
146 topLevelModule = module;
148 processIdentities(module, definitions, definitionNames, schemaContext);
149 processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
150 processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
152 if (isForSingleModule) {
153 processModule(module, definitions, definitionNames, schemaContext, oaversion);
159 public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
160 final DefinitionNames definitionNames, final OAversion oaversion,
161 final boolean isForSingleModule)
163 final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
164 if (isForSingleModule) {
165 definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
167 return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
170 private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
171 final SchemaContext schemaContext, final OAversion oaversion) {
172 final ObjectNode definition = JsonNodeFactory.instance.objectNode();
173 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
174 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
175 final String moduleName = module.getName();
176 final String definitionName = moduleName + MODULE_NAME_SUFFIX;
177 for (final DataSchemaNode node : module.getChildNodes()) {
178 final String localName = node.getQName().getLocalName();
179 if (node.isConfiguration()) {
180 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
181 for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
182 final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
184 final String ref = getAppropriateModelPrefix(oaversion)
185 + moduleName + CONFIG
187 + definitionNames.getDiscriminator(node);
189 if (node instanceof ListSchemaNode) {
190 childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
191 final ObjectNode items = JsonNodeFactory.instance.objectNode();
192 items.put(REF_KEY, ref);
193 childNodeProperties.set(ITEMS_KEY, items);
194 childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
195 childNodeProperties.put(TITLE_KEY, localName + CONFIG);
198 Description can't be added, because nothing allowed alongside $ref.
199 allOf is not an option, because ServiceNow can't parse it.
201 childNodeProperties.put(REF_KEY, ref);
203 //add module name prefix to property name, when ServiceNow can process colons
204 properties.set(localName, childNodeProperties);
207 if (node instanceof LeafSchemaNode) {
209 Add module name prefix to property name, when ServiceNow can process colons(second parameter
212 processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
213 definitions, definitionNames, oaversion);
218 definition.put(TITLE_KEY, definitionName);
219 definition.put(TYPE_KEY, OBJECT_TYPE);
220 definition.set(PROPERTIES_KEY, properties);
221 definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
222 setRequiredIfNotEmpty(definition, required);
224 definitions.set(definitionName, definition);
227 private void processContainersAndLists(final Module module, final ObjectNode definitions,
228 final DefinitionNames definitionNames, final SchemaContext schemaContext,
229 final OAversion oaversion) throws IOException {
230 final String moduleName = module.getName();
232 for (final DataSchemaNode childNode : module.getChildNodes()) {
233 // For every container and list in the module
234 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
235 if (childNode.isConfiguration()) {
236 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
237 true, schemaContext, oaversion);
239 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
240 false, schemaContext, oaversion);
241 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
247 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
248 final ObjectNode definitions, final DefinitionNames definitionNames,
249 final SchemaContext schemaContext, final OAversion oaversion)
251 for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
252 processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
256 private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
257 final SchemaContext schemaContext, final OAversion oaversion) throws IOException {
258 final String moduleName = module.getName();
259 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
260 processOperations(rpcDefinition, moduleName, definitions, definitionNames, schemaContext, oaversion);
265 private void processOperations(final OperationDefinition operationDef, final String parentName,
266 final ObjectNode definitions, final DefinitionNames definitionNames,
267 final SchemaContext schemaContext, final OAversion oaversion) throws IOException {
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 SchemaContext 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 ObjectNode processTopData(final String filename, final String discriminator, final ObjectNode definitions,
300 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 SchemaContext 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 SchemaContext 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 SchemaContext 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 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 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 SchemaContext 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 SchemaContext 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 SchemaContext 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 SchemaContext 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 SchemaContext 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 return processTypeDef(SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) leafTypeDef,
693 schemaContext, node), node, property, schemaContext, definitions, definitionNames, oaversion);
694 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
695 jsonType = BOOLEAN_TYPE;
696 setDefaultValue(property, true);
697 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
698 jsonType = processNumberType((RangeRestrictedTypeDefinition) leafTypeDef, property);
699 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
700 jsonType = processInstanceIdentifierType(node, property, schemaContext);
702 jsonType = STRING_TYPE;
704 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
705 putIfNonNull(property, TYPE_KEY, jsonType);
706 if (leafTypeDef.getDefaultValue().isPresent()) {
707 final Object defaultValue = leafTypeDef.getDefaultValue().get();
708 if (defaultValue instanceof String) {
709 final String stringDefaultValue = (String) defaultValue;
710 if (leafTypeDef instanceof BooleanTypeDefinition) {
711 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
712 } else if (leafTypeDef instanceof DecimalTypeDefinition
713 || leafTypeDef instanceof Uint64TypeDefinition) {
714 setDefaultValue(property, new BigDecimal(stringDefaultValue));
715 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
716 //uint8,16,32 int8,16,32,64
717 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition)leafTypeDef)) {
718 setDefaultValue(property, stringDefaultValue);
720 setDefaultValue(property, Long.valueOf(stringDefaultValue));
723 setDefaultValue(property, stringDefaultValue);
726 //we should never get here. getDefaultValue always gives us string
727 setDefaultValue(property, defaultValue.toString());
734 private static String processBinaryType(final ObjectNode property) {
735 property.put(FORMAT_KEY, "byte");
739 private static String processEnumType(final EnumTypeDefinition enumLeafType,
740 final ObjectNode property) {
741 final List<EnumPair> enumPairs = enumLeafType.getValues();
742 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
743 for (final EnumPair enumPair : enumPairs) {
744 enumNames.add(new TextNode(enumPair.getName()));
747 property.set(ENUM_KEY, enumNames);
748 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
752 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
753 final ObjectNode definitions, final DefinitionNames definitionNames,
754 final OAversion oaversion, final SchemaContext schemaContext) {
755 final String definitionName;
756 if (isImported(leafTypeDef)) {
757 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
759 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
760 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
762 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
766 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
767 final ObjectNode definitions, final DefinitionNames definitionNames,
768 final SchemaContext context) {
769 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
770 final String identityName = idNode.getQName().getLocalName();
771 if (!definitionNames.isListedNode(idNode)) {
772 final ObjectNode identityObj = buildIdentityObject(idNode, context);
773 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
774 final String name = identityName + discriminator;
775 definitions.set(name, identityObj);
778 return identityName + definitionNames.getDiscriminator(idNode);
782 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode, final SchemaContext context) {
783 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
784 final String identityName = idNode.getQName().getLocalName();
785 LOG.debug("Processing Identity: {}", identityName);
787 identityObj.put(TITLE_KEY, identityName);
788 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
790 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
792 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
793 enumPayload.add(identityName);
794 populateEnumWithDerived(derivedIds, enumPayload, context);
795 identityObj.set(ENUM_KEY, enumPayload);
796 identityObj.put(TYPE_KEY, STRING_TYPE);
800 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
801 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
804 private static String processBitsType(final BitsTypeDefinition bitsType,
805 final ObjectNode property) {
806 property.put(MIN_ITEMS, 0);
807 property.put(UNIQUE_ITEMS_KEY, true);
808 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
809 final Collection<? extends Bit> bits = bitsType.getBits();
810 for (final Bit bit : bits) {
811 enumNames.add(new TextNode(bit.getName()));
813 property.set(ENUM_KEY, enumNames);
814 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
818 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
819 final String nodeName) {
820 StringTypeDefinition type = (StringTypeDefinition) stringType;
821 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
822 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
823 type = type.getBaseType();
824 lengthConstraints = type.getLengthConstraint();
827 if (lengthConstraints.isPresent()) {
828 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
829 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
830 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
833 if (type.getPatternConstraints().iterator().hasNext()) {
834 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
835 String regex = pattern.getJavaPatternString();
836 regex = regex.substring(1, regex.length() - 1);
837 final Generex generex = new Generex(regex);
838 setDefaultValue(property, generex.random());
840 setDefaultValue(property, "Some " + nodeName);
845 private String processNumberType(final RangeRestrictedTypeDefinition leafTypeDef, final ObjectNode property) {
846 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
847 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
849 if (isHexadecimalOrOctal(leafTypeDef)) {
853 if (leafTypeDef instanceof DecimalTypeDefinition) {
854 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
857 if (leafTypeDef instanceof Uint8TypeDefinition
858 || leafTypeDef instanceof Uint16TypeDefinition
859 || leafTypeDef instanceof Int8TypeDefinition
860 || leafTypeDef instanceof Int16TypeDefinition
861 || leafTypeDef instanceof Int32TypeDefinition) {
863 property.put(FORMAT_KEY, INT32_FORMAT);
864 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
865 } else if (leafTypeDef instanceof Uint32TypeDefinition
866 || leafTypeDef instanceof Int64TypeDefinition) {
868 property.put(FORMAT_KEY, INT64_FORMAT);
869 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
872 setDefaultValue(property, 0);
877 private boolean isHexadecimalOrOctal(RangeRestrictedTypeDefinition typeDef) {
878 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
879 if (optDefaultValue.isPresent()) {
880 final String defaultValue = (String)optDefaultValue.get();
881 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
886 private String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
887 final SchemaContext schemaContext) {
888 SchemaPath path = node.getPath();
890 while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
891 path = path.getParent();
894 final QName rootContainer = path.getLastComponent();
895 final String rootContainerName = rootContainer.getLocalName();
896 final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
897 setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
901 private String processUnionType(final UnionTypeDefinition unionType) {
902 boolean isStringTakePlace = false;
903 boolean isNumberTakePlace = false;
904 boolean isBooleanTakePlace = false;
905 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
906 if (!isStringTakePlace) {
907 if (typeDef instanceof StringTypeDefinition
908 || typeDef instanceof BitsTypeDefinition
909 || typeDef instanceof BinaryTypeDefinition
910 || typeDef instanceof IdentityrefTypeDefinition
911 || typeDef instanceof EnumTypeDefinition
912 || typeDef instanceof LeafrefTypeDefinition
913 || typeDef instanceof UnionTypeDefinition) {
914 isStringTakePlace = true;
915 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
916 isNumberTakePlace = true;
917 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
918 isBooleanTakePlace = true;
922 if (isStringTakePlace) {
925 if (isBooleanTakePlace) {
926 if (isNumberTakePlace) {
934 private static ObjectNode buildXmlParameter(SchemaNode node) {
935 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
936 final QName qName = node.getQName();
937 xml.put(NAME_KEY, qName.getLocalName());
938 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
942 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
943 if (key != null && number != null) {
944 if (number instanceof Double) {
945 property.put(key, (Double) number);
946 } else if (number instanceof Float) {
947 property.put(key, (Float) number);
948 } else if (number instanceof Integer) {
949 property.put(key, (Integer) number);
950 } else if (number instanceof Short) {
951 property.put(key, (Short) number);
952 } else if (number instanceof Long) {
953 property.put(key, (Long) number);
958 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
959 if (key != null && value != null) {
960 property.put(key, value);
964 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
965 if (required.size() > 0) {
966 node.set(REQUIRED_KEY, required);
970 private static void setDefaultValue(final ObjectNode property, final String value) {
971 property.put(DEFAULT_KEY, value);
974 private static void setDefaultValue(final ObjectNode property, final Integer value) {
975 property.put(DEFAULT_KEY, value);
978 private static void setDefaultValue(final ObjectNode property, final Long value) {
979 property.put(DEFAULT_KEY, value);
982 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
983 property.put(DEFAULT_KEY, value);
986 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
987 property.put(DEFAULT_KEY, value);