Bump upstreams for Silicon
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / impl / DefinitionGenerator.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.sal.rest.doc.impl;
9
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;
18
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;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.Set;
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;
89
90 /**
91  * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
92  */
93 public class DefinitionGenerator {
94
95     private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
96
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";
125
126     private Module topLevelModule;
127
128     public DefinitionGenerator() {
129     }
130
131     /**
132      * Creates Json definitions from provided module according to swagger spec.
133      *
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
139      */
140
141
142     public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
143                                           final ObjectNode definitions, final DefinitionNames definitionNames,
144                                           final OAversion oaversion, final boolean isForSingleModule)
145             throws IOException {
146         topLevelModule = module;
147
148         processIdentities(module, definitions, definitionNames, schemaContext);
149         processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
150         processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
151
152         if (isForSingleModule) {
153             processModule(module, definitions, definitionNames, schemaContext, oaversion);
154         }
155
156         return definitions;
157     }
158
159     public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
160                                           final DefinitionNames definitionNames, final OAversion oaversion,
161                                           final boolean isForSingleModule)
162             throws IOException {
163         final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
164         if (isForSingleModule) {
165             definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
166         }
167         return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
168     }
169
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();
183
184                         final String ref = getAppropriateModelPrefix(oaversion)
185                                 + moduleName + CONFIG
186                                 + "_" + localName
187                                 + definitionNames.getDiscriminator(node);
188
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);
196                         } else {
197                          /*
198                             Description can't be added, because nothing allowed alongside $ref.
199                             allOf is not an option, because ServiceNow can't parse it.
200                           */
201                             childNodeProperties.put(REF_KEY, ref);
202                         }
203                         //add module name prefix to property name, when ServiceNow can process colons
204                         properties.set(localName, childNodeProperties);
205                     }
206                 } else {
207                     if (node instanceof LeafSchemaNode) {
208                         /*
209                             Add module name prefix to property name, when ServiceNow can process colons(second parameter
210                             of processLeafNode).
211                          */
212                         processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
213                                 definitions, definitionNames, oaversion);
214                     }
215                 }
216             }
217         }
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);
223
224         definitions.set(definitionName, definition);
225     }
226
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();
231
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);
238                 }
239                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
240                         false, schemaContext, oaversion);
241                 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
242                         oaversion);
243             }
244         }
245     }
246
247     private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
248                                             final ObjectNode definitions, final DefinitionNames definitionNames,
249                                             final SchemaContext schemaContext, final OAversion oaversion)
250             throws IOException {
251         for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
252             processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
253         }
254     }
255
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);
261         }
262     }
263
264
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);
273     }
274
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)
279             throws IOException {
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);
285
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);
294
295             processTopData(filename, discriminator, definitions, container, oaversion);
296         }
297     }
298
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;
305
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(""));
312         } else {
313              /*
314                 Description can't be added, because nothing allowed alongside $ref.
315                 allOf is not an option, because ServiceNow can't parse it.
316               */
317             dataNodeProperties.put(REF_KEY, ref);
318         }
319
320         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
321         /*
322             Add module name prefix to property name, when needed, when ServiceNow can process colons,
323             use RestDocGenUtil#resolveNodesName for creating property name
324          */
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);
330
331
332         definitions.set(topName + discriminator, finalChildSchema);
333
334         return dataNodeProperties;
335     }
336
337     /**
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
342      */
343     private static void processIdentities(final Module module, final ObjectNode definitions,
344                                           final DefinitionNames definitionNames, final SchemaContext context) {
345
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());
349
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);
356         }
357     }
358
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);
364         }
365     }
366
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);
380
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;
385
386             final String description = schemaNode.getDescription().orElse("");
387             final String discriminator;
388
389             if (!definitionNames.isListedNode(schemaNode)) {
390                 final List<String> names = List.of(parentNameConfigLocalName,
391                         parentNameConfigLocalName + TOP,
392                         nameAsParent,
393                         nameAsParent + TOP,
394                         postNodeName,
395                         postXmlNodeName);
396                 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
397             } else {
398                 discriminator = definitionNames.getDiscriminator(schemaNode);
399             }
400
401             if (isConfig) {
402                 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
403                 String truePostNodeName = postNodeName + discriminator;
404                 definitions.set(truePostNodeName, postSchema);
405
406                 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
407                 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
408                 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
409             }
410
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);
415
416             final String defName = nodeName + discriminator;
417             childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
418             definitions.set(defName, childSchema);
419
420             return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
421         }
422         return null;
423     }
424
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);
431         } else {
432             postItemProperties = properties.deepCopy();
433         }
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));
439         return postSchema;
440     }
441
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());
449
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());
455             }
456         }
457
458         return postListItemProperties;
459     }
460
461     /**
462      * Processes the nodes.
463      */
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()) {
472                 /*
473                     Add module name prefix to property name, when needed, when ServiceNow can process colons,
474                     use RestDocGenUtil#resolveNodesName for creating property name
475                  */
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,
483                             required);
484                 } else if (node instanceof AnydataSchemaNode) {
485                     processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
486                             required);
487                 } else {
488                     if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
489                         property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
490                                 definitionNames, isConfig, schemaContext, oaversion);
491                         if (!isConfig) {
492                             processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
493                                     oaversion);
494                         }
495                     } else if (node instanceof LeafListSchemaNode) {
496                         property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
497                                 definitionNames, oaversion);
498
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);
503                         }
504                         continue;
505
506                     } else {
507                         throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
508                     }
509                     properties.set(propertyName, property);
510                 }
511             }
512         }
513         parentNode.set(PROPERTIES_KEY, properties);
514         setRequiredIfNotEmpty(parentNode, required);
515         return properties;
516     }
517
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);
523
524         final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
525         final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
526         processElementCount(optConstraint, props);
527
528         processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
529         props.set(ITEMS_KEY, itemsVal);
530
531         props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
532
533         return props;
534     }
535
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)
540             throws IOException {
541         for (final DataSchemaNode node : nodes) {
542             /*
543                 Add module name prefix to property name, when needed, when ServiceNow can process colons,
544                 use RestDocGenUtil#resolveNodesName for creating property name
545              */
546             final String name = node.getQName().getLocalName();
547             final ObjectNode property;
548
549             /*
550                 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
551                 properties
552              */
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());
562             } else {
563                 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
564                     property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
565                             definitionNames, isConfig, schemaContext, oaversion);
566                     if (!isConfig) {
567                         processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
568                                 oaversion);
569                     }
570                 } else if (node instanceof LeafListSchemaNode) {
571                     property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
572                             definitionNames, oaversion);
573
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);
578                     }
579                     continue;
580                 } else {
581                     throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
582                 }
583                 properties.set(name, property);
584             }
585         }
586     }
587
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);
594             }
595             final Integer maxElements = constr.getMaxElements();
596             if (maxElements != null) {
597                 props.put(MAX_ITEMS, maxElements);
598             }
599         }
600     }
601
602     private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
603         if (node.isMandatory()) {
604             required.add(nodeName);
605         }
606     }
607
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();
613
614         final String leafDescription = leafNode.getDescription().orElse("");
615         /*
616             Description can't be added, because nothing allowed alongside $ref.
617             allOf is not an option, because ServiceNow can't parse it.
618         */
619         if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
620             property.put(DESCRIPTION_KEY, leafDescription);
621         }
622
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);
627
628         return property;
629     }
630
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();
634
635         final String leafDescription = leafNode.getDescription().orElse("");
636         property.put(DESCRIPTION_KEY, leafDescription);
637
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);
644
645         return property;
646     }
647
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();
651
652         final String leafDescription = leafNode.getDescription().orElse("");
653         property.put(DESCRIPTION_KEY, leafDescription);
654
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);
661
662         return property;
663     }
664
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);
672
673         } else if (leafTypeDef instanceof BitsTypeDefinition) {
674             jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
675
676         } else if (leafTypeDef instanceof EnumTypeDefinition) {
677             jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
678
679         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
680             jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
681                     definitionNames, oaversion, schemaContext);
682
683         } else if (leafTypeDef instanceof StringTypeDefinition) {
684             jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
685
686         } else if (leafTypeDef instanceof UnionTypeDefinition) {
687             jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
688
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);
701         } else {
702             jsonType = STRING_TYPE;
703         }
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);
719                         } else {
720                             setDefaultValue(property, Long.valueOf(stringDefaultValue));
721                         }
722                     } else {
723                         setDefaultValue(property, stringDefaultValue);
724                     }
725                 } else {
726                     //we should never get here. getDefaultValue always gives us string
727                     setDefaultValue(property, defaultValue.toString());
728                 }
729             }
730         }
731         return jsonType;
732     }
733
734     private static String processBinaryType(final ObjectNode property) {
735         property.put(FORMAT_KEY, "byte");
736         return STRING_TYPE;
737     }
738
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()));
745         }
746
747         property.set(ENUM_KEY, enumNames);
748         setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
749         return STRING_TYPE;
750     }
751
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);
758         } else {
759             final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
760             definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
761         }
762         property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
763         return STRING_TYPE;
764     }
765
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);
776             return name;
777         } else {
778             return identityName + definitionNames.getDiscriminator(idNode);
779         }
780     }
781
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);
786
787         identityObj.put(TITLE_KEY, identityName);
788         identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
789
790         final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
791
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);
797         return identityObj;
798     }
799
800     private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
801         return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
802     }
803
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()));
812         }
813         property.set(ENUM_KEY, enumNames);
814         property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
815         return STRING_TYPE;
816     }
817
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();
825         }
826
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());
831         }
832
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());
839         } else {
840             setDefaultValue(property, "Some " + nodeName);
841         }
842         return STRING_TYPE;
843     }
844
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);
848
849         if (isHexadecimalOrOctal(leafTypeDef)) {
850             return STRING_TYPE;
851         }
852
853         if (leafTypeDef instanceof DecimalTypeDefinition) {
854             maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
855             return NUMBER_TYPE;
856         }
857         if (leafTypeDef instanceof Uint8TypeDefinition
858                 || leafTypeDef instanceof Uint16TypeDefinition
859                 || leafTypeDef instanceof Int8TypeDefinition
860                 || leafTypeDef instanceof Int16TypeDefinition
861                 || leafTypeDef instanceof Int32TypeDefinition) {
862
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) {
867
868             property.put(FORMAT_KEY, INT64_FORMAT);
869             maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
870         } else {
871             //uint64
872             setDefaultValue(property, 0);
873         }
874         return INTEGER_TYPE;
875     }
876
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");
882         }
883         return false;
884     }
885
886     private String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
887                                                  final SchemaContext schemaContext) {
888         SchemaPath path = node.getPath();
889
890         while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
891             path = path.getParent();
892         }
893
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));
898         return STRING_TYPE;
899     }
900
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;
919                 }
920             }
921         }
922         if (isStringTakePlace) {
923             return STRING_TYPE;
924         }
925         if (isBooleanTakePlace) {
926             if (isNumberTakePlace) {
927                 return STRING_TYPE;
928             }
929             return BOOLEAN_TYPE;
930         }
931         return NUMBER_TYPE;
932     }
933
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());
939         return xml;
940     }
941
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);
954             }
955         }
956     }
957
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);
961         }
962     }
963
964     private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
965         if (required.size() > 0) {
966             node.set(REQUIRED_KEY, required);
967         }
968     }
969
970     private static void setDefaultValue(final ObjectNode property, final String value) {
971         property.put(DEFAULT_KEY, value);
972     }
973
974     private static void setDefaultValue(final ObjectNode property, final Integer value) {
975         property.put(DEFAULT_KEY, value);
976     }
977
978     private static void setDefaultValue(final ObjectNode property, final Long value) {
979         property.put(DEFAULT_KEY, value);
980     }
981
982     private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
983         property.put(DEFAULT_KEY, value);
984     }
985
986     private static void setDefaultValue(final ObjectNode property, final Boolean value) {
987         property.put(DEFAULT_KEY, value);
988     }
989
990 }