2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.netconf.sal.rest.doc.impl;
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;
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;
33 import java.util.Optional;
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;
91 * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
93 public class DefinitionGenerator {
95 private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
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";
126 private Module topLevelModule;
128 public DefinitionGenerator() {
132 * Creates Json definitions from provided module according to swagger spec.
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
142 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
143 final ObjectNode definitions, final DefinitionNames definitionNames,
144 final OAversion oaversion, final boolean isForSingleModule)
146 topLevelModule = module;
148 processIdentities(module, definitions, definitionNames, schemaContext);
149 processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
150 processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
152 if (isForSingleModule) {
153 processModule(module, definitions, definitionNames, schemaContext, oaversion);
159 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
160 final DefinitionNames definitionNames, final OAversion oaversion,
161 final boolean isForSingleModule)
163 final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
164 if (isForSingleModule) {
165 definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
167 return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
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 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
178 for (final DataSchemaNode node : module.getChildNodes()) {
179 stack.enterSchemaTree(node.getQName());
180 final String localName = node.getQName().getLocalName();
181 if (node.isConfiguration()) {
182 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
183 for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
184 final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
186 final String ref = getAppropriateModelPrefix(oaversion)
187 + moduleName + CONFIG
189 + definitionNames.getDiscriminator(node);
191 if (node instanceof ListSchemaNode) {
192 childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
193 final ObjectNode items = JsonNodeFactory.instance.objectNode();
194 items.put(REF_KEY, ref);
195 childNodeProperties.set(ITEMS_KEY, items);
196 childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
197 childNodeProperties.put(TITLE_KEY, localName + CONFIG);
200 Description can't be added, because nothing allowed alongside $ref.
201 allOf is not an option, because ServiceNow can't parse it.
203 childNodeProperties.put(REF_KEY, ref);
205 //add module name prefix to property name, when ServiceNow can process colons
206 properties.set(localName, childNodeProperties);
209 if (node instanceof LeafSchemaNode) {
211 Add module name prefix to property name, when ServiceNow can process colons(second parameter
214 processLeafNode((LeafSchemaNode) node, localName, properties, required, stack,
215 definitions, definitionNames, oaversion);
221 definition.put(TITLE_KEY, definitionName);
222 definition.put(TYPE_KEY, OBJECT_TYPE);
223 definition.set(PROPERTIES_KEY, properties);
224 definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
225 setRequiredIfNotEmpty(definition, required);
227 definitions.set(definitionName, definition);
230 private void processContainersAndLists(final Module module, final ObjectNode definitions,
231 final DefinitionNames definitionNames, final EffectiveModelContext schemaContext, final OAversion oaversion)
233 final String moduleName = module.getName();
234 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
235 for (final DataSchemaNode childNode : module.getChildNodes()) {
236 stack.enterSchemaTree(childNode.getQName());
237 // For every container and list in the module
238 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
239 if (childNode.isConfiguration()) {
240 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
241 true, stack, oaversion);
243 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
244 false, stack, oaversion);
245 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, stack, oaversion);
251 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
252 final ObjectNode definitions, final DefinitionNames definitionNames,
253 final SchemaInferenceStack stack, final OAversion oaversion)
255 for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
256 processOperations(actionDef, moduleName, definitions, definitionNames, stack, oaversion);
260 private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
261 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
262 final String moduleName = module.getName();
263 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
264 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
265 stack.enterSchemaTree(rpcDefinition.getQName());
266 processOperations(rpcDefinition, moduleName, definitions, definitionNames, stack, oaversion);
271 private void processOperations(final OperationDefinition operationDef, final String parentName,
272 final ObjectNode definitions, final DefinitionNames definitionNames,
273 final SchemaInferenceStack stack, final OAversion oaversion)
275 final String operationName = operationDef.getQName().getLocalName();
276 processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
277 definitionNames, stack, oaversion);
278 processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
279 definitionNames, stack, oaversion);
282 private void processOperationInputOutput(final ContainerLike container, final String operationName,
283 final String parentName, final boolean isInput,
284 final ObjectNode definitions, final DefinitionNames definitionNames,
285 final SchemaInferenceStack stack, final OAversion oaversion)
287 stack.enterSchemaTree(container.getQName());
288 if (!container.getChildNodes().isEmpty()) {
289 final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
290 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
291 processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
292 false, stack, oaversion);
294 childSchema.put(TYPE_KEY, OBJECT_TYPE);
295 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
296 xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
297 childSchema.set(XML_KEY, xml);
298 childSchema.put(TITLE_KEY, filename);
299 final String discriminator =
300 definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
301 definitions.set(filename + discriminator, childSchema);
303 processTopData(filename, discriminator, definitions, container, oaversion);
308 private static ObjectNode processTopData(final String filename, final String discriminator,
309 final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
310 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
311 final String name = filename + discriminator;
312 final String ref = getAppropriateModelPrefix(oaversion) + name;
313 final String topName = filename + TOP;
315 if (schemaNode instanceof ListSchemaNode) {
316 dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
317 final ObjectNode items = JsonNodeFactory.instance.objectNode();
318 items.put(REF_KEY, ref);
319 dataNodeProperties.set(ITEMS_KEY, items);
320 dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
323 Description can't be added, because nothing allowed alongside $ref.
324 allOf is not an option, because ServiceNow can't parse it.
326 dataNodeProperties.put(REF_KEY, ref);
329 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
331 Add module name prefix to property name, when needed, when ServiceNow can process colons,
332 use RestDocGenUtil#resolveNodesName for creating property name
334 properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
335 final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
336 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
337 finalChildSchema.set(PROPERTIES_KEY, properties);
338 finalChildSchema.put(TITLE_KEY, topName);
341 definitions.set(topName + discriminator, finalChildSchema);
343 return dataNodeProperties;
347 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
348 * @param module The module from which the identity stmt will be processed
349 * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
350 * @param definitionNames Store for definition names
352 private static void processIdentities(final Module module, final ObjectNode definitions,
353 final DefinitionNames definitionNames, final EffectiveModelContext context) {
354 final String moduleName = module.getName();
355 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
356 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
358 for (final IdentitySchemaNode idNode : idNodes) {
359 final ObjectNode identityObj = buildIdentityObject(idNode, context);
360 final String idName = idNode.getQName().getLocalName();
361 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
362 final String name = idName + discriminator;
363 definitions.set(name, identityObj);
367 private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
368 final ArrayNode enumPayload, final EffectiveModelContext context) {
369 for (final IdentitySchemaNode derivedId : derivedIds) {
370 enumPayload.add(derivedId.getQName().getLocalName());
371 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
375 private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
376 final ObjectNode definitions, final DefinitionNames definitionNames,
377 final boolean isConfig, final SchemaInferenceStack stack,
378 final OAversion oaversion) throws IOException {
379 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
380 final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
381 final SchemaNode schemaNode = (SchemaNode) dataNode;
382 final String localName = schemaNode.getQName().getLocalName();
383 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
384 final String nameAsParent = parentName + "_" + localName;
385 final ObjectNode properties =
386 processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
387 definitionNames, isConfig, stack, oaversion);
389 final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
390 final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
391 final String postXmlNodeName = postNodeName + XML_SUFFIX;
392 final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
394 final String description = schemaNode.getDescription().orElse("");
395 final String discriminator;
397 if (!definitionNames.isListedNode(schemaNode)) {
398 final List<String> names = List.of(parentNameConfigLocalName,
399 parentNameConfigLocalName + TOP,
404 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
406 discriminator = definitionNames.getDiscriminator(schemaNode);
410 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
411 String truePostNodeName = postNodeName + discriminator;
412 definitions.set(truePostNodeName, postSchema);
414 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
415 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
416 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
419 childSchema.put(TYPE_KEY, OBJECT_TYPE);
420 childSchema.set(PROPERTIES_KEY, properties);
421 childSchema.put(TITLE_KEY, nodeName);
422 childSchema.put(DESCRIPTION_KEY, description);
424 final String defName = nodeName + discriminator;
425 childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
426 definitions.set(defName, childSchema);
428 return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
433 private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
434 final String postNodeName, final String description) {
435 final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
436 final ObjectNode postItemProperties;
437 if (dataNode instanceof ListSchemaNode) {
438 postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
440 postItemProperties = properties.deepCopy();
442 postSchema.put(TYPE_KEY, OBJECT_TYPE);
443 postSchema.set(PROPERTIES_KEY, postItemProperties);
444 postSchema.put(TITLE_KEY, postNodeName);
445 postSchema.put(DESCRIPTION_KEY, description);
446 postSchema.set(XML_KEY, buildXmlParameter(dataNode));
450 private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
451 final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
452 final List<QName> keyDefinition = listNode.getKeyDefinition();
453 final Set<String> keys = listNode.getChildNodes().stream()
454 .filter(node -> keyDefinition.contains(node.getQName()))
455 .map(node -> node.getQName().getLocalName())
456 .collect(Collectors.toSet());
458 Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
459 while (it.hasNext()) {
460 Map.Entry<String, JsonNode> property = it.next();
461 if (!keys.contains(property.getKey())) {
462 postListItemProperties.set(property.getKey(), property.getValue());
466 return postListItemProperties;
470 * Processes the nodes.
472 private ObjectNode processChildren(
473 final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
474 final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
475 final SchemaInferenceStack stack, final OAversion oaversion) throws IOException {
476 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
477 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
478 for (final DataSchemaNode node : nodes) {
479 stack.enterSchemaTree(node.getQName());
480 if (!isConfig || node.isConfiguration()) {
482 Add module name prefix to property name, when needed, when ServiceNow can process colons,
483 use RestDocGenUtil#resolveNodesName for creating property name
485 final String propertyName = node.getQName().getLocalName();
486 final ObjectNode property;
487 if (node instanceof LeafSchemaNode) {
488 processLeafNode((LeafSchemaNode) node, propertyName, properties,
489 required, stack, definitions, definitionNames, oaversion);
490 } else if (node instanceof AnyxmlSchemaNode) {
491 processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
493 } else if (node instanceof AnydataSchemaNode) {
494 processAnydataNode((AnydataSchemaNode) node, propertyName, properties, required);
496 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
497 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
498 definitionNames, isConfig, stack, oaversion);
500 processActionNodeContainer(node, parentName, definitions, definitionNames, stack,
503 } else if (node instanceof LeafListSchemaNode) {
504 property = processLeafListNode((LeafListSchemaNode) node, stack, definitions,
505 definitionNames, oaversion);
507 } else if (node instanceof ChoiceSchemaNode) {
508 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
509 stack.enterSchemaTree(variant.getQName());
510 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
511 isConfig, stack, properties, oaversion);
515 // FIXME dangerous statement here! Try to rework without continue.
518 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
520 properties.set(propertyName, property);
525 parentNode.set(PROPERTIES_KEY, properties);
526 setRequiredIfNotEmpty(parentNode, required);
530 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack,
531 final ObjectNode definitions, final DefinitionNames definitionNames,
532 final OAversion oaversion) {
533 final ObjectNode props = JsonNodeFactory.instance.objectNode();
534 props.put(TYPE_KEY, ARRAY_TYPE);
536 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
537 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
538 processElementCount(optConstraint, props);
540 processTypeDef(listNode.getType(), listNode, itemsVal, stack, definitions, definitionNames, oaversion);
541 props.set(ITEMS_KEY, itemsVal);
543 props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
548 private void processChoiceNode(
549 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
550 final DefinitionNames definitionNames, final boolean isConfig,
551 final SchemaInferenceStack stack, final ObjectNode properties, final OAversion oaversion)
553 for (final DataSchemaNode node : nodes) {
554 stack.enterSchemaTree(node.getQName());
556 Add module name prefix to property name, when needed, when ServiceNow can process colons,
557 use RestDocGenUtil#resolveNodesName for creating property name
559 final String name = node.getQName().getLocalName();
560 final ObjectNode property;
563 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
566 if (node instanceof LeafSchemaNode) {
567 processLeafNode((LeafSchemaNode) node, name, properties,
568 JsonNodeFactory.instance.arrayNode(), stack, definitions, definitionNames, oaversion);
569 } else if (node instanceof AnyxmlSchemaNode) {
570 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
571 JsonNodeFactory.instance.arrayNode());
572 } else if (node instanceof AnydataSchemaNode) {
573 processAnydataNode((AnydataSchemaNode) node, name, properties,
574 JsonNodeFactory.instance.arrayNode());
576 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
577 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
578 definitionNames, isConfig, stack, oaversion);
580 processActionNodeContainer(node, parentName, definitions, definitionNames, stack,
583 } else if (node instanceof LeafListSchemaNode) {
584 property = processLeafListNode((LeafListSchemaNode) node, stack, definitions,
585 definitionNames, oaversion);
587 } else if (node instanceof ChoiceSchemaNode) {
588 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
589 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
590 stack, properties, oaversion);
594 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
596 properties.set(name, property);
602 private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
603 if (constraint.isPresent()) {
604 final ElementCountConstraint constr = constraint.get();
605 final Integer minElements = constr.getMinElements();
606 if (minElements != null) {
607 props.put(MIN_ITEMS, minElements);
609 final Integer maxElements = constr.getMaxElements();
610 if (maxElements != null) {
611 props.put(MAX_ITEMS, maxElements);
616 private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
617 if (node.isMandatory()) {
618 required.add(nodeName);
622 private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
623 final ObjectNode properties, final ArrayNode required,
624 final SchemaInferenceStack stack, final ObjectNode definitions,
625 final DefinitionNames definitionNames, final OAversion oaversion) {
626 final ObjectNode property = JsonNodeFactory.instance.objectNode();
628 final String leafDescription = leafNode.getDescription().orElse("");
630 Description can't be added, because nothing allowed alongside $ref.
631 allOf is not an option, because ServiceNow can't parse it.
633 if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
634 property.put(DESCRIPTION_KEY, leafDescription);
637 processTypeDef(leafNode.getType(), leafNode, property, stack, definitions, definitionNames, oaversion);
638 properties.set(jsonLeafName, property);
639 property.set(XML_KEY, buildXmlParameter(leafNode));
640 processMandatory(leafNode, jsonLeafName, required);
645 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
646 final ObjectNode properties, final ArrayNode required) {
647 final ObjectNode property = JsonNodeFactory.instance.objectNode();
649 final String leafDescription = leafNode.getDescription().orElse("");
650 property.put(DESCRIPTION_KEY, leafDescription);
652 final String localName = leafNode.getQName().getLocalName();
653 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
654 property.put(TYPE_KEY, STRING_TYPE);
655 property.set(XML_KEY, buildXmlParameter(leafNode));
656 processMandatory(leafNode, name, required);
657 properties.set(name, property);
662 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
663 final ObjectNode properties, final ArrayNode required) {
664 final ObjectNode property = JsonNodeFactory.instance.objectNode();
666 final String leafDescription = leafNode.getDescription().orElse("");
667 property.put(DESCRIPTION_KEY, leafDescription);
669 final String localName = leafNode.getQName().getLocalName();
670 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
671 property.put(TYPE_KEY, STRING_TYPE);
672 property.set(XML_KEY, buildXmlParameter(leafNode));
673 processMandatory(leafNode, name, required);
674 properties.set(name, property);
679 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
680 final ObjectNode property, final SchemaInferenceStack stack,
681 final ObjectNode definitions, final DefinitionNames definitionNames,
682 final OAversion oaversion) {
683 final String jsonType;
684 if (leafTypeDef instanceof BinaryTypeDefinition) {
685 jsonType = processBinaryType(property);
687 } else if (leafTypeDef instanceof BitsTypeDefinition) {
688 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
690 } else if (leafTypeDef instanceof EnumTypeDefinition) {
691 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
693 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
694 jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
695 definitionNames, oaversion, stack.getEffectiveModelContext());
697 } else if (leafTypeDef instanceof StringTypeDefinition) {
698 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
700 } else if (leafTypeDef instanceof UnionTypeDefinition) {
701 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
703 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
704 jsonType = OBJECT_TYPE;
705 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
706 return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
707 stack, definitions, definitionNames, oaversion);
708 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
709 jsonType = BOOLEAN_TYPE;
710 setDefaultValue(property, true);
711 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
712 jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
713 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
714 jsonType = processInstanceIdentifierType(node, property, stack.getEffectiveModelContext());
716 jsonType = STRING_TYPE;
718 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
719 putIfNonNull(property, TYPE_KEY, jsonType);
720 if (leafTypeDef.getDefaultValue().isPresent()) {
721 final Object defaultValue = leafTypeDef.getDefaultValue().get();
722 if (defaultValue instanceof String) {
723 final String stringDefaultValue = (String) defaultValue;
724 if (leafTypeDef instanceof BooleanTypeDefinition) {
725 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
726 } else if (leafTypeDef instanceof DecimalTypeDefinition
727 || leafTypeDef instanceof Uint64TypeDefinition) {
728 setDefaultValue(property, new BigDecimal(stringDefaultValue));
729 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
730 //uint8,16,32 int8,16,32,64
731 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
732 setDefaultValue(property, stringDefaultValue);
734 setDefaultValue(property, Long.valueOf(stringDefaultValue));
737 setDefaultValue(property, stringDefaultValue);
740 //we should never get here. getDefaultValue always gives us string
741 setDefaultValue(property, defaultValue.toString());
748 private static String processBinaryType(final ObjectNode property) {
749 property.put(FORMAT_KEY, "byte");
753 private static String processEnumType(final EnumTypeDefinition enumLeafType,
754 final ObjectNode property) {
755 final List<EnumPair> enumPairs = enumLeafType.getValues();
756 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
757 for (final EnumPair enumPair : enumPairs) {
758 enumNames.add(new TextNode(enumPair.getName()));
761 property.set(ENUM_KEY, enumNames);
762 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
766 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
767 final ObjectNode definitions, final DefinitionNames definitionNames,
768 final OAversion oaversion, final EffectiveModelContext schemaContext) {
769 final String definitionName;
770 if (isImported(leafTypeDef)) {
771 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
773 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
774 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
776 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
780 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
781 final ObjectNode definitions, final DefinitionNames definitionNames,
782 final EffectiveModelContext context) {
783 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
784 final String identityName = idNode.getQName().getLocalName();
785 if (!definitionNames.isListedNode(idNode)) {
786 final ObjectNode identityObj = buildIdentityObject(idNode, context);
787 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
788 final String name = identityName + discriminator;
789 definitions.set(name, identityObj);
792 return identityName + definitionNames.getDiscriminator(idNode);
796 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
797 final EffectiveModelContext context) {
798 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
799 final String identityName = idNode.getQName().getLocalName();
800 LOG.debug("Processing Identity: {}", identityName);
802 identityObj.put(TITLE_KEY, identityName);
803 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
805 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
807 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
808 enumPayload.add(identityName);
809 populateEnumWithDerived(derivedIds, enumPayload, context);
810 identityObj.set(ENUM_KEY, enumPayload);
811 identityObj.put(TYPE_KEY, STRING_TYPE);
815 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
816 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
819 private static String processBitsType(final BitsTypeDefinition bitsType,
820 final ObjectNode property) {
821 property.put(MIN_ITEMS, 0);
822 property.put(UNIQUE_ITEMS_KEY, true);
823 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
824 final Collection<? extends Bit> bits = bitsType.getBits();
825 for (final Bit bit : bits) {
826 enumNames.add(new TextNode(bit.getName()));
828 property.set(ENUM_KEY, enumNames);
829 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
833 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
834 final String nodeName) {
835 StringTypeDefinition type = (StringTypeDefinition) stringType;
836 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
837 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
838 type = type.getBaseType();
839 lengthConstraints = type.getLengthConstraint();
842 if (lengthConstraints.isPresent()) {
843 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
844 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
845 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
848 if (type.getPatternConstraints().iterator().hasNext()) {
849 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
850 String regex = pattern.getJavaPatternString();
851 regex = regex.substring(1, regex.length() - 1);
852 String defaultValue = "";
854 final Generex generex = new Generex(regex);
855 defaultValue = generex.random();
856 } catch (IllegalArgumentException ex) {
857 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
859 setDefaultValue(property, defaultValue);
861 setDefaultValue(property, "Some " + nodeName);
866 private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
867 final ObjectNode property) {
868 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
869 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
871 if (isHexadecimalOrOctal(leafTypeDef)) {
875 if (leafTypeDef instanceof DecimalTypeDefinition) {
876 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
879 if (leafTypeDef instanceof Uint8TypeDefinition
880 || leafTypeDef instanceof Uint16TypeDefinition
881 || leafTypeDef instanceof Int8TypeDefinition
882 || leafTypeDef instanceof Int16TypeDefinition
883 || leafTypeDef instanceof Int32TypeDefinition) {
885 property.put(FORMAT_KEY, INT32_FORMAT);
886 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
887 } else if (leafTypeDef instanceof Uint32TypeDefinition
888 || leafTypeDef instanceof Int64TypeDefinition) {
890 property.put(FORMAT_KEY, INT64_FORMAT);
891 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
894 setDefaultValue(property, 0);
899 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
900 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
901 if (optDefaultValue.isPresent()) {
902 final String defaultValue = (String)optDefaultValue.get();
903 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
908 private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
909 final EffectiveModelContext schemaContext) {
910 SchemaPath path = node.getPath();
912 while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
913 path = path.getParent();
916 final QName rootContainer = path.getLastComponent();
917 final String rootContainerName = rootContainer.getLocalName();
918 final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
919 setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
923 private static String processUnionType(final UnionTypeDefinition unionType) {
924 boolean isStringTakePlace = false;
925 boolean isNumberTakePlace = false;
926 boolean isBooleanTakePlace = false;
927 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
928 if (!isStringTakePlace) {
929 if (typeDef instanceof StringTypeDefinition
930 || typeDef instanceof BitsTypeDefinition
931 || typeDef instanceof BinaryTypeDefinition
932 || typeDef instanceof IdentityrefTypeDefinition
933 || typeDef instanceof EnumTypeDefinition
934 || typeDef instanceof LeafrefTypeDefinition
935 || typeDef instanceof UnionTypeDefinition) {
936 isStringTakePlace = true;
937 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
938 isNumberTakePlace = true;
939 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
940 isBooleanTakePlace = true;
944 if (isStringTakePlace) {
947 if (isBooleanTakePlace) {
948 if (isNumberTakePlace) {
956 private static ObjectNode buildXmlParameter(final SchemaNode node) {
957 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
958 final QName qName = node.getQName();
959 xml.put(NAME_KEY, qName.getLocalName());
960 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
964 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
965 if (key != null && number != null) {
966 if (number instanceof Double) {
967 property.put(key, (Double) number);
968 } else if (number instanceof Float) {
969 property.put(key, (Float) number);
970 } else if (number instanceof Integer) {
971 property.put(key, (Integer) number);
972 } else if (number instanceof Short) {
973 property.put(key, (Short) number);
974 } else if (number instanceof Long) {
975 property.put(key, (Long) number);
980 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
981 if (key != null && value != null) {
982 property.put(key, value);
986 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
987 if (required.size() > 0) {
988 node.set(REQUIRED_KEY, required);
992 private static void setDefaultValue(final ObjectNode property, final String value) {
993 property.put(DEFAULT_KEY, value);
996 private static void setDefaultValue(final ObjectNode property, final Integer value) {
997 property.put(DEFAULT_KEY, value);
1000 private static void setDefaultValue(final ObjectNode property, final Long value) {
1001 property.put(DEFAULT_KEY, value);
1004 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
1005 property.put(DEFAULT_KEY, value);
1008 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
1009 property.put(DEFAULT_KEY, value);