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