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 for (final DataSchemaNode node : module.getChildNodes()) {
178 final String localName = node.getQName().getLocalName();
179 if (node.isConfiguration()) {
180 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
181 for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
182 final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
184 final String ref = getAppropriateModelPrefix(oaversion)
185 + moduleName + CONFIG
187 + definitionNames.getDiscriminator(node);
189 if (node instanceof ListSchemaNode) {
190 childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
191 final ObjectNode items = JsonNodeFactory.instance.objectNode();
192 items.put(REF_KEY, ref);
193 childNodeProperties.set(ITEMS_KEY, items);
194 childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
195 childNodeProperties.put(TITLE_KEY, localName + CONFIG);
198 Description can't be added, because nothing allowed alongside $ref.
199 allOf is not an option, because ServiceNow can't parse it.
201 childNodeProperties.put(REF_KEY, ref);
203 //add module name prefix to property name, when ServiceNow can process colons
204 properties.set(localName, childNodeProperties);
207 if (node instanceof LeafSchemaNode) {
209 Add module name prefix to property name, when ServiceNow can process colons(second parameter
212 processLeafNode((LeafSchemaNode) node, localName, properties, required, schemaContext,
213 definitions, definitionNames, oaversion);
218 definition.put(TITLE_KEY, definitionName);
219 definition.put(TYPE_KEY, OBJECT_TYPE);
220 definition.set(PROPERTIES_KEY, properties);
221 definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
222 setRequiredIfNotEmpty(definition, required);
224 definitions.set(definitionName, definition);
227 private void processContainersAndLists(final Module module, final ObjectNode definitions,
228 final DefinitionNames definitionNames, final EffectiveModelContext schemaContext, final OAversion oaversion)
230 final String moduleName = module.getName();
232 for (final DataSchemaNode childNode : module.getChildNodes()) {
233 // For every container and list in the module
234 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
235 if (childNode.isConfiguration()) {
236 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
237 true, schemaContext, oaversion);
239 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
240 false, schemaContext, oaversion);
241 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, schemaContext,
247 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
248 final ObjectNode definitions, final DefinitionNames definitionNames,
249 final EffectiveModelContext schemaContext, final OAversion oaversion)
251 for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
252 processOperations(actionDef, moduleName, definitions, definitionNames, schemaContext, oaversion);
256 private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
257 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
258 final String moduleName = module.getName();
259 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
260 processOperations(rpcDefinition, moduleName, definitions, definitionNames, schemaContext, oaversion);
265 private void processOperations(final OperationDefinition operationDef, final String parentName,
266 final ObjectNode definitions, final DefinitionNames definitionNames,
267 final EffectiveModelContext schemaContext, final OAversion oaversion)
269 final String operationName = operationDef.getQName().getLocalName();
270 processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
271 definitionNames, schemaContext, oaversion);
272 processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
273 definitionNames, schemaContext, oaversion);
276 private void processOperationInputOutput(final ContainerLike container, final String operationName,
277 final String parentName, final boolean isInput,
278 final ObjectNode definitions, final DefinitionNames definitionNames,
279 final EffectiveModelContext schemaContext, final OAversion oaversion)
281 if (!container.getChildNodes().isEmpty()) {
282 final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
283 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
284 processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
285 false, schemaContext, oaversion);
287 childSchema.put(TYPE_KEY, OBJECT_TYPE);
288 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
289 xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
290 childSchema.set(XML_KEY, xml);
291 childSchema.put(TITLE_KEY, filename);
292 final String discriminator =
293 definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
294 definitions.set(filename + discriminator, childSchema);
296 processTopData(filename, discriminator, definitions, container, oaversion);
300 private static ObjectNode processTopData(final String filename, final String discriminator,
301 final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
302 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
303 final String name = filename + discriminator;
304 final String ref = getAppropriateModelPrefix(oaversion) + name;
305 final String topName = filename + TOP;
307 if (schemaNode instanceof ListSchemaNode) {
308 dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
309 final ObjectNode items = JsonNodeFactory.instance.objectNode();
310 items.put(REF_KEY, ref);
311 dataNodeProperties.set(ITEMS_KEY, items);
312 dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
315 Description can't be added, because nothing allowed alongside $ref.
316 allOf is not an option, because ServiceNow can't parse it.
318 dataNodeProperties.put(REF_KEY, ref);
321 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
323 Add module name prefix to property name, when needed, when ServiceNow can process colons,
324 use RestDocGenUtil#resolveNodesName for creating property name
326 properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
327 final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
328 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
329 finalChildSchema.set(PROPERTIES_KEY, properties);
330 finalChildSchema.put(TITLE_KEY, topName);
333 definitions.set(topName + discriminator, finalChildSchema);
335 return dataNodeProperties;
339 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
340 * @param module The module from which the identity stmt will be processed
341 * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
342 * @param definitionNames Store for definition names
344 private static void processIdentities(final Module module, final ObjectNode definitions,
345 final DefinitionNames definitionNames, final EffectiveModelContext context) {
347 final String moduleName = module.getName();
348 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
349 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
351 for (final IdentitySchemaNode idNode : idNodes) {
352 final ObjectNode identityObj = buildIdentityObject(idNode, context);
353 final String idName = idNode.getQName().getLocalName();
354 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
355 final String name = idName + discriminator;
356 definitions.set(name, identityObj);
360 private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
361 final ArrayNode enumPayload, final EffectiveModelContext context) {
362 for (final IdentitySchemaNode derivedId : derivedIds) {
363 enumPayload.add(derivedId.getQName().getLocalName());
364 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
368 private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
369 final ObjectNode definitions, final DefinitionNames definitionNames,
370 final boolean isConfig, final EffectiveModelContext schemaContext,
371 final OAversion oaversion) throws IOException {
372 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
373 final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
374 final SchemaNode schemaNode = (SchemaNode) dataNode;
375 final String localName = schemaNode.getQName().getLocalName();
376 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
377 final String nameAsParent = parentName + "_" + localName;
378 final ObjectNode properties =
379 processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
380 definitionNames, isConfig, schemaContext, oaversion);
382 final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
383 final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
384 final String postXmlNodeName = postNodeName + XML_SUFFIX;
385 final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
387 final String description = schemaNode.getDescription().orElse("");
388 final String discriminator;
390 if (!definitionNames.isListedNode(schemaNode)) {
391 final List<String> names = List.of(parentNameConfigLocalName,
392 parentNameConfigLocalName + TOP,
397 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
399 discriminator = definitionNames.getDiscriminator(schemaNode);
403 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
404 String truePostNodeName = postNodeName + discriminator;
405 definitions.set(truePostNodeName, postSchema);
407 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
408 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
409 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
412 childSchema.put(TYPE_KEY, OBJECT_TYPE);
413 childSchema.set(PROPERTIES_KEY, properties);
414 childSchema.put(TITLE_KEY, nodeName);
415 childSchema.put(DESCRIPTION_KEY, description);
417 final String defName = nodeName + discriminator;
418 childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
419 definitions.set(defName, childSchema);
421 return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
426 private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
427 final String postNodeName, final String description) {
428 final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
429 final ObjectNode postItemProperties;
430 if (dataNode instanceof ListSchemaNode) {
431 postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
433 postItemProperties = properties.deepCopy();
435 postSchema.put(TYPE_KEY, OBJECT_TYPE);
436 postSchema.set(PROPERTIES_KEY, postItemProperties);
437 postSchema.put(TITLE_KEY, postNodeName);
438 postSchema.put(DESCRIPTION_KEY, description);
439 postSchema.set(XML_KEY, buildXmlParameter(dataNode));
443 private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
444 final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
445 final List<QName> keyDefinition = listNode.getKeyDefinition();
446 final Set<String> keys = listNode.getChildNodes().stream()
447 .filter(node -> keyDefinition.contains(node.getQName()))
448 .map(node -> node.getQName().getLocalName())
449 .collect(Collectors.toSet());
451 Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
452 while (it.hasNext()) {
453 Map.Entry<String, JsonNode> property = it.next();
454 if (!keys.contains(property.getKey())) {
455 postListItemProperties.set(property.getKey(), property.getValue());
459 return postListItemProperties;
463 * Processes the nodes.
465 private ObjectNode processChildren(
466 final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
467 final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
468 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
469 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
470 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
471 for (final DataSchemaNode node : nodes) {
472 if (!isConfig || node.isConfiguration()) {
474 Add module name prefix to property name, when needed, when ServiceNow can process colons,
475 use RestDocGenUtil#resolveNodesName for creating property name
477 final String propertyName = node.getQName().getLocalName();
478 final ObjectNode property;
479 if (node instanceof LeafSchemaNode) {
480 processLeafNode((LeafSchemaNode) node, propertyName, properties,
481 required, schemaContext, definitions, definitionNames, oaversion);
482 } else if (node instanceof AnyxmlSchemaNode) {
483 processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
485 } else if (node instanceof AnydataSchemaNode) {
486 processAnydataNode((AnydataSchemaNode) node, propertyName, properties,
489 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
490 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
491 definitionNames, isConfig, schemaContext, oaversion);
493 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
496 } else if (node instanceof LeafListSchemaNode) {
497 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
498 definitionNames, oaversion);
500 } else if (node instanceof ChoiceSchemaNode) {
501 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
502 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
503 isConfig, schemaContext, properties, oaversion);
508 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
510 properties.set(propertyName, property);
514 parentNode.set(PROPERTIES_KEY, properties);
515 setRequiredIfNotEmpty(parentNode, required);
519 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final EffectiveModelContext schemaContext,
520 final ObjectNode definitions, final DefinitionNames definitionNames,
521 final OAversion oaversion) {
522 final ObjectNode props = JsonNodeFactory.instance.objectNode();
523 props.put(TYPE_KEY, ARRAY_TYPE);
525 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
526 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
527 processElementCount(optConstraint, props);
529 processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext, definitions, definitionNames, oaversion);
530 props.set(ITEMS_KEY, itemsVal);
532 props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
537 private void processChoiceNode(
538 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
539 final DefinitionNames definitionNames, final boolean isConfig,
540 final EffectiveModelContext schemaContext, final ObjectNode properties, final OAversion oaversion)
542 for (final DataSchemaNode node : nodes) {
544 Add module name prefix to property name, when needed, when ServiceNow can process colons,
545 use RestDocGenUtil#resolveNodesName for creating property name
547 final String name = node.getQName().getLocalName();
548 final ObjectNode property;
551 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
554 if (node instanceof LeafSchemaNode) {
555 processLeafNode((LeafSchemaNode) node, name, properties,
556 JsonNodeFactory.instance.arrayNode(), schemaContext, definitions, definitionNames, oaversion);
557 } else if (node instanceof AnyxmlSchemaNode) {
558 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
559 JsonNodeFactory.instance.arrayNode());
560 } else if (node instanceof AnydataSchemaNode) {
561 processAnydataNode((AnydataSchemaNode) node, name, properties,
562 JsonNodeFactory.instance.arrayNode());
564 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
565 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
566 definitionNames, isConfig, schemaContext, oaversion);
568 processActionNodeContainer(node, parentName, definitions, definitionNames, schemaContext,
571 } else if (node instanceof LeafListSchemaNode) {
572 property = processLeafListNode((LeafListSchemaNode) node, schemaContext, definitions,
573 definitionNames, oaversion);
575 } else if (node instanceof ChoiceSchemaNode) {
576 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
577 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
578 schemaContext, properties, oaversion);
582 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
584 properties.set(name, property);
589 private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
590 if (constraint.isPresent()) {
591 final ElementCountConstraint constr = constraint.get();
592 final Integer minElements = constr.getMinElements();
593 if (minElements != null) {
594 props.put(MIN_ITEMS, minElements);
596 final Integer maxElements = constr.getMaxElements();
597 if (maxElements != null) {
598 props.put(MAX_ITEMS, maxElements);
603 private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
604 if (node.isMandatory()) {
605 required.add(nodeName);
609 private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
610 final ObjectNode properties, final ArrayNode required,
611 final EffectiveModelContext schemaContext, final ObjectNode definitions,
612 final DefinitionNames definitionNames, final OAversion oaversion) {
613 final ObjectNode property = JsonNodeFactory.instance.objectNode();
615 final String leafDescription = leafNode.getDescription().orElse("");
617 Description can't be added, because nothing allowed alongside $ref.
618 allOf is not an option, because ServiceNow can't parse it.
620 if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
621 property.put(DESCRIPTION_KEY, leafDescription);
624 processTypeDef(leafNode.getType(), leafNode, property, schemaContext, definitions, definitionNames, oaversion);
625 properties.set(jsonLeafName, property);
626 property.set(XML_KEY, buildXmlParameter(leafNode));
627 processMandatory(leafNode, jsonLeafName, required);
632 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
633 final ObjectNode properties, final ArrayNode required) {
634 final ObjectNode property = JsonNodeFactory.instance.objectNode();
636 final String leafDescription = leafNode.getDescription().orElse("");
637 property.put(DESCRIPTION_KEY, leafDescription);
639 final String localName = leafNode.getQName().getLocalName();
640 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
641 property.put(TYPE_KEY, STRING_TYPE);
642 property.set(XML_KEY, buildXmlParameter(leafNode));
643 processMandatory(leafNode, name, required);
644 properties.set(name, property);
649 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
650 final ObjectNode properties, final ArrayNode required) {
651 final ObjectNode property = JsonNodeFactory.instance.objectNode();
653 final String leafDescription = leafNode.getDescription().orElse("");
654 property.put(DESCRIPTION_KEY, leafDescription);
656 final String localName = leafNode.getQName().getLocalName();
657 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
658 property.put(TYPE_KEY, STRING_TYPE);
659 property.set(XML_KEY, buildXmlParameter(leafNode));
660 processMandatory(leafNode, name, required);
661 properties.set(name, property);
666 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
667 final ObjectNode property, final EffectiveModelContext schemaContext,
668 final ObjectNode definitions, final DefinitionNames definitionNames,
669 final OAversion oaversion) {
670 final String jsonType;
671 if (leafTypeDef instanceof BinaryTypeDefinition) {
672 jsonType = processBinaryType(property);
674 } else if (leafTypeDef instanceof BitsTypeDefinition) {
675 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
677 } else if (leafTypeDef instanceof EnumTypeDefinition) {
678 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
680 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
681 jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
682 definitionNames, oaversion, schemaContext);
684 } else if (leafTypeDef instanceof StringTypeDefinition) {
685 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
687 } else if (leafTypeDef instanceof UnionTypeDefinition) {
688 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
690 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
691 jsonType = OBJECT_TYPE;
692 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
693 final SchemaInferenceStack stack = SchemaInferenceStack.ofSchemaPath(schemaContext, node.getPath());
694 return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
695 schemaContext, definitions, definitionNames, oaversion);
696 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
697 jsonType = BOOLEAN_TYPE;
698 setDefaultValue(property, true);
699 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
700 jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
701 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
702 jsonType = processInstanceIdentifierType(node, property, schemaContext);
704 jsonType = STRING_TYPE;
706 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
707 putIfNonNull(property, TYPE_KEY, jsonType);
708 if (leafTypeDef.getDefaultValue().isPresent()) {
709 final Object defaultValue = leafTypeDef.getDefaultValue().get();
710 if (defaultValue instanceof String) {
711 final String stringDefaultValue = (String) defaultValue;
712 if (leafTypeDef instanceof BooleanTypeDefinition) {
713 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
714 } else if (leafTypeDef instanceof DecimalTypeDefinition
715 || leafTypeDef instanceof Uint64TypeDefinition) {
716 setDefaultValue(property, new BigDecimal(stringDefaultValue));
717 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
718 //uint8,16,32 int8,16,32,64
719 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
720 setDefaultValue(property, stringDefaultValue);
722 setDefaultValue(property, Long.valueOf(stringDefaultValue));
725 setDefaultValue(property, stringDefaultValue);
728 //we should never get here. getDefaultValue always gives us string
729 setDefaultValue(property, defaultValue.toString());
736 private static String processBinaryType(final ObjectNode property) {
737 property.put(FORMAT_KEY, "byte");
741 private static String processEnumType(final EnumTypeDefinition enumLeafType,
742 final ObjectNode property) {
743 final List<EnumPair> enumPairs = enumLeafType.getValues();
744 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
745 for (final EnumPair enumPair : enumPairs) {
746 enumNames.add(new TextNode(enumPair.getName()));
749 property.set(ENUM_KEY, enumNames);
750 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
754 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
755 final ObjectNode definitions, final DefinitionNames definitionNames,
756 final OAversion oaversion, final EffectiveModelContext schemaContext) {
757 final String definitionName;
758 if (isImported(leafTypeDef)) {
759 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
761 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
762 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
764 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
768 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
769 final ObjectNode definitions, final DefinitionNames definitionNames,
770 final EffectiveModelContext context) {
771 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
772 final String identityName = idNode.getQName().getLocalName();
773 if (!definitionNames.isListedNode(idNode)) {
774 final ObjectNode identityObj = buildIdentityObject(idNode, context);
775 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
776 final String name = identityName + discriminator;
777 definitions.set(name, identityObj);
780 return identityName + definitionNames.getDiscriminator(idNode);
784 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
785 final EffectiveModelContext context) {
786 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
787 final String identityName = idNode.getQName().getLocalName();
788 LOG.debug("Processing Identity: {}", identityName);
790 identityObj.put(TITLE_KEY, identityName);
791 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
793 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
795 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
796 enumPayload.add(identityName);
797 populateEnumWithDerived(derivedIds, enumPayload, context);
798 identityObj.set(ENUM_KEY, enumPayload);
799 identityObj.put(TYPE_KEY, STRING_TYPE);
803 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
804 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
807 private static String processBitsType(final BitsTypeDefinition bitsType,
808 final ObjectNode property) {
809 property.put(MIN_ITEMS, 0);
810 property.put(UNIQUE_ITEMS_KEY, true);
811 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
812 final Collection<? extends Bit> bits = bitsType.getBits();
813 for (final Bit bit : bits) {
814 enumNames.add(new TextNode(bit.getName()));
816 property.set(ENUM_KEY, enumNames);
817 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
821 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
822 final String nodeName) {
823 StringTypeDefinition type = (StringTypeDefinition) stringType;
824 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
825 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
826 type = type.getBaseType();
827 lengthConstraints = type.getLengthConstraint();
830 if (lengthConstraints.isPresent()) {
831 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
832 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
833 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
836 if (type.getPatternConstraints().iterator().hasNext()) {
837 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
838 String regex = pattern.getJavaPatternString();
839 regex = regex.substring(1, regex.length() - 1);
840 String defaultValue = "";
842 final Generex generex = new Generex(regex);
843 defaultValue = generex.random();
844 } catch (IllegalArgumentException ex) {
845 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
847 setDefaultValue(property, defaultValue);
849 setDefaultValue(property, "Some " + nodeName);
854 private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
855 final ObjectNode property) {
856 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
857 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
859 if (isHexadecimalOrOctal(leafTypeDef)) {
863 if (leafTypeDef instanceof DecimalTypeDefinition) {
864 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
867 if (leafTypeDef instanceof Uint8TypeDefinition
868 || leafTypeDef instanceof Uint16TypeDefinition
869 || leafTypeDef instanceof Int8TypeDefinition
870 || leafTypeDef instanceof Int16TypeDefinition
871 || leafTypeDef instanceof Int32TypeDefinition) {
873 property.put(FORMAT_KEY, INT32_FORMAT);
874 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
875 } else if (leafTypeDef instanceof Uint32TypeDefinition
876 || leafTypeDef instanceof Int64TypeDefinition) {
878 property.put(FORMAT_KEY, INT64_FORMAT);
879 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
882 setDefaultValue(property, 0);
887 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
888 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
889 if (optDefaultValue.isPresent()) {
890 final String defaultValue = (String)optDefaultValue.get();
891 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
896 private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
897 final EffectiveModelContext schemaContext) {
898 SchemaPath path = node.getPath();
900 while (path.getParent() != null && path.getParent().getPathFromRoot().iterator().hasNext()) {
901 path = path.getParent();
904 final QName rootContainer = path.getLastComponent();
905 final String rootContainerName = rootContainer.getLocalName();
906 final String prefix = schemaContext.findModule(rootContainer.getModule()).get().getPrefix();
907 setDefaultValue(property, String.format("/%s:%s", prefix, rootContainerName));
911 private static String processUnionType(final UnionTypeDefinition unionType) {
912 boolean isStringTakePlace = false;
913 boolean isNumberTakePlace = false;
914 boolean isBooleanTakePlace = false;
915 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
916 if (!isStringTakePlace) {
917 if (typeDef instanceof StringTypeDefinition
918 || typeDef instanceof BitsTypeDefinition
919 || typeDef instanceof BinaryTypeDefinition
920 || typeDef instanceof IdentityrefTypeDefinition
921 || typeDef instanceof EnumTypeDefinition
922 || typeDef instanceof LeafrefTypeDefinition
923 || typeDef instanceof UnionTypeDefinition) {
924 isStringTakePlace = true;
925 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
926 isNumberTakePlace = true;
927 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
928 isBooleanTakePlace = true;
932 if (isStringTakePlace) {
935 if (isBooleanTakePlace) {
936 if (isNumberTakePlace) {
944 private static ObjectNode buildXmlParameter(final SchemaNode node) {
945 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
946 final QName qName = node.getQName();
947 xml.put(NAME_KEY, qName.getLocalName());
948 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
952 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
953 if (key != null && number != null) {
954 if (number instanceof Double) {
955 property.put(key, (Double) number);
956 } else if (number instanceof Float) {
957 property.put(key, (Float) number);
958 } else if (number instanceof Integer) {
959 property.put(key, (Integer) number);
960 } else if (number instanceof Short) {
961 property.put(key, (Short) number);
962 } else if (number instanceof Long) {
963 property.put(key, (Long) number);
968 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
969 if (key != null && value != null) {
970 property.put(key, value);
974 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
975 if (required.size() > 0) {
976 node.set(REQUIRED_KEY, required);
980 private static void setDefaultValue(final ObjectNode property, final String value) {
981 property.put(DEFAULT_KEY, value);
984 private static void setDefaultValue(final ObjectNode property, final Integer value) {
985 property.put(DEFAULT_KEY, value);
988 private static void setDefaultValue(final ObjectNode property, final Long value) {
989 property.put(DEFAULT_KEY, value);
992 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
993 property.put(DEFAULT_KEY, value);
996 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
997 property.put(DEFAULT_KEY, value);