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.ContainerSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
46 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
48 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
53 import org.opendaylight.yangtools.yang.model.api.Module;
54 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
55 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
58 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
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.SchemaContextUtil;
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 SchemaContext 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 SchemaContext 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 SchemaContext 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 SchemaContext schemaContext,
228 final OAversion oaversion) throws IOException {
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 SchemaContext 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 SchemaContext 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 SchemaContext schemaContext, final OAversion oaversion) throws IOException {
267 final String operationName = operationDef.getQName().getLocalName();
268 processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
269 definitionNames, schemaContext, oaversion);
270 processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
271 definitionNames, schemaContext, oaversion);
274 private void processOperationInputOutput(final ContainerSchemaNode container, final String operationName,
275 final String parentName, final boolean isInput,
276 final ObjectNode definitions, final DefinitionNames definitionNames,
277 final SchemaContext schemaContext, final OAversion oaversion)
279 if (!container.getChildNodes().isEmpty()) {
280 final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
281 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
282 processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
283 false, schemaContext, oaversion);
285 childSchema.put(TYPE_KEY, OBJECT_TYPE);
286 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
287 xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
288 childSchema.set(XML_KEY, xml);
289 childSchema.put(TITLE_KEY, filename);
290 final String discriminator =
291 definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
292 definitions.set(filename + discriminator, childSchema);
294 processTopData(filename, discriminator, definitions, container, oaversion);
298 private ObjectNode processTopData(final String filename, final String discriminator, final ObjectNode definitions,
299 final SchemaNode schemaNode, final OAversion oaversion) {
300 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
301 final String name = filename + discriminator;
302 final String ref = getAppropriateModelPrefix(oaversion) + name;
303 final String topName = filename + TOP;
305 if (schemaNode instanceof ListSchemaNode) {
306 dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
307 final ObjectNode items = JsonNodeFactory.instance.objectNode();
308 items.put(REF_KEY, ref);
309 dataNodeProperties.set(ITEMS_KEY, items);
310 dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
313 Description can't be added, because nothing allowed alongside $ref.
314 allOf is not an option, because ServiceNow can't parse it.
316 dataNodeProperties.put(REF_KEY, ref);
319 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
321 Add module name prefix to property name, when needed, when ServiceNow can process colons,
322 use RestDocGenUtil#resolveNodesName for creating property name
324 properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
325 final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
326 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
327 finalChildSchema.set(PROPERTIES_KEY, properties);
328 finalChildSchema.put(TITLE_KEY, topName);
331 definitions.set(topName + discriminator, finalChildSchema);
333 return dataNodeProperties;
337 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
338 * @param module The module from which the identity stmt will be processed
339 * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
340 * @param definitionNames Store for definition names
342 private static void processIdentities(final Module module, final ObjectNode definitions,
343 final DefinitionNames definitionNames, final SchemaContext context) {
345 final String moduleName = module.getName();
346 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
347 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
349 for (final IdentitySchemaNode idNode : idNodes) {
350 final ObjectNode identityObj = buildIdentityObject(idNode, context);
351 final String idName = idNode.getQName().getLocalName();
352 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
353 final String name = idName + discriminator;
354 definitions.set(name, identityObj);
358 private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
359 final ArrayNode enumPayload, final SchemaContext context) {
360 for (final IdentitySchemaNode derivedId : derivedIds) {
361 enumPayload.add(derivedId.getQName().getLocalName());
362 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
366 private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
367 final ObjectNode definitions, final DefinitionNames definitionNames,
368 final boolean isConfig, final SchemaContext schemaContext,
369 final OAversion oaversion) throws IOException {
370 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
371 final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
372 final SchemaNode schemaNode = (SchemaNode) dataNode;
373 final String localName = schemaNode.getQName().getLocalName();
374 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
375 final String nameAsParent = parentName + "_" + localName;
376 final ObjectNode properties =
377 processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
378 definitionNames, isConfig, schemaContext, oaversion);
380 final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
381 final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
382 final String postXmlNodeName = postNodeName + XML_SUFFIX;
383 final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
385 final String description = schemaNode.getDescription().orElse("");
386 final String discriminator;
388 if (!definitionNames.isListedNode(schemaNode)) {
389 final List<String> names = List.of(parentNameConfigLocalName,
390 parentNameConfigLocalName + TOP,
395 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
397 discriminator = definitionNames.getDiscriminator(schemaNode);
401 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
402 String truePostNodeName = postNodeName + discriminator;
403 definitions.set(truePostNodeName, postSchema);
405 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
406 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
407 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
410 childSchema.put(TYPE_KEY, OBJECT_TYPE);
411 childSchema.set(PROPERTIES_KEY, properties);
412 childSchema.put(TITLE_KEY, nodeName);
413 childSchema.put(DESCRIPTION_KEY, description);
415 final String defName = nodeName + discriminator;
416 childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
417 definitions.set(defName, childSchema);
419 return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
424 private ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
425 final String postNodeName, final String description) {
426 final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
427 final ObjectNode postItemProperties;
428 if (dataNode instanceof ListSchemaNode) {
429 postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
431 postItemProperties = properties.deepCopy();
433 postSchema.put(TYPE_KEY, OBJECT_TYPE);
434 postSchema.set(PROPERTIES_KEY, postItemProperties);
435 postSchema.put(TITLE_KEY, postNodeName);
436 postSchema.put(DESCRIPTION_KEY, description);
437 postSchema.set(XML_KEY, buildXmlParameter(dataNode));
441 private ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
442 final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
443 final List<QName> keyDefinition = listNode.getKeyDefinition();
444 final Set<String> keys = listNode.getChildNodes().stream()
445 .filter(node -> keyDefinition.contains(node.getQName()))
446 .map(node -> node.getQName().getLocalName())
447 .collect(Collectors.toSet());
449 Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
450 while (it.hasNext()) {
451 Map.Entry<String, JsonNode> property = it.next();
452 if (!keys.contains(property.getKey())) {
453 postListItemProperties.set(property.getKey(), property.getValue());
457 return postListItemProperties;
461 * Processes the nodes.
463 private ObjectNode processChildren(
464 final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
465 final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
466 final SchemaContext schemaContext, final OAversion oaversion) throws IOException {
467 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
468 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
469 for (final DataSchemaNode node : nodes) {
470 if (!isConfig || node.isConfiguration()) {
472 Add module name prefix to property name, when needed, when ServiceNow can process colons,
473 use RestDocGenUtil#resolveNodesName for creating property name
475 final String propertyName = node.getQName().getLocalName();
476 final ObjectNode property;
477 if (node instanceof LeafSchemaNode) {
478 processLeafNode((LeafSchemaNode) node, propertyName, properties,
479 required, schemaContext, definitions, definitionNames, oaversion);
480 } else if (node instanceof AnyxmlSchemaNode) {
481 processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
483 } else if (node instanceof AnydataSchemaNode) {
484 processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
487 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
488 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
489 definitionNames, isConfig, schemaContext, oaversion);
491 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
494 } else if (node instanceof LeafListSchemaNode) {
495 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
496 definitionNames, oaversion);
498 } else if (node instanceof ChoiceSchemaNode) {
499 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
500 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
501 isConfig, schemaContext, properties, oaversion);
506 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
508 properties.set(propertyName, property);
512 parentNode.set(PROPERTIES_KEY, properties);
513 setRequiredIfNotEmpty(parentNode, required);
517 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final SchemaContext schemaContext,
518 final ObjectNode definitions, final DefinitionNames definitionNames,
519 final OAversion oaversion) {
520 final ObjectNode props = JsonNodeFactory.instance.objectNode();
521 props.put(TYPE_KEY, ARRAY_TYPE);
523 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
524 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
525 processElementCount(optConstraint, props);
527 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
528 props.set(ITEMS_KEY, itemsVal);
530 props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
535 private void processChoiceNode(
536 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
537 final DefinitionNames definitionNames, final boolean isConfig,
538 final SchemaContext schemaContext, final ObjectNode properties, final OAversion oaversion)
540 for (final DataSchemaNode node : nodes) {
542 Add module name prefix to property name, when needed, when ServiceNow can process colons,
543 use RestDocGenUtil#resolveNodesName for creating property name
545 final String name = node.getQName().getLocalName();
546 final ObjectNode property;
549 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
552 if (node instanceof LeafSchemaNode) {
553 processLeafNode((LeafSchemaNode) node, name, properties,
554 JsonNodeFactory.instance.arrayNode(), schemaContext, definitions, definitionNames, oaversion);
555 } else if (node instanceof AnyxmlSchemaNode) {
556 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
557 JsonNodeFactory.instance.arrayNode());
558 } else if (node instanceof AnydataSchemaNode) {
559 processAnydataNode((AnydataSchemaNode) node, name, properties,
560 JsonNodeFactory.instance.arrayNode());
562 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
563 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
564 definitionNames, isConfig, schemaContext, oaversion);
566 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
569 } else if (node instanceof LeafListSchemaNode) {
570 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
571 definitionNames, oaversion);
573 } else if (node instanceof ChoiceSchemaNode) {
574 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
575 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
576 schemaContext, properties, oaversion);
580 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
582 properties.set(name, property);
587 private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
588 if (constraint.isPresent()) {
589 final ElementCountConstraint constr = constraint.get();
590 final Integer minElements = constr.getMinElements();
591 if (minElements != null) {
592 props.put(MIN_ITEMS, minElements);
594 final Integer maxElements = constr.getMaxElements();
595 if (maxElements != null) {
596 props.put(MAX_ITEMS, maxElements);
601 private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
602 if (node.isMandatory()) {
603 required.add(nodeName);
607 private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
608 final ObjectNode properties, final ArrayNode required,
609 final SchemaContext schemaContext, final ObjectNode definitions,
610 final DefinitionNames definitionNames, final OAversion oaversion) {
611 final ObjectNode property = JsonNodeFactory.instance.objectNode();
613 final String leafDescription = leafNode.getDescription().orElse("");
615 Description can't be added, because nothing allowed alongside $ref.
616 allOf is not an option, because ServiceNow can't parse it.
618 if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
619 property.put(DESCRIPTION_KEY, leafDescription);
622 processTypeDef(leafNode.getType(), leafNode, property, schemaContext, definitions, definitionNames, oaversion);
623 properties.set(jsonLeafName, property);
624 property.set(XML_KEY, buildXmlParameter(leafNode));
625 processMandatory(leafNode, jsonLeafName, required);
630 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
631 final ObjectNode properties, final ArrayNode required) {
632 final ObjectNode property = JsonNodeFactory.instance.objectNode();
634 final String leafDescription = leafNode.getDescription().orElse("");
635 property.put(DESCRIPTION_KEY, leafDescription);
637 final String localName = leafNode.getQName().getLocalName();
638 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
639 property.put(TYPE_KEY, STRING_TYPE);
640 property.set(XML_KEY, buildXmlParameter(leafNode));
641 processMandatory(leafNode, name, required);
642 properties.set(name, property);
647 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
648 final ObjectNode properties, final ArrayNode required) {
649 final ObjectNode property = JsonNodeFactory.instance.objectNode();
651 final String leafDescription = leafNode.getDescription().orElse("");
652 property.put(DESCRIPTION_KEY, leafDescription);
654 final String localName = leafNode.getQName().getLocalName();
655 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
656 property.put(TYPE_KEY, STRING_TYPE);
657 property.set(XML_KEY, buildXmlParameter(leafNode));
658 processMandatory(leafNode, name, required);
659 properties.set(name, property);
664 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
665 final ObjectNode property, final SchemaContext schemaContext,
666 final ObjectNode definitions, final DefinitionNames definitionNames,
667 final OAversion oaversion) {
668 final String jsonType;
669 if (leafTypeDef instanceof BinaryTypeDefinition) {
670 jsonType = processBinaryType(property);
672 } else if (leafTypeDef instanceof BitsTypeDefinition) {
673 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
675 } else if (leafTypeDef instanceof EnumTypeDefinition) {
676 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
678 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
679 jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
680 definitionNames, oaversion, schemaContext);
682 } else if (leafTypeDef instanceof StringTypeDefinition) {
683 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
685 } else if (leafTypeDef instanceof UnionTypeDefinition) {
686 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
688 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
689 jsonType = OBJECT_TYPE;
690 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
691 return processTypeDef(SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) leafTypeDef,
692 schemaContext, node), node, property, schemaContext, definitions, definitionNames, oaversion);
693 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
694 jsonType = BOOLEAN_TYPE;
695 setDefaultValue(property, true);
696 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
697 jsonType = processNumberType((RangeRestrictedTypeDefinition) leafTypeDef, property);
698 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
699 jsonType = processInstanceIdentifierType(node, property, schemaContext);
701 jsonType = STRING_TYPE;
703 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
704 putIfNonNull(property, TYPE_KEY, jsonType);
705 if (leafTypeDef.getDefaultValue().isPresent()) {
706 final Object defaultValue = leafTypeDef.getDefaultValue().get();
707 if (defaultValue instanceof String) {
708 final String stringDefaultValue = (String) defaultValue;
709 if (leafTypeDef instanceof BooleanTypeDefinition) {
710 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
711 } else if (leafTypeDef instanceof DecimalTypeDefinition
712 || leafTypeDef instanceof Uint64TypeDefinition) {
713 setDefaultValue(property, new BigDecimal(stringDefaultValue));
714 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
715 //uint8,16,32 int8,16,32,64
716 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition)leafTypeDef)) {
717 setDefaultValue(property, stringDefaultValue);
719 setDefaultValue(property, Long.valueOf(stringDefaultValue));
722 setDefaultValue(property, stringDefaultValue);
725 //we should never get here. getDefaultValue always gives us string
726 setDefaultValue(property, defaultValue.toString());
733 private static String processBinaryType(final ObjectNode property) {
734 property.put(FORMAT_KEY, "byte");
738 private static String processEnumType(final EnumTypeDefinition enumLeafType,
739 final ObjectNode property) {
740 final List<EnumPair> enumPairs = enumLeafType.getValues();
741 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
742 for (final EnumPair enumPair : enumPairs) {
743 enumNames.add(new TextNode(enumPair.getName()));
746 property.set(ENUM_KEY, enumNames);
747 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
751 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
752 final ObjectNode definitions, final DefinitionNames definitionNames,
753 final OAversion oaversion, final SchemaContext schemaContext) {
754 final String definitionName;
755 if (isImported(leafTypeDef)) {
756 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
758 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
759 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
761 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
765 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
766 final ObjectNode definitions, final DefinitionNames definitionNames,
767 final SchemaContext context) {
768 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
769 final String identityName = idNode.getQName().getLocalName();
770 if (!definitionNames.isListedNode(idNode)) {
771 final ObjectNode identityObj = buildIdentityObject(idNode, context);
772 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
773 final String name = identityName + discriminator;
774 definitions.set(name, identityObj);
777 return identityName + definitionNames.getDiscriminator(idNode);
781 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode, final SchemaContext context) {
782 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
783 final String identityName = idNode.getQName().getLocalName();
784 LOG.debug("Processing Identity: {}", identityName);
786 identityObj.put(TITLE_KEY, identityName);
787 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
789 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
791 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
792 enumPayload.add(identityName);
793 populateEnumWithDerived(derivedIds, enumPayload, context);
794 identityObj.set(ENUM_KEY, enumPayload);
795 identityObj.put(TYPE_KEY, STRING_TYPE);
799 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
800 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
803 private static String processBitsType(final BitsTypeDefinition bitsType,
804 final ObjectNode property) {
805 property.put(MIN_ITEMS, 0);
806 property.put(UNIQUE_ITEMS_KEY, true);
807 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
808 final Collection<? extends Bit> bits = bitsType.getBits();
809 for (final Bit bit : bits) {
810 enumNames.add(new TextNode(bit.getName()));
812 property.set(ENUM_KEY, enumNames);
813 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
817 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
818 final String nodeName) {
819 StringTypeDefinition type = (StringTypeDefinition) stringType;
820 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
821 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
822 type = type.getBaseType();
823 lengthConstraints = type.getLengthConstraint();
826 if (lengthConstraints.isPresent()) {
827 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
828 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
829 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
832 if (type.getPatternConstraints().iterator().hasNext()) {
833 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
834 String regex = pattern.getJavaPatternString();
835 regex = regex.substring(1, regex.length() - 1);
836 final Generex generex = new Generex(regex);
837 setDefaultValue(property, generex.random());
839 setDefaultValue(property, "Some " + nodeName);
844 private String processNumberType(final RangeRestrictedTypeDefinition leafTypeDef, final ObjectNode property) {
845 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
846 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
848 if (isHexadecimalOrOctal(leafTypeDef)) {
852 if (leafTypeDef instanceof DecimalTypeDefinition) {
853 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
856 if (leafTypeDef instanceof Uint8TypeDefinition
857 || leafTypeDef instanceof Uint16TypeDefinition
858 || leafTypeDef instanceof Int8TypeDefinition
859 || leafTypeDef instanceof Int16TypeDefinition
860 || leafTypeDef instanceof Int32TypeDefinition) {
862 property.put(FORMAT_KEY, INT32_FORMAT);
863 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
864 } else if (leafTypeDef instanceof Uint32TypeDefinition
865 || leafTypeDef instanceof Int64TypeDefinition) {
867 property.put(FORMAT_KEY, INT64_FORMAT);
868 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
871 setDefaultValue(property, 0);
876 private boolean isHexadecimalOrOctal(RangeRestrictedTypeDefinition typeDef) {
877 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
878 if (optDefaultValue.isPresent()) {
879 final String defaultValue = ((String)optDefaultValue.get());
880 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
885 private String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
886 final SchemaContext schemaContext) {
887 SchemaPath path = node.getPath();
889 while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
890 path = path.getParent();
893 final QName rootContainer = path.getLastComponent();
894 final String rootContainerName = rootContainer.getLocalName();
895 final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
896 setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
900 private String processUnionType(final UnionTypeDefinition unionType) {
901 boolean isStringTakePlace = false;
902 boolean isNumberTakePlace = false;
903 boolean isBooleanTakePlace = false;
904 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
905 if (!isStringTakePlace) {
906 if (typeDef instanceof StringTypeDefinition
907 || typeDef instanceof BitsTypeDefinition
908 || typeDef instanceof BinaryTypeDefinition
909 || typeDef instanceof IdentityrefTypeDefinition
910 || typeDef instanceof EnumTypeDefinition
911 || typeDef instanceof LeafrefTypeDefinition
912 || typeDef instanceof UnionTypeDefinition) {
913 isStringTakePlace = true;
914 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
915 isNumberTakePlace = true;
916 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
917 isBooleanTakePlace = true;
921 if (isStringTakePlace) {
924 if (isBooleanTakePlace) {
925 if (isNumberTakePlace) {
933 private static ObjectNode buildXmlParameter(SchemaNode node) {
934 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
935 final QName qName = node.getQName();
936 xml.put(NAME_KEY, qName.getLocalName());
937 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
941 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
942 if (key != null && number != null) {
943 if (number instanceof Double) {
944 property.put(key, (Double) number);
945 } else if (number instanceof Float) {
946 property.put(key, (Float) number);
947 } else if (number instanceof Integer) {
948 property.put(key, (Integer) number);
949 } else if (number instanceof Short) {
950 property.put(key, (Short) number);
951 } else if (number instanceof Long) {
952 property.put(key, (Long) number);
957 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
958 if (key != null && value != null) {
959 property.put(key, value);
963 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
964 if (required.size() > 0) {
965 node.set(REQUIRED_KEY, required);
969 private static void setDefaultValue(final ObjectNode property, final String value) {
970 property.put(DEFAULT_KEY, value);
973 private static void setDefaultValue(final ObjectNode property, final Integer value) {
974 property.put(DEFAULT_KEY, value);
977 private static void setDefaultValue(final ObjectNode property, final Long value) {
978 property.put(DEFAULT_KEY, value);
981 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
982 property.put(DEFAULT_KEY, value);
985 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
986 property.put(DEFAULT_KEY, value);