Replace #getPath in processInstanceIdentifierType
[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.EffectiveModelContext;
49 import org.opendaylight.yangtools.yang.model.api.ElementCountConstraint;
50 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
54 import org.opendaylight.yangtools.yang.model.api.MandatoryAware;
55 import org.opendaylight.yangtools.yang.model.api.Module;
56 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
57 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
58 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
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.SchemaInferenceStack;
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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext schemaContext, final OAversion oaversion)
228                 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext schemaContext, final OAversion oaversion)
267                 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 EffectiveModelContext 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 static ObjectNode processTopData(final String filename, final String discriminator,
300             final ObjectNode definitions, 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext 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 static 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 static 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext 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 EffectiveModelContext 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             final SchemaInferenceStack stack = SchemaInferenceStack.ofSchemaPath(schemaContext, node.getPath());
693             return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
694                 schemaContext, definitions, definitionNames, oaversion);
695         } else if (leafTypeDef instanceof BooleanTypeDefinition) {
696             jsonType = BOOLEAN_TYPE;
697             setDefaultValue(property, true);
698         } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
699             jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
700         } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
701             jsonType = processInstanceIdentifierType(node, property, schemaContext);
702         } else {
703             jsonType = STRING_TYPE;
704         }
705         if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
706             putIfNonNull(property, TYPE_KEY, jsonType);
707             if (leafTypeDef.getDefaultValue().isPresent()) {
708                 final Object defaultValue = leafTypeDef.getDefaultValue().get();
709                 if (defaultValue instanceof String) {
710                     final String stringDefaultValue = (String) defaultValue;
711                     if (leafTypeDef instanceof BooleanTypeDefinition) {
712                         setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
713                     } else if (leafTypeDef instanceof DecimalTypeDefinition
714                             || leafTypeDef instanceof Uint64TypeDefinition) {
715                         setDefaultValue(property, new BigDecimal(stringDefaultValue));
716                     } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
717                         //uint8,16,32 int8,16,32,64
718                         if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
719                             setDefaultValue(property, stringDefaultValue);
720                         } else {
721                             setDefaultValue(property, Long.valueOf(stringDefaultValue));
722                         }
723                     } else {
724                         setDefaultValue(property, stringDefaultValue);
725                     }
726                 } else {
727                     //we should never get here. getDefaultValue always gives us string
728                     setDefaultValue(property, defaultValue.toString());
729                 }
730             }
731         }
732         return jsonType;
733     }
734
735     private static String processBinaryType(final ObjectNode property) {
736         property.put(FORMAT_KEY, "byte");
737         return STRING_TYPE;
738     }
739
740     private static String processEnumType(final EnumTypeDefinition enumLeafType,
741                                           final ObjectNode property) {
742         final List<EnumPair> enumPairs = enumLeafType.getValues();
743         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
744         for (final EnumPair enumPair : enumPairs) {
745             enumNames.add(new TextNode(enumPair.getName()));
746         }
747
748         property.set(ENUM_KEY, enumNames);
749         setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
750         return STRING_TYPE;
751     }
752
753     private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
754                                           final ObjectNode definitions, final DefinitionNames definitionNames,
755                                           final OAversion oaversion, final EffectiveModelContext schemaContext) {
756         final String definitionName;
757         if (isImported(leafTypeDef)) {
758             definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
759         } else {
760             final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
761             definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
762         }
763         property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
764         return STRING_TYPE;
765     }
766
767     private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
768                                               final ObjectNode definitions, final DefinitionNames definitionNames,
769                                               final EffectiveModelContext context) {
770         final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
771         final String identityName = idNode.getQName().getLocalName();
772         if (!definitionNames.isListedNode(idNode)) {
773             final ObjectNode identityObj = buildIdentityObject(idNode, context);
774             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
775             final String name = identityName + discriminator;
776             definitions.set(name, identityObj);
777             return name;
778         } else {
779             return identityName + definitionNames.getDiscriminator(idNode);
780         }
781     }
782
783     private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
784             final EffectiveModelContext context) {
785         final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
786         final String identityName = idNode.getQName().getLocalName();
787         LOG.debug("Processing Identity: {}", identityName);
788
789         identityObj.put(TITLE_KEY, identityName);
790         identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
791
792         final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
793
794         final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
795         enumPayload.add(identityName);
796         populateEnumWithDerived(derivedIds, enumPayload, context);
797         identityObj.set(ENUM_KEY, enumPayload);
798         identityObj.put(TYPE_KEY, STRING_TYPE);
799         return identityObj;
800     }
801
802     private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
803         return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
804     }
805
806     private static String processBitsType(final BitsTypeDefinition bitsType,
807                                           final ObjectNode property) {
808         property.put(MIN_ITEMS, 0);
809         property.put(UNIQUE_ITEMS_KEY, true);
810         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
811         final Collection<? extends Bit> bits = bitsType.getBits();
812         for (final Bit bit : bits) {
813             enumNames.add(new TextNode(bit.getName()));
814         }
815         property.set(ENUM_KEY, enumNames);
816         property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
817         return STRING_TYPE;
818     }
819
820     private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
821                                             final String nodeName) {
822         StringTypeDefinition type = (StringTypeDefinition) stringType;
823         Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
824         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
825             type = type.getBaseType();
826             lengthConstraints = type.getLengthConstraint();
827         }
828
829         if (lengthConstraints.isPresent()) {
830             final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
831             putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
832             putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
833         }
834
835         if (type.getPatternConstraints().iterator().hasNext()) {
836             final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
837             String regex = pattern.getJavaPatternString();
838             regex = regex.substring(1, regex.length() - 1);
839             String defaultValue = "";
840             try {
841                 final Generex generex = new Generex(regex);
842                 defaultValue = generex.random();
843             } catch (IllegalArgumentException ex) {
844                 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
845             }
846             setDefaultValue(property, defaultValue);
847         } else {
848             setDefaultValue(property, "Some " + nodeName);
849         }
850         return STRING_TYPE;
851     }
852
853     private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
854             final ObjectNode property) {
855         final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
856                 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
857
858         if (isHexadecimalOrOctal(leafTypeDef)) {
859             return STRING_TYPE;
860         }
861
862         if (leafTypeDef instanceof DecimalTypeDefinition) {
863             maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
864             return NUMBER_TYPE;
865         }
866         if (leafTypeDef instanceof Uint8TypeDefinition
867                 || leafTypeDef instanceof Uint16TypeDefinition
868                 || leafTypeDef instanceof Int8TypeDefinition
869                 || leafTypeDef instanceof Int16TypeDefinition
870                 || leafTypeDef instanceof Int32TypeDefinition) {
871
872             property.put(FORMAT_KEY, INT32_FORMAT);
873             maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
874         } else if (leafTypeDef instanceof Uint32TypeDefinition
875                 || leafTypeDef instanceof Int64TypeDefinition) {
876
877             property.put(FORMAT_KEY, INT64_FORMAT);
878             maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
879         } else {
880             //uint64
881             setDefaultValue(property, 0);
882         }
883         return INTEGER_TYPE;
884     }
885
886     private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
887         final Optional<?> optDefaultValue = typeDef.getDefaultValue();
888         if (optDefaultValue.isPresent()) {
889             final String defaultValue = (String)optDefaultValue.get();
890             return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
891         }
892         return false;
893     }
894
895     private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
896                                                         final EffectiveModelContext schemaContext) {
897         // create example instance-identifier to the first container of node's module if exists or leave it empty
898         final var module = schemaContext.findModule(node.getQName().getModule());
899         if (module.isPresent()) {
900             final var container = module.get().getChildNodes().stream()
901                     .filter(n -> n instanceof ContainerSchemaNode)
902                     .findFirst();
903             container.ifPresent(c -> setDefaultValue(property, String.format("/%s:%s", module.get().getPrefix(),
904                     c.getQName().getLocalName())));
905         }
906
907         return STRING_TYPE;
908     }
909
910     private static String processUnionType(final UnionTypeDefinition unionType) {
911         boolean isStringTakePlace = false;
912         boolean isNumberTakePlace = false;
913         boolean isBooleanTakePlace = false;
914         for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
915             if (!isStringTakePlace) {
916                 if (typeDef instanceof StringTypeDefinition
917                         || typeDef instanceof BitsTypeDefinition
918                         || typeDef instanceof BinaryTypeDefinition
919                         || typeDef instanceof IdentityrefTypeDefinition
920                         || typeDef instanceof EnumTypeDefinition
921                         || typeDef instanceof LeafrefTypeDefinition
922                         || typeDef instanceof UnionTypeDefinition) {
923                     isStringTakePlace = true;
924                 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
925                     isNumberTakePlace = true;
926                 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
927                     isBooleanTakePlace = true;
928                 }
929             }
930         }
931         if (isStringTakePlace) {
932             return STRING_TYPE;
933         }
934         if (isBooleanTakePlace) {
935             if (isNumberTakePlace) {
936                 return STRING_TYPE;
937             }
938             return BOOLEAN_TYPE;
939         }
940         return NUMBER_TYPE;
941     }
942
943     private static ObjectNode buildXmlParameter(final SchemaNode node) {
944         final ObjectNode xml = JsonNodeFactory.instance.objectNode();
945         final QName qName = node.getQName();
946         xml.put(NAME_KEY, qName.getLocalName());
947         xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
948         return xml;
949     }
950
951     private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
952         if (key != null && number != null) {
953             if (number instanceof Double) {
954                 property.put(key, (Double) number);
955             } else if (number instanceof Float) {
956                 property.put(key, (Float) number);
957             } else if (number instanceof Integer) {
958                 property.put(key, (Integer) number);
959             } else if (number instanceof Short) {
960                 property.put(key, (Short) number);
961             } else if (number instanceof Long) {
962                 property.put(key, (Long) number);
963             }
964         }
965     }
966
967     private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
968         if (key != null && value != null) {
969             property.put(key, value);
970         }
971     }
972
973     private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
974         if (required.size() > 0) {
975             node.set(REQUIRED_KEY, required);
976         }
977     }
978
979     private static void setDefaultValue(final ObjectNode property, final String value) {
980         property.put(DEFAULT_KEY, value);
981     }
982
983     private static void setDefaultValue(final ObjectNode property, final Integer value) {
984         property.put(DEFAULT_KEY, value);
985     }
986
987     private static void setDefaultValue(final ObjectNode property, final Long value) {
988         property.put(DEFAULT_KEY, value);
989     }
990
991     private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
992         property.put(DEFAULT_KEY, value);
993     }
994
995     private static void setDefaultValue(final ObjectNode property, final Boolean value) {
996         property.put(DEFAULT_KEY, value);
997     }
998
999 }