Revert "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.SchemaPath;
60 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
63 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
64 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
68 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
69 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
71 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
73 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
74 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
76 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
77 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
78 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
79 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
80 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
81 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
82 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
83 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
84 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
85 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
86 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
89
90 /**
91  * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
92  */
93 public class DefinitionGenerator {
94
95     private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
96
97     private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
98     private static final String MAX_ITEMS = "maxItems";
99     private static final String MIN_ITEMS = "minItems";
100     private static final String MAX_LENGTH_KEY = "maxLength";
101     private static final String MIN_LENGTH_KEY = "minLength";
102     private static final String REQUIRED_KEY = "required";
103     private static final String REF_KEY = "$ref";
104     private static final String ITEMS_KEY = "items";
105     private static final String TYPE_KEY = "type";
106     private static final String PROPERTIES_KEY = "properties";
107     private static final String DESCRIPTION_KEY = "description";
108     private static final String ARRAY_TYPE = "array";
109     private static final String ENUM_KEY = "enum";
110     private static final String TITLE_KEY = "title";
111     private static final String DEFAULT_KEY = "default";
112     private static final String FORMAT_KEY = "format";
113     private static final String NAMESPACE_KEY = "namespace";
114     public static final String INPUT = "input";
115     public static final String INPUT_SUFFIX = "_input";
116     public static final String OUTPUT = "output";
117     public static final String OUTPUT_SUFFIX = "_output";
118     private static final String STRING_TYPE = "string";
119     private static final String OBJECT_TYPE = "object";
120     private static final String NUMBER_TYPE = "number";
121     private static final String INTEGER_TYPE = "integer";
122     private static final String INT32_FORMAT = "int32";
123     private static final String INT64_FORMAT = "int64";
124     private static final String BOOLEAN_TYPE = "boolean";
125
126     private Module topLevelModule;
127
128     public DefinitionGenerator() {
129     }
130
131     /**
132      * Creates Json definitions from provided module according to swagger spec.
133      *
134      * @param module          - Yang module to be converted
135      * @param schemaContext   - SchemaContext of all Yang files used by Api Doc
136      * @param definitionNames - Store for definition names
137      * @return ObjectNode containing data used for creating examples and definitions in Api Doc
138      * @throws IOException if I/O operation fails
139      */
140
141
142     public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
143                                           final ObjectNode definitions, final DefinitionNames definitionNames,
144                                           final OAversion oaversion, final boolean isForSingleModule)
145             throws IOException {
146         topLevelModule = module;
147
148         processIdentities(module, definitions, definitionNames, schemaContext);
149         processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
150         processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
151
152         if (isForSingleModule) {
153             processModule(module, definitions, definitionNames, schemaContext, oaversion);
154         }
155
156         return definitions;
157     }
158
159     public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
160                                           final DefinitionNames definitionNames, final OAversion oaversion,
161                                           final boolean isForSingleModule)
162             throws IOException {
163         final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
164         if (isForSingleModule) {
165             definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
166         }
167         return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
168     }
169
170     private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
171                                final EffectiveModelContext schemaContext, final OAversion oaversion) {
172         final ObjectNode definition = JsonNodeFactory.instance.objectNode();
173         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
174         final ArrayNode required = JsonNodeFactory.instance.arrayNode();
175         final String moduleName = module.getName();
176         final String definitionName = moduleName + MODULE_NAME_SUFFIX;
177         for (final DataSchemaNode node : module.getChildNodes()) {
178             final String localName = node.getQName().getLocalName();
179             if (node.isConfiguration()) {
180                 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
181                     for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
182                         final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
183
184                         final String ref = getAppropriateModelPrefix(oaversion)
185                                 + moduleName + CONFIG
186                                 + "_" + localName
187                                 + definitionNames.getDiscriminator(node);
188
189                         if (node instanceof ListSchemaNode) {
190                             childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
191                             final ObjectNode items = JsonNodeFactory.instance.objectNode();
192                             items.put(REF_KEY, ref);
193                             childNodeProperties.set(ITEMS_KEY, items);
194                             childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
195                             childNodeProperties.put(TITLE_KEY, localName + CONFIG);
196                         } else {
197                          /*
198                             Description can't be added, because nothing allowed alongside $ref.
199                             allOf is not an option, because ServiceNow can't parse it.
200                           */
201                             childNodeProperties.put(REF_KEY, ref);
202                         }
203                         //add module name prefix to property name, when ServiceNow can process colons
204                         properties.set(localName, childNodeProperties);
205                     }
206                 } else {
207                     if (node instanceof LeafSchemaNode) {
208                         /*
209                             Add module name prefix to property name, when ServiceNow can process colons(second parameter
210                             of processLeafNode).
211                          */
212                         processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
213                                 definitions, definitionNames, oaversion);
214                     }
215                 }
216             }
217         }
218         definition.put(TITLE_KEY, definitionName);
219         definition.put(TYPE_KEY, OBJECT_TYPE);
220         definition.set(PROPERTIES_KEY, properties);
221         definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
222         setRequiredIfNotEmpty(definition, required);
223
224         definitions.set(definitionName, definition);
225     }
226
227     private void processContainersAndLists(final Module module, final ObjectNode definitions,
228             final DefinitionNames definitionNames, final EffectiveModelContext schemaContext, final OAversion oaversion)
229                 throws IOException {
230         final String moduleName = module.getName();
231
232         for (final DataSchemaNode childNode : module.getChildNodes()) {
233             // For every container and list in the module
234             if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
235                 if (childNode.isConfiguration()) {
236                     processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
237                             true, schemaContext, oaversion);
238                 }
239                 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
240                         false, schemaContext, oaversion);
241                 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
242                         oaversion);
243             }
244         }
245     }
246
247     private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
248                                             final ObjectNode definitions, final DefinitionNames definitionNames,
249                                             final EffectiveModelContext schemaContext, final OAversion oaversion)
250             throws IOException {
251         for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
252             processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
253         }
254     }
255
256     private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
257                              final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
258         final String moduleName = module.getName();
259         for (final RpcDefinition rpcDefinition : module.getRpcs()) {
260             processOperations(rpcDefinition, moduleName, definitions, definitionNames, schemaContext, oaversion);
261         }
262     }
263
264
265     private void processOperations(final OperationDefinition operationDef, final String parentName,
266             final ObjectNode definitions, final DefinitionNames definitionNames,
267             final EffectiveModelContext schemaContext, final OAversion oaversion)
268                 throws IOException {
269         final String operationName = operationDef.getQName().getLocalName();
270         processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
271                 definitionNames, schemaContext, oaversion);
272         processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
273                 definitionNames, schemaContext, oaversion);
274     }
275
276     private void processOperationInputOutput(final ContainerLike container, final String operationName,
277                                              final String parentName, final boolean isInput,
278                                              final ObjectNode definitions, final DefinitionNames definitionNames,
279                                              final EffectiveModelContext schemaContext, final OAversion oaversion)
280             throws IOException {
281         if (!container.getChildNodes().isEmpty()) {
282             final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
283             final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
284             processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
285                     false, schemaContext, oaversion);
286
287             childSchema.put(TYPE_KEY, OBJECT_TYPE);
288             final ObjectNode xml = JsonNodeFactory.instance.objectNode();
289             xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
290             childSchema.set(XML_KEY, xml);
291             childSchema.put(TITLE_KEY, filename);
292             final String discriminator =
293                     definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
294             definitions.set(filename + discriminator, childSchema);
295
296             processTopData(filename, discriminator, definitions, container, oaversion);
297         }
298     }
299
300     private static ObjectNode processTopData(final String filename, final String discriminator,
301             final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
302         final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
303         final String name = filename + discriminator;
304         final String ref = getAppropriateModelPrefix(oaversion) + name;
305         final String topName = filename + TOP;
306
307         if (schemaNode instanceof ListSchemaNode) {
308             dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
309             final ObjectNode items = JsonNodeFactory.instance.objectNode();
310             items.put(REF_KEY, ref);
311             dataNodeProperties.set(ITEMS_KEY, items);
312             dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
313         } else {
314              /*
315                 Description can't be added, because nothing allowed alongside $ref.
316                 allOf is not an option, because ServiceNow can't parse it.
317               */
318             dataNodeProperties.put(REF_KEY, ref);
319         }
320
321         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
322         /*
323             Add module name prefix to property name, when needed, when ServiceNow can process colons,
324             use RestDocGenUtil#resolveNodesName for creating property name
325          */
326         properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
327         final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
328         finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
329         finalChildSchema.set(PROPERTIES_KEY, properties);
330         finalChildSchema.put(TITLE_KEY, topName);
331
332
333         definitions.set(topName + discriminator, finalChildSchema);
334
335         return dataNodeProperties;
336     }
337
338     /**
339      * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
340      * @param module          The module from which the identity stmt will be processed
341      * @param definitions     The ObjectNode in which the parsed identity will be put as a 'model' obj
342      * @param definitionNames Store for definition names
343      */
344     private static void processIdentities(final Module module, final ObjectNode definitions,
345                                           final DefinitionNames definitionNames, final EffectiveModelContext context) {
346
347         final String moduleName = module.getName();
348         final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
349         LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
350
351         for (final IdentitySchemaNode idNode : idNodes) {
352             final ObjectNode identityObj = buildIdentityObject(idNode, context);
353             final String idName = idNode.getQName().getLocalName();
354             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
355             final String name = idName + discriminator;
356             definitions.set(name, identityObj);
357         }
358     }
359
360     private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
361                                                 final ArrayNode enumPayload, final EffectiveModelContext context) {
362         for (final IdentitySchemaNode derivedId : derivedIds) {
363             enumPayload.add(derivedId.getQName().getLocalName());
364             populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
365         }
366     }
367
368     private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
369                                                 final ObjectNode definitions, final DefinitionNames definitionNames,
370                                                 final boolean isConfig, final EffectiveModelContext schemaContext,
371                                                 final OAversion oaversion) throws IOException {
372         if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
373             final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
374             final SchemaNode schemaNode = (SchemaNode) dataNode;
375             final String localName = schemaNode.getQName().getLocalName();
376             final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
377             final String nameAsParent = parentName + "_" + localName;
378             final ObjectNode properties =
379                     processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
380                             definitionNames, isConfig, schemaContext, oaversion);
381
382             final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
383             final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
384             final String postXmlNodeName = postNodeName + XML_SUFFIX;
385             final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
386
387             final String description = schemaNode.getDescription().orElse("");
388             final String discriminator;
389
390             if (!definitionNames.isListedNode(schemaNode)) {
391                 final List<String> names = List.of(parentNameConfigLocalName,
392                         parentNameConfigLocalName + TOP,
393                         nameAsParent,
394                         nameAsParent + TOP,
395                         postNodeName,
396                         postXmlNodeName);
397                 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
398             } else {
399                 discriminator = definitionNames.getDiscriminator(schemaNode);
400             }
401
402             if (isConfig) {
403                 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
404                 String truePostNodeName = postNodeName + discriminator;
405                 definitions.set(truePostNodeName, postSchema);
406
407                 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
408                 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
409                 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
410             }
411
412             childSchema.put(TYPE_KEY, OBJECT_TYPE);
413             childSchema.set(PROPERTIES_KEY, properties);
414             childSchema.put(TITLE_KEY, nodeName);
415             childSchema.put(DESCRIPTION_KEY, description);
416
417             final String defName = nodeName + discriminator;
418             childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
419             definitions.set(defName, childSchema);
420
421             return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
422         }
423         return null;
424     }
425
426     private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
427             final String postNodeName, final String description) {
428         final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
429         final ObjectNode postItemProperties;
430         if (dataNode instanceof ListSchemaNode) {
431             postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
432         } else {
433             postItemProperties = properties.deepCopy();
434         }
435         postSchema.put(TYPE_KEY, OBJECT_TYPE);
436         postSchema.set(PROPERTIES_KEY, postItemProperties);
437         postSchema.put(TITLE_KEY, postNodeName);
438         postSchema.put(DESCRIPTION_KEY, description);
439         postSchema.set(XML_KEY, buildXmlParameter(dataNode));
440         return postSchema;
441     }
442
443     private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
444         final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
445         final List<QName> keyDefinition = listNode.getKeyDefinition();
446         final Set<String> keys = listNode.getChildNodes().stream()
447                 .filter(node -> keyDefinition.contains(node.getQName()))
448                 .map(node -> node.getQName().getLocalName())
449                 .collect(Collectors.toSet());
450
451         Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
452         while (it.hasNext()) {
453             Map.Entry<String, JsonNode> property = it.next();
454             if (!keys.contains(property.getKey())) {
455                 postListItemProperties.set(property.getKey(), property.getValue());
456             }
457         }
458
459         return postListItemProperties;
460     }
461
462     /**
463      * Processes the nodes.
464      */
465     private ObjectNode processChildren(
466             final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
467             final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
468             final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
469         final ObjectNode properties = JsonNodeFactory.instance.objectNode();
470         final ArrayNode required = JsonNodeFactory.instance.arrayNode();
471         for (final DataSchemaNode node : nodes) {
472             if (!isConfig || node.isConfiguration()) {
473                 /*
474                     Add module name prefix to property name, when needed, when ServiceNow can process colons,
475                     use RestDocGenUtil#resolveNodesName for creating property name
476                  */
477                 final String propertyName = node.getQName().getLocalName();
478                 final ObjectNode property;
479                 if (node instanceof LeafSchemaNode) {
480                     processLeafNode((LeafSchemaNode) node, propertyName, properties,
481                             required, schemaContext, definitions, definitionNames, oaversion);
482                 } else if (node instanceof AnyxmlSchemaNode) {
483                     processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
484                             required);
485                 } else if (node instanceof AnydataSchemaNode) {
486                     processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
487                             required);
488                 } else {
489                     if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
490                         property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
491                                 definitionNames, isConfig, schemaContext, oaversion);
492                         if (!isConfig) {
493                             processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
494                                     oaversion);
495                         }
496                     } else if (node instanceof LeafListSchemaNode) {
497                         property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
498                                 definitionNames, oaversion);
499
500                     } else if (node instanceof ChoiceSchemaNode) {
501                         for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
502                             processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
503                                     isConfig, schemaContext, properties, oaversion);
504                         }
505                         continue;
506
507                     } else {
508                         throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
509                     }
510                     properties.set(propertyName, property);
511                 }
512             }
513         }
514         parentNode.set(PROPERTIES_KEY, properties);
515         setRequiredIfNotEmpty(parentNode, required);
516         return properties;
517     }
518
519     private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final EffectiveModelContext schemaContext,
520                                            final ObjectNode definitions, final DefinitionNames definitionNames,
521                                            final OAversion oaversion) {
522         final ObjectNode props = JsonNodeFactory.instance.objectNode();
523         props.put(TYPE_KEY, ARRAY_TYPE);
524
525         final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
526         final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
527         processElementCount(optConstraint, props);
528
529         processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
530         props.set(ITEMS_KEY, itemsVal);
531
532         props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
533
534         return props;
535     }
536
537     private void processChoiceNode(
538             final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
539             final DefinitionNames definitionNames, final boolean isConfig,
540             final EffectiveModelContext schemaContext, final ObjectNode properties, final OAversion oaversion)
541             throws IOException {
542         for (final DataSchemaNode node : nodes) {
543             /*
544                 Add module name prefix to property name, when needed, when ServiceNow can process colons,
545                 use RestDocGenUtil#resolveNodesName for creating property name
546              */
547             final String name = node.getQName().getLocalName();
548             final ObjectNode property;
549
550             /*
551                 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
552                 properties
553              */
554             if (node instanceof LeafSchemaNode) {
555                 processLeafNode((LeafSchemaNode) node, name, properties,
556                         JsonNodeFactory.instance.arrayNode(), schemaContext, definitions, definitionNames, oaversion);
557             } else if (node instanceof AnyxmlSchemaNode) {
558                 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
559                         JsonNodeFactory.instance.arrayNode());
560             } else if (node instanceof AnydataSchemaNode) {
561                 processAnydataNode((AnydataSchemaNode) node, name, properties,
562                         JsonNodeFactory.instance.arrayNode());
563             } else {
564                 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
565                     property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
566                             definitionNames, isConfig, schemaContext, oaversion);
567                     if (!isConfig) {
568                         processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
569                                 oaversion);
570                     }
571                 } else if (node instanceof LeafListSchemaNode) {
572                     property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
573                             definitionNames, oaversion);
574
575                 } else if (node instanceof ChoiceSchemaNode) {
576                     for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
577                         processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
578                                 schemaContext, properties, oaversion);
579                     }
580                     continue;
581                 } else {
582                     throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
583                 }
584                 properties.set(name, property);
585             }
586         }
587     }
588
589     private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
590         if (constraint.isPresent()) {
591             final ElementCountConstraint constr = constraint.get();
592             final Integer minElements = constr.getMinElements();
593             if (minElements != null) {
594                 props.put(MIN_ITEMS, minElements);
595             }
596             final Integer maxElements = constr.getMaxElements();
597             if (maxElements != null) {
598                 props.put(MAX_ITEMS, maxElements);
599             }
600         }
601     }
602
603     private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
604         if (node.isMandatory()) {
605             required.add(nodeName);
606         }
607     }
608
609     private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
610                                        final ObjectNode properties, final ArrayNode required,
611                                        final EffectiveModelContext schemaContext, final ObjectNode definitions,
612                                        final DefinitionNames definitionNames, final OAversion oaversion) {
613         final ObjectNode property = JsonNodeFactory.instance.objectNode();
614
615         final String leafDescription = leafNode.getDescription().orElse("");
616         /*
617             Description can't be added, because nothing allowed alongside $ref.
618             allOf is not an option, because ServiceNow can't parse it.
619         */
620         if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
621             property.put(DESCRIPTION_KEY, leafDescription);
622         }
623
624         processTypeDef(leafNode.getType(), leafNode, property, schemaContext, definitions, definitionNames, oaversion);
625         properties.set(jsonLeafName, property);
626         property.set(XML_KEY, buildXmlParameter(leafNode));
627         processMandatory(leafNode, jsonLeafName, required);
628
629         return property;
630     }
631
632     private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
633                                                  final ObjectNode properties, final ArrayNode required) {
634         final ObjectNode property = JsonNodeFactory.instance.objectNode();
635
636         final String leafDescription = leafNode.getDescription().orElse("");
637         property.put(DESCRIPTION_KEY, leafDescription);
638
639         final String localName = leafNode.getQName().getLocalName();
640         setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
641         property.put(TYPE_KEY, STRING_TYPE);
642         property.set(XML_KEY, buildXmlParameter(leafNode));
643         processMandatory(leafNode, name, required);
644         properties.set(name, property);
645
646         return property;
647     }
648
649     private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
650                                                 final ObjectNode properties, final ArrayNode required) {
651         final ObjectNode property = JsonNodeFactory.instance.objectNode();
652
653         final String leafDescription = leafNode.getDescription().orElse("");
654         property.put(DESCRIPTION_KEY, leafDescription);
655
656         final String localName = leafNode.getQName().getLocalName();
657         setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
658         property.put(TYPE_KEY, STRING_TYPE);
659         property.set(XML_KEY, buildXmlParameter(leafNode));
660         processMandatory(leafNode, name, required);
661         properties.set(name, property);
662
663         return property;
664     }
665
666     private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
667                                   final ObjectNode property, final EffectiveModelContext schemaContext,
668                                   final ObjectNode definitions, final DefinitionNames definitionNames,
669                                   final OAversion oaversion) {
670         final String jsonType;
671         if (leafTypeDef instanceof BinaryTypeDefinition) {
672             jsonType = processBinaryType(property);
673
674         } else if (leafTypeDef instanceof BitsTypeDefinition) {
675             jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
676
677         } else if (leafTypeDef instanceof EnumTypeDefinition) {
678             jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
679
680         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
681             jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
682                     definitionNames, oaversion, schemaContext);
683
684         } else if (leafTypeDef instanceof StringTypeDefinition) {
685             jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
686
687         } else if (leafTypeDef instanceof UnionTypeDefinition) {
688             jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
689
690         } else if (leafTypeDef instanceof EmptyTypeDefinition) {
691             jsonType = OBJECT_TYPE;
692         } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
693             final SchemaInferenceStack stack = SchemaInferenceStack.ofSchemaPath(schemaContext, node.getPath());
694             return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
695                 schemaContext, definitions, definitionNames, oaversion);
696         } else if (leafTypeDef instanceof BooleanTypeDefinition) {
697             jsonType = BOOLEAN_TYPE;
698             setDefaultValue(property, true);
699         } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
700             jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
701         } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
702             jsonType = processInstanceIdentifierType(node, property, schemaContext);
703         } else {
704             jsonType = STRING_TYPE;
705         }
706         if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
707             putIfNonNull(property, TYPE_KEY, jsonType);
708             if (leafTypeDef.getDefaultValue().isPresent()) {
709                 final Object defaultValue = leafTypeDef.getDefaultValue().get();
710                 if (defaultValue instanceof String) {
711                     final String stringDefaultValue = (String) defaultValue;
712                     if (leafTypeDef instanceof BooleanTypeDefinition) {
713                         setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
714                     } else if (leafTypeDef instanceof DecimalTypeDefinition
715                             || leafTypeDef instanceof Uint64TypeDefinition) {
716                         setDefaultValue(property, new BigDecimal(stringDefaultValue));
717                     } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
718                         //uint8,16,32 int8,16,32,64
719                         if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
720                             setDefaultValue(property, stringDefaultValue);
721                         } else {
722                             setDefaultValue(property, Long.valueOf(stringDefaultValue));
723                         }
724                     } else {
725                         setDefaultValue(property, stringDefaultValue);
726                     }
727                 } else {
728                     //we should never get here. getDefaultValue always gives us string
729                     setDefaultValue(property, defaultValue.toString());
730                 }
731             }
732         }
733         return jsonType;
734     }
735
736     private static String processBinaryType(final ObjectNode property) {
737         property.put(FORMAT_KEY, "byte");
738         return STRING_TYPE;
739     }
740
741     private static String processEnumType(final EnumTypeDefinition enumLeafType,
742                                           final ObjectNode property) {
743         final List<EnumPair> enumPairs = enumLeafType.getValues();
744         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
745         for (final EnumPair enumPair : enumPairs) {
746             enumNames.add(new TextNode(enumPair.getName()));
747         }
748
749         property.set(ENUM_KEY, enumNames);
750         setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
751         return STRING_TYPE;
752     }
753
754     private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
755                                           final ObjectNode definitions, final DefinitionNames definitionNames,
756                                           final OAversion oaversion, final EffectiveModelContext schemaContext) {
757         final String definitionName;
758         if (isImported(leafTypeDef)) {
759             definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
760         } else {
761             final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
762             definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
763         }
764         property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
765         return STRING_TYPE;
766     }
767
768     private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
769                                               final ObjectNode definitions, final DefinitionNames definitionNames,
770                                               final EffectiveModelContext context) {
771         final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
772         final String identityName = idNode.getQName().getLocalName();
773         if (!definitionNames.isListedNode(idNode)) {
774             final ObjectNode identityObj = buildIdentityObject(idNode, context);
775             final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
776             final String name = identityName + discriminator;
777             definitions.set(name, identityObj);
778             return name;
779         } else {
780             return identityName + definitionNames.getDiscriminator(idNode);
781         }
782     }
783
784     private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
785             final EffectiveModelContext context) {
786         final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
787         final String identityName = idNode.getQName().getLocalName();
788         LOG.debug("Processing Identity: {}", identityName);
789
790         identityObj.put(TITLE_KEY, identityName);
791         identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
792
793         final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
794
795         final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
796         enumPayload.add(identityName);
797         populateEnumWithDerived(derivedIds, enumPayload, context);
798         identityObj.set(ENUM_KEY, enumPayload);
799         identityObj.put(TYPE_KEY, STRING_TYPE);
800         return identityObj;
801     }
802
803     private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
804         return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
805     }
806
807     private static String processBitsType(final BitsTypeDefinition bitsType,
808                                           final ObjectNode property) {
809         property.put(MIN_ITEMS, 0);
810         property.put(UNIQUE_ITEMS_KEY, true);
811         final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
812         final Collection<? extends Bit> bits = bitsType.getBits();
813         for (final Bit bit : bits) {
814             enumNames.add(new TextNode(bit.getName()));
815         }
816         property.set(ENUM_KEY, enumNames);
817         property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
818         return STRING_TYPE;
819     }
820
821     private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
822                                             final String nodeName) {
823         StringTypeDefinition type = (StringTypeDefinition) stringType;
824         Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
825         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
826             type = type.getBaseType();
827             lengthConstraints = type.getLengthConstraint();
828         }
829
830         if (lengthConstraints.isPresent()) {
831             final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
832             putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
833             putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
834         }
835
836         if (type.getPatternConstraints().iterator().hasNext()) {
837             final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
838             String regex = pattern.getJavaPatternString();
839             regex = regex.substring(1, regex.length() - 1);
840             String defaultValue = "";
841             try {
842                 final Generex generex = new Generex(regex);
843                 defaultValue = generex.random();
844             } catch (IllegalArgumentException ex) {
845                 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
846             }
847             setDefaultValue(property, defaultValue);
848         } else {
849             setDefaultValue(property, "Some " + nodeName);
850         }
851         return STRING_TYPE;
852     }
853
854     private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
855             final ObjectNode property) {
856         final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
857                 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
858
859         if (isHexadecimalOrOctal(leafTypeDef)) {
860             return STRING_TYPE;
861         }
862
863         if (leafTypeDef instanceof DecimalTypeDefinition) {
864             maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
865             return NUMBER_TYPE;
866         }
867         if (leafTypeDef instanceof Uint8TypeDefinition
868                 || leafTypeDef instanceof Uint16TypeDefinition
869                 || leafTypeDef instanceof Int8TypeDefinition
870                 || leafTypeDef instanceof Int16TypeDefinition
871                 || leafTypeDef instanceof Int32TypeDefinition) {
872
873             property.put(FORMAT_KEY, INT32_FORMAT);
874             maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
875         } else if (leafTypeDef instanceof Uint32TypeDefinition
876                 || leafTypeDef instanceof Int64TypeDefinition) {
877
878             property.put(FORMAT_KEY, INT64_FORMAT);
879             maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
880         } else {
881             //uint64
882             setDefaultValue(property, 0);
883         }
884         return INTEGER_TYPE;
885     }
886
887     private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
888         final Optional<?> optDefaultValue = typeDef.getDefaultValue();
889         if (optDefaultValue.isPresent()) {
890             final String defaultValue = (String)optDefaultValue.get();
891             return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
892         }
893         return false;
894     }
895
896     private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
897                                                         final EffectiveModelContext schemaContext) {
898         SchemaPath path = node.getPath();
899
900         while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
901             path = path.getParent();
902         }
903
904         final QName rootContainer = path.getLastComponent();
905         final String rootContainerName = rootContainer.getLocalName();
906         final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
907         setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
908         return STRING_TYPE;
909     }
910
911     private static String processUnionType(final UnionTypeDefinition unionType) {
912         boolean isStringTakePlace = false;
913         boolean isNumberTakePlace = false;
914         boolean isBooleanTakePlace = false;
915         for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
916             if (!isStringTakePlace) {
917                 if (typeDef instanceof StringTypeDefinition
918                         || typeDef instanceof BitsTypeDefinition
919                         || typeDef instanceof BinaryTypeDefinition
920                         || typeDef instanceof IdentityrefTypeDefinition
921                         || typeDef instanceof EnumTypeDefinition
922                         || typeDef instanceof LeafrefTypeDefinition
923                         || typeDef instanceof UnionTypeDefinition) {
924                     isStringTakePlace = true;
925                 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
926                     isNumberTakePlace = true;
927                 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
928                     isBooleanTakePlace = true;
929                 }
930             }
931         }
932         if (isStringTakePlace) {
933             return STRING_TYPE;
934         }
935         if (isBooleanTakePlace) {
936             if (isNumberTakePlace) {
937                 return STRING_TYPE;
938             }
939             return BOOLEAN_TYPE;
940         }
941         return NUMBER_TYPE;
942     }
943
944     private static ObjectNode buildXmlParameter(final SchemaNode node) {
945         final ObjectNode xml = JsonNodeFactory.instance.objectNode();
946         final QName qName = node.getQName();
947         xml.put(NAME_KEY, qName.getLocalName());
948         xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
949         return xml;
950     }
951
952     private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
953         if (key != null && number != null) {
954             if (number instanceof Double) {
955                 property.put(key, (Double) number);
956             } else if (number instanceof Float) {
957                 property.put(key, (Float) number);
958             } else if (number instanceof Integer) {
959                 property.put(key, (Integer) number);
960             } else if (number instanceof Short) {
961                 property.put(key, (Short) number);
962             } else if (number instanceof Long) {
963                 property.put(key, (Long) number);
964             }
965         }
966     }
967
968     private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
969         if (key != null && value != null) {
970             property.put(key, value);
971         }
972     }
973
974     private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
975         if (required.size() > 0) {
976             node.set(REQUIRED_KEY, required);
977         }
978     }
979
980     private static void setDefaultValue(final ObjectNode property, final String value) {
981         property.put(DEFAULT_KEY, value);
982     }
983
984     private static void setDefaultValue(final ObjectNode property, final Integer value) {
985         property.put(DEFAULT_KEY, value);
986     }
987
988     private static void setDefaultValue(final ObjectNode property, final Long value) {
989         property.put(DEFAULT_KEY, value);
990     }
991
992     private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
993         property.put(DEFAULT_KEY, value);
994     }
995
996     private static void setDefaultValue(final ObjectNode property, final Boolean value) {
997         property.put(DEFAULT_KEY, value);
998     }
999
1000 }