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 setDefaultValue(property, Long.valueOf(stringDefaultValue));
718 setDefaultValue(property, stringDefaultValue);
721 //we should never get here. getDefaultValue always gives us string
722 setDefaultValue(property, defaultValue.toString());
729 private static String processBinaryType(final ObjectNode property) {
730 property.put(FORMAT_KEY, "byte");
734 private static String processEnumType(final EnumTypeDefinition enumLeafType,
735 final ObjectNode property) {
736 final List<EnumPair> enumPairs = enumLeafType.getValues();
737 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
738 for (final EnumPair enumPair : enumPairs) {
739 enumNames.add(new TextNode(enumPair.getName()));
742 property.set(ENUM_KEY, enumNames);
743 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
747 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
748 final ObjectNode definitions, final DefinitionNames definitionNames,
749 final OAversion oaversion, final SchemaContext schemaContext) {
750 final String definitionName;
751 if (isImported(leafTypeDef)) {
752 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
754 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
755 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
757 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
761 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
762 final ObjectNode definitions, final DefinitionNames definitionNames,
763 final SchemaContext context) {
764 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
765 final String identityName = idNode.getQName().getLocalName();
766 if (!definitionNames.isListedNode(idNode)) {
767 final ObjectNode identityObj = buildIdentityObject(idNode, context);
768 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
769 final String name = identityName + discriminator;
770 definitions.set(name, identityObj);
773 return identityName + definitionNames.getDiscriminator(idNode);
777 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode, final SchemaContext context) {
778 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
779 final String identityName = idNode.getQName().getLocalName();
780 LOG.debug("Processing Identity: {}", identityName);
782 identityObj.put(TITLE_KEY, identityName);
783 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
785 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
787 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
788 enumPayload.add(identityName);
789 populateEnumWithDerived(derivedIds, enumPayload, context);
790 identityObj.set(ENUM_KEY, enumPayload);
791 identityObj.put(TYPE_KEY, STRING_TYPE);
795 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
796 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
799 private static String processBitsType(final BitsTypeDefinition bitsType,
800 final ObjectNode property) {
801 property.put(MIN_ITEMS, 0);
802 property.put(UNIQUE_ITEMS_KEY, true);
803 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
804 final Collection<? extends Bit> bits = bitsType.getBits();
805 for (final Bit bit : bits) {
806 enumNames.add(new TextNode(bit.getName()));
808 property.set(ENUM_KEY, enumNames);
809 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
813 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
814 final String nodeName) {
815 StringTypeDefinition type = (StringTypeDefinition) stringType;
816 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
817 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
818 type = type.getBaseType();
819 lengthConstraints = type.getLengthConstraint();
822 if (lengthConstraints.isPresent()) {
823 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
824 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
825 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
828 if (type.getPatternConstraints().iterator().hasNext()) {
829 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
830 String regex = pattern.getJavaPatternString();
831 regex = regex.substring(1, regex.length() - 1);
832 final Generex generex = new Generex(regex);
833 setDefaultValue(property, generex.random());
835 setDefaultValue(property, "Some " + nodeName);
840 private String processNumberType(final RangeRestrictedTypeDefinition leafTypeDef, final ObjectNode property) {
841 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
842 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
844 if (leafTypeDef instanceof DecimalTypeDefinition) {
845 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
848 if (leafTypeDef instanceof Uint8TypeDefinition
849 || leafTypeDef instanceof Uint16TypeDefinition
850 || leafTypeDef instanceof Int8TypeDefinition
851 || leafTypeDef instanceof Int16TypeDefinition
852 || leafTypeDef instanceof Int32TypeDefinition) {
854 property.put(FORMAT_KEY, INT32_FORMAT);
855 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
856 } else if (leafTypeDef instanceof Uint32TypeDefinition
857 || leafTypeDef instanceof Int64TypeDefinition) {
859 property.put(FORMAT_KEY, INT64_FORMAT);
860 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
863 setDefaultValue(property, 0);
868 private String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
869 final SchemaContext schemaContext) {
870 SchemaPath path = node.getPath();
872 while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
873 path = path.getParent();
876 final QName rootContainer = path.getLastComponent();
877 final String rootContainerName = rootContainer.getLocalName();
878 final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
879 setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
883 private String processUnionType(final UnionTypeDefinition unionType) {
884 boolean isStringTakePlace = false;
885 boolean isNumberTakePlace = false;
886 boolean isBooleanTakePlace = false;
887 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
888 if (!isStringTakePlace) {
889 if (typeDef instanceof StringTypeDefinition
890 || typeDef instanceof BitsTypeDefinition
891 || typeDef instanceof BinaryTypeDefinition
892 || typeDef instanceof IdentityrefTypeDefinition
893 || typeDef instanceof EnumTypeDefinition
894 || typeDef instanceof LeafrefTypeDefinition
895 || typeDef instanceof UnionTypeDefinition) {
896 isStringTakePlace = true;
897 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
898 isNumberTakePlace = true;
899 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
900 isBooleanTakePlace = true;
904 if (isStringTakePlace) {
907 if (isBooleanTakePlace) {
908 if (isNumberTakePlace) {
916 private static ObjectNode buildXmlParameter(SchemaNode node) {
917 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
918 final QName qName = node.getQName();
919 xml.put(NAME_KEY, qName.getLocalName());
920 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
924 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
925 if (key != null && number != null) {
926 if (number instanceof Double) {
927 property.put(key, (Double) number);
928 } else if (number instanceof Float) {
929 property.put(key, (Float) number);
930 } else if (number instanceof Integer) {
931 property.put(key, (Integer) number);
932 } else if (number instanceof Short) {
933 property.put(key, (Short) number);
934 } else if (number instanceof Long) {
935 property.put(key, (Long) number);
940 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
941 if (key != null && value != null) {
942 property.put(key, value);
946 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
947 if (required.size() > 0) {
948 node.set(REQUIRED_KEY, required);
952 private static void setDefaultValue(final ObjectNode property, final String value) {
953 property.put(DEFAULT_KEY, value);
956 private static void setDefaultValue(final ObjectNode property, final Integer value) {
957 property.put(DEFAULT_KEY, value);
960 private static void setDefaultValue(final ObjectNode property, final Long value) {
961 property.put(DEFAULT_KEY, value);
964 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
965 property.put(DEFAULT_KEY, value);
968 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
969 property.put(DEFAULT_KEY, value);