Fix processing of hexadecimal, octal values
[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.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;
88
89 /**
90  * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
91  */
92 public class DefinitionGenerator {
93
94     private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
95
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";
124
125     private Module topLevelModule;
126
127     public DefinitionGenerator() {
128     }
129
130     /**
131      * Creates Json definitions from provided module according to swagger spec.
132      *
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
138      */
139
140
141     public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
142                                           final ObjectNode definitions, final DefinitionNames definitionNames,
143                                           final OAversion oaversion, final boolean isForSingleModule)
144             throws IOException {
145         topLevelModule = module;
146
147         processIdentities(module, definitions, definitionNames, schemaContext);
148         processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
149         processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
150
151         if (isForSingleModule) {
152             processModule(module, definitions, definitionNames, schemaContext, oaversion);
153         }
154
155         return definitions;
156     }
157
158     public ObjectNode convertToJsonSchema(final Module module, final SchemaContext schemaContext,
159                                           final DefinitionNames definitionNames, final OAversion oaversion,
160                                           final boolean isForSingleModule)
161             throws IOException {
162         final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
163         if (isForSingleModule) {
164             definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
165         }
166         return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
167     }
168
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();
182
183                         final String ref = getAppropriateModelPrefix(oaversion)
184                                 + moduleName + CONFIG
185                                 + "_" + localName
186                                 + definitionNames.getDiscriminator(node);
187
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);
195                         } else {
196                          /*
197                             Description can't be added, because nothing allowed alongside $ref.
198                             allOf is not an option, because ServiceNow can't parse it.
199                           */
200                             childNodeProperties.put(REF_KEY, ref);
201                         }
202                         //add module name prefix to property name, when ServiceNow can process colons
203                         properties.set(localName, childNodeProperties);
204                     }
205                 } else {
206                     if (node instanceof LeafSchemaNode) {
207                         /*
208                             Add module name prefix to property name, when ServiceNow can process colons(second parameter
209                             of processLeafNode).
210                          */
211                         processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
212                                 definitions, definitionNames, oaversion);
213                     }
214                 }
215             }
216         }
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);
222
223         definitions.set(definitionName, definition);
224     }
225
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();
230
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);
237                 }
238                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
239                         false, schemaContext, oaversion);
240                 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
241                         oaversion);
242             }
243         }
244     }
245
246     private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
247                                             final ObjectNode definitions, final DefinitionNames definitionNames,
248                                             final SchemaContext schemaContext, final OAversion oaversion)
249             throws IOException {
250         for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
251             processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
252         }
253     }
254
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);
260         }
261     }
262
263
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);
272     }
273
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)
278             throws IOException {
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);
284
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);
293
294             processTopData(filename, discriminator, definitions, container, oaversion);
295         }
296     }
297
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;
304
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(""));
311         } else {
312              /*
313                 Description can't be added, because nothing allowed alongside $ref.
314                 allOf is not an option, because ServiceNow can't parse it.
315               */
316             dataNodeProperties.put(REF_KEY, ref);
317         }
318
319         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
320         /*
321             Add module name prefix to property name, when needed, when ServiceNow can process colons,
322             use RestDocGenUtil#resolveNodesName for creating property name
323          */
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);
329
330
331         definitions.set(topName + discriminator, finalChildSchema);
332
333         return dataNodeProperties;
334     }
335
336     /**
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
341      */
342     private static void processIdentities(final Module module, final ObjectNode definitions,
343                                           final DefinitionNames definitionNames, final SchemaContext context) {
344
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());
348
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);
355         }
356     }
357
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);
363         }
364     }
365
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);
379
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;
384
385             final String description = schemaNode.getDescription().orElse("");
386             final String discriminator;
387
388             if (!definitionNames.isListedNode(schemaNode)) {
389                 final List<String> names = List.of(parentNameConfigLocalName,
390                         parentNameConfigLocalName + TOP,
391                         nameAsParent,
392                         nameAsParent + TOP,
393                         postNodeName,
394                         postXmlNodeName);
395                 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
396             } else {
397                 discriminator = definitionNames.getDiscriminator(schemaNode);
398             }
399
400             if (isConfig) {
401                 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
402                 String truePostNodeName = postNodeName + discriminator;
403                 definitions.set(truePostNodeName, postSchema);
404
405                 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
406                 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
407                 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
408             }
409
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);
414
415             final String defName = nodeName + discriminator;
416             childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
417             definitions.set(defName, childSchema);
418
419             return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
420         }
421         return null;
422     }
423
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);
430         } else {
431             postItemProperties = properties.deepCopy();
432         }
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));
438         return postSchema;
439     }
440
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());
448
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());
454             }
455         }
456
457         return postListItemProperties;
458     }
459
460     /**
461      * Processes the nodes.
462      */
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()) {
471                 /*
472                     Add module name prefix to property name, when needed, when ServiceNow can process colons,
473                     use RestDocGenUtil#resolveNodesName for creating property name
474                  */
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,
482                             required);
483                 } else if (node instanceof AnydataSchemaNode) {
484                     processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
485                             required);
486                 } else {
487                     if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
488                         property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
489                                 definitionNames, isConfig, schemaContext, oaversion);
490                         if (!isConfig) {
491                             processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
492                                     oaversion);
493                         }
494                     } else if (node instanceof LeafListSchemaNode) {
495                         property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
496                                 definitionNames, oaversion);
497
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);
502                         }
503                         continue;
504
505                     } else {
506                         throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
507                     }
508                     properties.set(propertyName, property);
509                 }
510             }
511         }
512         parentNode.set(PROPERTIES_KEY, properties);
513         setRequiredIfNotEmpty(parentNode, required);
514         return properties;
515     }
516
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);
522
523         final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
524         final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
525         processElementCount(optConstraint, props);
526
527         processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
528         props.set(ITEMS_KEY, itemsVal);
529
530         props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
531
532         return props;
533     }
534
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)
539             throws IOException {
540         for (final DataSchemaNode node : nodes) {
541             /*
542                 Add module name prefix to property name, when needed, when ServiceNow can process colons,
543                 use RestDocGenUtil#resolveNodesName for creating property name
544              */
545             final String name = node.getQName().getLocalName();
546             final ObjectNode property;
547
548             /*
549                 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
550                 properties
551              */
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());
561             } else {
562                 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
563                     property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
564                             definitionNames, isConfig, schemaContext, oaversion);
565                     if (!isConfig) {
566                         processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
567                                 oaversion);
568                     }
569                 } else if (node instanceof LeafListSchemaNode) {
570                     property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
571                             definitionNames, oaversion);
572
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);
577                     }
578                     continue;
579                 } else {
580                     throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
581                 }
582                 properties.set(name, property);
583             }
584         }
585     }
586
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);
593             }
594             final Integer maxElements = constr.getMaxElements();
595             if (maxElements != null) {
596                 props.put(MAX_ITEMS, maxElements);
597             }
598         }
599     }
600
601     private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
602         if (node.isMandatory()) {
603             required.add(nodeName);
604         }
605     }
606
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();
612
613         final String leafDescription = leafNode.getDescription().orElse("");
614         /*
615             Description can't be added, because nothing allowed alongside $ref.
616             allOf is not an option, because ServiceNow can't parse it.
617         */
618         if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
619             property.put(DESCRIPTION_KEY, leafDescription);
620         }
621
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);
626
627         return property;
628     }
629
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();
633
634         final String leafDescription = leafNode.getDescription().orElse("");
635         property.put(DESCRIPTION_KEY, leafDescription);
636
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);
643
644         return property;
645     }
646
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();
650
651         final String leafDescription = leafNode.getDescription().orElse("");
652         property.put(DESCRIPTION_KEY, leafDescription);
653
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);
660
661         return property;
662     }
663
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);
671
672         } else if (leafTypeDef instanceof BitsTypeDefinition) {
673             jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
674
675         } else if (leafTypeDef instanceof EnumTypeDefinition) {
676             jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
677
678         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
679             jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
680                     definitionNames, oaversion, schemaContext);
681
682         } else if (leafTypeDef instanceof StringTypeDefinition) {
683             jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
684
685         } else if (leafTypeDef instanceof UnionTypeDefinition) {
686             jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
687
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);
700         } else {
701             jsonType = STRING_TYPE;
702         }
703         if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
704             putIfNonNull(property, TYPE_KEY, jsonType);
705             if (leafTypeDef.getDefaultValue().isPresent()) {
706                 final Object defaultValue = leafTypeDef.getDefaultValue().get();
707                 if (defaultValue instanceof String) {
708                     final String stringDefaultValue = (String) defaultValue;
709                     if (leafTypeDef instanceof BooleanTypeDefinition) {
710                         setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
711                     } else if (leafTypeDef instanceof DecimalTypeDefinition
712                             || leafTypeDef instanceof Uint64TypeDefinition) {
713                         setDefaultValue(property, new BigDecimal(stringDefaultValue));
714                     } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
715                         //uint8,16,32 int8,16,32,64
716                         if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition)leafTypeDef)) {
717                             setDefaultValue(property, stringDefaultValue);
718                         } else {
719                             setDefaultValue(property, Long.valueOf(stringDefaultValue));
720                         }
721                     } else {
722                         setDefaultValue(property, stringDefaultValue);
723                     }
724                 } else {
725                     //we should never get here. getDefaultValue always gives us string
726                     setDefaultValue(property, defaultValue.toString());
727                 }
728             }
729         }
730         return jsonType;
731     }
732
733     private static String processBinaryType(final ObjectNode property) {
734         property.put(FORMAT_KEY, "byte");
735         return STRING_TYPE;
736     }
737
738     private static String processEnumType(final EnumTypeDefinition enumLeafType,
739                                           final ObjectNode property) {
740         final List<EnumPair> enumPairs = enumLeafType.getValues();
741         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
742         for (final EnumPair enumPair : enumPairs) {
743             enumNames.add(new TextNode(enumPair.getName()));
744         }
745
746         property.set(ENUM_KEY, enumNames);
747         setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
748         return STRING_TYPE;
749     }
750
751     private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
752                                           final ObjectNode definitions, final DefinitionNames definitionNames,
753                                           final OAversion oaversion, final SchemaContext schemaContext) {
754         final String definitionName;
755         if (isImported(leafTypeDef)) {
756             definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
757         } else {
758             final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
759             definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
760         }
761         property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
762         return STRING_TYPE;
763     }
764
765     private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
766                                               final ObjectNode definitions, final DefinitionNames definitionNames,
767                                               final SchemaContext context) {
768         final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
769         final String identityName = idNode.getQName().getLocalName();
770         if (!definitionNames.isListedNode(idNode)) {
771             final ObjectNode identityObj = buildIdentityObject(idNode, context);
772             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
773             final String name = identityName + discriminator;
774             definitions.set(name, identityObj);
775             return name;
776         } else {
777             return identityName + definitionNames.getDiscriminator(idNode);
778         }
779     }
780
781     private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode, final SchemaContext context) {
782         final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
783         final String identityName = idNode.getQName().getLocalName();
784         LOG.debug("Processing Identity: {}", identityName);
785
786         identityObj.put(TITLE_KEY, identityName);
787         identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
788
789         final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
790
791         final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
792         enumPayload.add(identityName);
793         populateEnumWithDerived(derivedIds, enumPayload, context);
794         identityObj.set(ENUM_KEY, enumPayload);
795         identityObj.put(TYPE_KEY, STRING_TYPE);
796         return identityObj;
797     }
798
799     private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
800         return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
801     }
802
803     private static String processBitsType(final BitsTypeDefinition bitsType,
804                                           final ObjectNode property) {
805         property.put(MIN_ITEMS, 0);
806         property.put(UNIQUE_ITEMS_KEY, true);
807         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
808         final Collection<? extends Bit> bits = bitsType.getBits();
809         for (final Bit bit : bits) {
810             enumNames.add(new TextNode(bit.getName()));
811         }
812         property.set(ENUM_KEY, enumNames);
813         property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
814         return STRING_TYPE;
815     }
816
817     private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
818                                             final String nodeName) {
819         StringTypeDefinition type = (StringTypeDefinition) stringType;
820         Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
821         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
822             type = type.getBaseType();
823             lengthConstraints = type.getLengthConstraint();
824         }
825
826         if (lengthConstraints.isPresent()) {
827             final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
828             putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
829             putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
830         }
831
832         if (type.getPatternConstraints().iterator().hasNext()) {
833             final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
834             String regex = pattern.getJavaPatternString();
835             regex = regex.substring(1, regex.length() - 1);
836             final Generex generex = new Generex(regex);
837             setDefaultValue(property, generex.random());
838         } else {
839             setDefaultValue(property, "Some " + nodeName);
840         }
841         return STRING_TYPE;
842     }
843
844     private String processNumberType(final RangeRestrictedTypeDefinition leafTypeDef, final ObjectNode property) {
845         final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
846                 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
847
848         if (isHexadecimalOrOctal(leafTypeDef)) {
849             return STRING_TYPE;
850         }
851
852         if (leafTypeDef instanceof DecimalTypeDefinition) {
853             maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
854             return NUMBER_TYPE;
855         }
856         if (leafTypeDef instanceof Uint8TypeDefinition
857                 || leafTypeDef instanceof Uint16TypeDefinition
858                 || leafTypeDef instanceof Int8TypeDefinition
859                 || leafTypeDef instanceof Int16TypeDefinition
860                 || leafTypeDef instanceof Int32TypeDefinition) {
861
862             property.put(FORMAT_KEY, INT32_FORMAT);
863             maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
864         } else if (leafTypeDef instanceof Uint32TypeDefinition
865                 || leafTypeDef instanceof Int64TypeDefinition) {
866
867             property.put(FORMAT_KEY, INT64_FORMAT);
868             maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
869         } else {
870             //uint64
871             setDefaultValue(property, 0);
872         }
873         return INTEGER_TYPE;
874     }
875
876     private boolean isHexadecimalOrOctal(RangeRestrictedTypeDefinition typeDef) {
877         final Optional<?> optDefaultValue = typeDef.getDefaultValue();
878         if (optDefaultValue.isPresent()) {
879             final String defaultValue = ((String)optDefaultValue.get());
880             return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
881         }
882         return false;
883     }
884
885     private String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
886                                                  final SchemaContext schemaContext) {
887         SchemaPath path = node.getPath();
888
889         while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
890             path = path.getParent();
891         }
892
893         final QName rootContainer = path.getLastComponent();
894         final String rootContainerName = rootContainer.getLocalName();
895         final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
896         setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
897         return STRING_TYPE;
898     }
899
900     private String processUnionType(final UnionTypeDefinition unionType) {
901         boolean isStringTakePlace = false;
902         boolean isNumberTakePlace = false;
903         boolean isBooleanTakePlace = false;
904         for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
905             if (!isStringTakePlace) {
906                 if (typeDef instanceof StringTypeDefinition
907                         || typeDef instanceof BitsTypeDefinition
908                         || typeDef instanceof BinaryTypeDefinition
909                         || typeDef instanceof IdentityrefTypeDefinition
910                         || typeDef instanceof EnumTypeDefinition
911                         || typeDef instanceof LeafrefTypeDefinition
912                         || typeDef instanceof UnionTypeDefinition) {
913                     isStringTakePlace = true;
914                 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
915                     isNumberTakePlace = true;
916                 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
917                     isBooleanTakePlace = true;
918                 }
919             }
920         }
921         if (isStringTakePlace) {
922             return STRING_TYPE;
923         }
924         if (isBooleanTakePlace) {
925             if (isNumberTakePlace) {
926                 return STRING_TYPE;
927             }
928             return BOOLEAN_TYPE;
929         }
930         return NUMBER_TYPE;
931     }
932
933     private static ObjectNode buildXmlParameter(SchemaNode node) {
934         final ObjectNode xml = JsonNodeFactory.instance.objectNode();
935         final QName qName = node.getQName();
936         xml.put(NAME_KEY, qName.getLocalName());
937         xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
938         return xml;
939     }
940
941     private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
942         if (key != null && number != null) {
943             if (number instanceof Double) {
944                 property.put(key, (Double) number);
945             } else if (number instanceof Float) {
946                 property.put(key, (Float) number);
947             } else if (number instanceof Integer) {
948                 property.put(key, (Integer) number);
949             } else if (number instanceof Short) {
950                 property.put(key, (Short) number);
951             } else if (number instanceof Long) {
952                 property.put(key, (Long) number);
953             }
954         }
955     }
956
957     private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
958         if (key != null && value != null) {
959             property.put(key, value);
960         }
961     }
962
963     private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
964         if (required.size() > 0) {
965             node.set(REQUIRED_KEY, required);
966         }
967     }
968
969     private static void setDefaultValue(final ObjectNode property, final String value) {
970         property.put(DEFAULT_KEY, value);
971     }
972
973     private static void setDefaultValue(final ObjectNode property, final Integer value) {
974         property.put(DEFAULT_KEY, value);
975     }
976
977     private static void setDefaultValue(final ObjectNode property, final Long value) {
978         property.put(DEFAULT_KEY, value);
979     }
980
981     private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
982         property.put(DEFAULT_KEY, value);
983     }
984
985     private static void setDefaultValue(final ObjectNode property, final Boolean value) {
986         property.put(DEFAULT_KEY, value);
987     }
988
989 }