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.TypeDefinition;
60 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
61 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
62 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
63 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
64 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
65 import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
66 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
67 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
68 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
69 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
70 import org.opendaylight.yangtools.yang.model.api.type.Int16TypeDefinition;
71 import org.opendaylight.yangtools.yang.model.api.type.Int32TypeDefinition;
72 import org.opendaylight.yangtools.yang.model.api.type.Int64TypeDefinition;
73 import org.opendaylight.yangtools.yang.model.api.type.Int8TypeDefinition;
74 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
75 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
76 import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
77 import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
78 import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition;
79 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
80 import org.opendaylight.yangtools.yang.model.api.type.Uint16TypeDefinition;
81 import org.opendaylight.yangtools.yang.model.api.type.Uint32TypeDefinition;
82 import org.opendaylight.yangtools.yang.model.api.type.Uint64TypeDefinition;
83 import org.opendaylight.yangtools.yang.model.api.type.Uint8TypeDefinition;
84 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
85 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
90 * Generates JSON Schema for data defined in YANG. This class is not thread-safe.
92 public class DefinitionGenerator {
94 private static final Logger LOG = LoggerFactory.getLogger(DefinitionGenerator.class);
96 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
97 private static final String MAX_ITEMS = "maxItems";
98 private static final String MIN_ITEMS = "minItems";
99 private static final String MAX_LENGTH_KEY = "maxLength";
100 private static final String MIN_LENGTH_KEY = "minLength";
101 private static final String REQUIRED_KEY = "required";
102 private static final String REF_KEY = "$ref";
103 private static final String ITEMS_KEY = "items";
104 private static final String TYPE_KEY = "type";
105 private static final String PROPERTIES_KEY = "properties";
106 private static final String DESCRIPTION_KEY = "description";
107 private static final String ARRAY_TYPE = "array";
108 private static final String ENUM_KEY = "enum";
109 private static final String TITLE_KEY = "title";
110 private static final String DEFAULT_KEY = "default";
111 private static final String FORMAT_KEY = "format";
112 private static final String NAMESPACE_KEY = "namespace";
113 public static final String INPUT = "input";
114 public static final String INPUT_SUFFIX = "_input";
115 public static final String OUTPUT = "output";
116 public static final String OUTPUT_SUFFIX = "_output";
117 private static final String STRING_TYPE = "string";
118 private static final String OBJECT_TYPE = "object";
119 private static final String NUMBER_TYPE = "number";
120 private static final String INTEGER_TYPE = "integer";
121 private static final String INT32_FORMAT = "int32";
122 private static final String INT64_FORMAT = "int64";
123 private static final String BOOLEAN_TYPE = "boolean";
125 private Module topLevelModule;
127 public DefinitionGenerator() {
131 * Creates Json definitions from provided module according to swagger spec.
133 * @param module - Yang module to be converted
134 * @param schemaContext - SchemaContext of all Yang files used by Api Doc
135 * @param definitionNames - Store for definition names
136 * @return ObjectNode containing data used for creating examples and definitions in Api Doc
137 * @throws IOException if I/O operation fails
141 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
142 final ObjectNode definitions, final DefinitionNames definitionNames,
143 final OAversion oaversion, final boolean isForSingleModule)
145 topLevelModule = module;
147 processIdentities(module, definitions, definitionNames, schemaContext);
148 processContainersAndLists(module, definitions, definitionNames, schemaContext, oaversion);
149 processRPCs(module, definitions, definitionNames, schemaContext, oaversion);
151 if (isForSingleModule) {
152 processModule(module, definitions, definitionNames, schemaContext, oaversion);
158 public ObjectNode convertToJsonSchema(final Module module, final EffectiveModelContext schemaContext,
159 final DefinitionNames definitionNames, final OAversion oaversion,
160 final boolean isForSingleModule)
162 final ObjectNode definitions = JsonNodeFactory.instance.objectNode();
163 if (isForSingleModule) {
164 definitionNames.addUnlinkedName(module.getName() + MODULE_NAME_SUFFIX);
166 return convertToJsonSchema(module, schemaContext, definitions, definitionNames, oaversion, isForSingleModule);
169 private void processModule(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
170 final EffectiveModelContext schemaContext, final OAversion oaversion) {
171 final ObjectNode definition = JsonNodeFactory.instance.objectNode();
172 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
173 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
174 final String moduleName = module.getName();
175 final String definitionName = moduleName + MODULE_NAME_SUFFIX;
176 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
177 for (final DataSchemaNode node : module.getChildNodes()) {
178 stack.enterSchemaTree(node.getQName());
179 final String localName = node.getQName().getLocalName();
180 if (node.isConfiguration()) {
181 if (node instanceof ContainerSchemaNode || node instanceof ListSchemaNode) {
182 for (final DataSchemaNode childNode : ((DataNodeContainer) node).getChildNodes()) {
183 final ObjectNode childNodeProperties = JsonNodeFactory.instance.objectNode();
185 final String ref = getAppropriateModelPrefix(oaversion)
186 + moduleName + CONFIG
188 + definitionNames.getDiscriminator(node);
190 if (node instanceof ListSchemaNode) {
191 childNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
192 final ObjectNode items = JsonNodeFactory.instance.objectNode();
193 items.put(REF_KEY, ref);
194 childNodeProperties.set(ITEMS_KEY, items);
195 childNodeProperties.put(DESCRIPTION_KEY, childNode.getDescription().orElse(""));
196 childNodeProperties.put(TITLE_KEY, localName + CONFIG);
199 Description can't be added, because nothing allowed alongside $ref.
200 allOf is not an option, because ServiceNow can't parse it.
202 childNodeProperties.put(REF_KEY, ref);
204 //add module name prefix to property name, when ServiceNow can process colons
205 properties.set(localName, childNodeProperties);
208 if (node instanceof LeafSchemaNode) {
210 Add module name prefix to property name, when ServiceNow can process colons(second parameter
213 processLeafNode((LeafSchemaNode) node, localName, properties, required, stack,
214 definitions, definitionNames, oaversion);
220 definition.put(TITLE_KEY, definitionName);
221 definition.put(TYPE_KEY, OBJECT_TYPE);
222 definition.set(PROPERTIES_KEY, properties);
223 definition.put(DESCRIPTION_KEY, module.getDescription().orElse(""));
224 setRequiredIfNotEmpty(definition, required);
226 definitions.set(definitionName, definition);
229 private void processContainersAndLists(final Module module, final ObjectNode definitions,
230 final DefinitionNames definitionNames, final EffectiveModelContext schemaContext, final OAversion oaversion)
232 final String moduleName = module.getName();
233 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
234 for (final DataSchemaNode childNode : module.getChildNodes()) {
235 stack.enterSchemaTree(childNode.getQName());
236 // For every container and list in the module
237 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
238 if (childNode.isConfiguration()) {
239 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
240 true, stack, oaversion);
242 processDataNodeContainer((DataNodeContainer) childNode, moduleName, definitions, definitionNames,
243 false, stack, oaversion);
244 processActionNodeContainer(childNode, moduleName, definitions, definitionNames, stack, oaversion);
250 private void processActionNodeContainer(final DataSchemaNode childNode, final String moduleName,
251 final ObjectNode definitions, final DefinitionNames definitionNames,
252 final SchemaInferenceStack stack, final OAversion oaversion)
254 for (final ActionDefinition actionDef : ((ActionNodeContainer) childNode).getActions()) {
255 processOperations(actionDef, moduleName, definitions, definitionNames, stack, oaversion);
259 private void processRPCs(final Module module, final ObjectNode definitions, final DefinitionNames definitionNames,
260 final EffectiveModelContext schemaContext, final OAversion oaversion) throws IOException {
261 final String moduleName = module.getName();
262 final SchemaInferenceStack stack = SchemaInferenceStack.of(schemaContext);
263 for (final RpcDefinition rpcDefinition : module.getRpcs()) {
264 stack.enterSchemaTree(rpcDefinition.getQName());
265 processOperations(rpcDefinition, moduleName, definitions, definitionNames, stack, oaversion);
270 private void processOperations(final OperationDefinition operationDef, final String parentName,
271 final ObjectNode definitions, final DefinitionNames definitionNames,
272 final SchemaInferenceStack stack, final OAversion oaversion)
274 final String operationName = operationDef.getQName().getLocalName();
275 processOperationInputOutput(operationDef.getInput(), operationName, parentName, true, definitions,
276 definitionNames, stack, oaversion);
277 processOperationInputOutput(operationDef.getOutput(), operationName, parentName, false, definitions,
278 definitionNames, stack, oaversion);
281 private void processOperationInputOutput(final ContainerLike container, final String operationName,
282 final String parentName, final boolean isInput,
283 final ObjectNode definitions, final DefinitionNames definitionNames,
284 final SchemaInferenceStack stack, final OAversion oaversion)
286 stack.enterSchemaTree(container.getQName());
287 if (!container.getChildNodes().isEmpty()) {
288 final String filename = parentName + "_" + operationName + (isInput ? INPUT_SUFFIX : OUTPUT_SUFFIX);
289 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
290 processChildren(childSchema, container.getChildNodes(), parentName, definitions, definitionNames,
291 false, stack, oaversion);
293 childSchema.put(TYPE_KEY, OBJECT_TYPE);
294 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
295 xml.put(NAME_KEY, isInput ? INPUT : OUTPUT);
296 childSchema.set(XML_KEY, xml);
297 childSchema.put(TITLE_KEY, filename);
298 final String discriminator =
299 definitionNames.pickDiscriminator(container, List.of(filename, filename + TOP));
300 definitions.set(filename + discriminator, childSchema);
302 processTopData(filename, discriminator, definitions, container, oaversion);
307 private static ObjectNode processTopData(final String filename, final String discriminator,
308 final ObjectNode definitions, final SchemaNode schemaNode, final OAversion oaversion) {
309 final ObjectNode dataNodeProperties = JsonNodeFactory.instance.objectNode();
310 final String name = filename + discriminator;
311 final String ref = getAppropriateModelPrefix(oaversion) + name;
312 final String topName = filename + TOP;
314 if (schemaNode instanceof ListSchemaNode) {
315 dataNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
316 final ObjectNode items = JsonNodeFactory.instance.objectNode();
317 items.put(REF_KEY, ref);
318 dataNodeProperties.set(ITEMS_KEY, items);
319 dataNodeProperties.put(DESCRIPTION_KEY, schemaNode.getDescription().orElse(""));
322 Description can't be added, because nothing allowed alongside $ref.
323 allOf is not an option, because ServiceNow can't parse it.
325 dataNodeProperties.put(REF_KEY, ref);
328 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
330 Add module name prefix to property name, when needed, when ServiceNow can process colons,
331 use RestDocGenUtil#resolveNodesName for creating property name
333 properties.set(schemaNode.getQName().getLocalName(), dataNodeProperties);
334 final ObjectNode finalChildSchema = JsonNodeFactory.instance.objectNode();
335 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
336 finalChildSchema.set(PROPERTIES_KEY, properties);
337 finalChildSchema.put(TITLE_KEY, topName);
340 definitions.set(topName + discriminator, finalChildSchema);
342 return dataNodeProperties;
346 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
347 * @param module The module from which the identity stmt will be processed
348 * @param definitions The ObjectNode in which the parsed identity will be put as a 'model' obj
349 * @param definitionNames Store for definition names
351 private static void processIdentities(final Module module, final ObjectNode definitions,
352 final DefinitionNames definitionNames, final EffectiveModelContext context) {
353 final String moduleName = module.getName();
354 final Collection<? extends IdentitySchemaNode> idNodes = module.getIdentities();
355 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
357 for (final IdentitySchemaNode idNode : idNodes) {
358 final ObjectNode identityObj = buildIdentityObject(idNode, context);
359 final String idName = idNode.getQName().getLocalName();
360 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(idName));
361 final String name = idName + discriminator;
362 definitions.set(name, identityObj);
366 private static void populateEnumWithDerived(final Collection<? extends IdentitySchemaNode> derivedIds,
367 final ArrayNode enumPayload, final EffectiveModelContext context) {
368 for (final IdentitySchemaNode derivedId : derivedIds) {
369 enumPayload.add(derivedId.getQName().getLocalName());
370 populateEnumWithDerived(context.getDerivedIdentities(derivedId), enumPayload, context);
374 private ObjectNode processDataNodeContainer(final DataNodeContainer dataNode, final String parentName,
375 final ObjectNode definitions, final DefinitionNames definitionNames,
376 final boolean isConfig, final SchemaInferenceStack stack,
377 final OAversion oaversion) throws IOException {
378 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
379 final Collection<? extends DataSchemaNode> containerChildren = dataNode.getChildNodes();
380 final SchemaNode schemaNode = (SchemaNode) dataNode;
381 final String localName = schemaNode.getQName().getLocalName();
382 final ObjectNode childSchema = JsonNodeFactory.instance.objectNode();
383 final String nameAsParent = parentName + "_" + localName;
384 final ObjectNode properties =
385 processChildren(childSchema, containerChildren, parentName + "_" + localName, definitions,
386 definitionNames, isConfig, stack, oaversion);
388 final String nodeName = parentName + (isConfig ? CONFIG : "") + "_" + localName;
389 final String postNodeName = parentName + CONFIG + "_" + localName + POST_SUFFIX;
390 final String postXmlNodeName = postNodeName + XML_SUFFIX;
391 final String parentNameConfigLocalName = parentName + CONFIG + "_" + localName;
393 final String description = schemaNode.getDescription().orElse("");
394 final String discriminator;
396 if (!definitionNames.isListedNode(schemaNode)) {
397 final List<String> names = List.of(parentNameConfigLocalName,
398 parentNameConfigLocalName + TOP,
403 discriminator = definitionNames.pickDiscriminator(schemaNode, names);
405 discriminator = definitionNames.getDiscriminator(schemaNode);
409 final ObjectNode postSchema = createPostJsonSchema(schemaNode, properties, postNodeName, description);
410 String truePostNodeName = postNodeName + discriminator;
411 definitions.set(truePostNodeName, postSchema);
413 final ObjectNode postXmlSchema = JsonNodeFactory.instance.objectNode();
414 postXmlSchema.put(REF_KEY, getAppropriateModelPrefix(oaversion) + truePostNodeName);
415 definitions.set(postXmlNodeName + discriminator, postXmlSchema);
418 childSchema.put(TYPE_KEY, OBJECT_TYPE);
419 childSchema.set(PROPERTIES_KEY, properties);
420 childSchema.put(TITLE_KEY, nodeName);
421 childSchema.put(DESCRIPTION_KEY, description);
423 final String defName = nodeName + discriminator;
424 childSchema.set(XML_KEY, buildXmlParameter(schemaNode));
425 definitions.set(defName, childSchema);
427 return processTopData(nodeName, discriminator, definitions, schemaNode, oaversion);
432 private static ObjectNode createPostJsonSchema(final SchemaNode dataNode, final ObjectNode properties,
433 final String postNodeName, final String description) {
434 final ObjectNode postSchema = JsonNodeFactory.instance.objectNode();
435 final ObjectNode postItemProperties;
436 if (dataNode instanceof ListSchemaNode) {
437 postItemProperties = createListItemProperties(properties, (ListSchemaNode) dataNode);
439 postItemProperties = properties.deepCopy();
441 postSchema.put(TYPE_KEY, OBJECT_TYPE);
442 postSchema.set(PROPERTIES_KEY, postItemProperties);
443 postSchema.put(TITLE_KEY, postNodeName);
444 postSchema.put(DESCRIPTION_KEY, description);
445 postSchema.set(XML_KEY, buildXmlParameter(dataNode));
449 private static ObjectNode createListItemProperties(final ObjectNode properties, final ListSchemaNode listNode) {
450 final ObjectNode postListItemProperties = JsonNodeFactory.instance.objectNode();
451 final List<QName> keyDefinition = listNode.getKeyDefinition();
452 final Set<String> keys = listNode.getChildNodes().stream()
453 .filter(node -> keyDefinition.contains(node.getQName()))
454 .map(node -> node.getQName().getLocalName())
455 .collect(Collectors.toSet());
457 Iterator<Map.Entry<String, JsonNode>> it = properties.fields();
458 while (it.hasNext()) {
459 Map.Entry<String, JsonNode> property = it.next();
460 if (!keys.contains(property.getKey())) {
461 postListItemProperties.set(property.getKey(), property.getValue());
465 return postListItemProperties;
469 * Processes the nodes.
471 private ObjectNode processChildren(
472 final ObjectNode parentNode, final Collection<? extends DataSchemaNode> nodes, final String parentName,
473 final ObjectNode definitions, final DefinitionNames definitionNames, final boolean isConfig,
474 final SchemaInferenceStack stack, final OAversion oaversion) throws IOException {
475 final ObjectNode properties = JsonNodeFactory.instance.objectNode();
476 final ArrayNode required = JsonNodeFactory.instance.arrayNode();
477 for (final DataSchemaNode node : nodes) {
478 stack.enterSchemaTree(node.getQName());
479 if (!isConfig || node.isConfiguration()) {
481 Add module name prefix to property name, when needed, when ServiceNow can process colons,
482 use RestDocGenUtil#resolveNodesName for creating property name
484 final String propertyName = node.getQName().getLocalName();
485 final ObjectNode property;
486 if (node instanceof LeafSchemaNode) {
487 processLeafNode((LeafSchemaNode) node, propertyName, properties,
488 required, stack, definitions, definitionNames, oaversion);
489 } else if (node instanceof AnyxmlSchemaNode) {
490 processAnyXMLNode((AnyxmlSchemaNode) node, propertyName, properties,
492 } else if (node instanceof AnydataSchemaNode) {
493 processAnydataNode((AnydataSchemaNode) node, propertyName, properties, required);
495 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
496 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
497 definitionNames, isConfig, stack, oaversion);
499 processActionNodeContainer(node, parentName, definitions, definitionNames, stack,
502 } else if (node instanceof LeafListSchemaNode) {
503 property = processLeafListNode((LeafListSchemaNode) node, stack, definitions,
504 definitionNames, oaversion);
506 } else if (node instanceof ChoiceSchemaNode) {
507 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
508 stack.enterSchemaTree(variant.getQName());
509 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames,
510 isConfig, stack, properties, oaversion);
514 // FIXME dangerous statement here! Try to rework without continue.
517 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
519 properties.set(propertyName, property);
524 parentNode.set(PROPERTIES_KEY, properties);
525 setRequiredIfNotEmpty(parentNode, required);
529 private ObjectNode processLeafListNode(final LeafListSchemaNode listNode, final SchemaInferenceStack stack,
530 final ObjectNode definitions, final DefinitionNames definitionNames,
531 final OAversion oaversion) {
532 final ObjectNode props = JsonNodeFactory.instance.objectNode();
533 props.put(TYPE_KEY, ARRAY_TYPE);
535 final ObjectNode itemsVal = JsonNodeFactory.instance.objectNode();
536 final Optional<ElementCountConstraint> optConstraint = listNode.getElementCountConstraint();
537 processElementCount(optConstraint, props);
539 processTypeDef(listNode.getType(), listNode, itemsVal, stack, definitions, definitionNames, oaversion);
540 props.set(ITEMS_KEY, itemsVal);
542 props.put(DESCRIPTION_KEY, listNode.getDescription().orElse(""));
547 private void processChoiceNode(
548 final Iterable<? extends DataSchemaNode> nodes, final String parentName, final ObjectNode definitions,
549 final DefinitionNames definitionNames, final boolean isConfig,
550 final SchemaInferenceStack stack, final ObjectNode properties, final OAversion oaversion)
552 for (final DataSchemaNode node : nodes) {
553 stack.enterSchemaTree(node.getQName());
555 Add module name prefix to property name, when needed, when ServiceNow can process colons,
556 use RestDocGenUtil#resolveNodesName for creating property name
558 final String name = node.getQName().getLocalName();
559 final ObjectNode property;
562 Ignore mandatoriness(passing unreferenced arrayNode to process...Node), because choice produces multiple
565 if (node instanceof LeafSchemaNode) {
566 processLeafNode((LeafSchemaNode) node, name, properties,
567 JsonNodeFactory.instance.arrayNode(), stack, definitions, definitionNames, oaversion);
568 } else if (node instanceof AnyxmlSchemaNode) {
569 processAnyXMLNode((AnyxmlSchemaNode) node, name, properties,
570 JsonNodeFactory.instance.arrayNode());
571 } else if (node instanceof AnydataSchemaNode) {
572 processAnydataNode((AnydataSchemaNode) node, name, properties,
573 JsonNodeFactory.instance.arrayNode());
575 if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
576 property = processDataNodeContainer((DataNodeContainer) node, parentName, definitions,
577 definitionNames, isConfig, stack, oaversion);
579 processActionNodeContainer(node, parentName, definitions, definitionNames, stack,
582 } else if (node instanceof LeafListSchemaNode) {
583 property = processLeafListNode((LeafListSchemaNode) node, stack, definitions,
584 definitionNames, oaversion);
586 } else if (node instanceof ChoiceSchemaNode) {
587 for (final CaseSchemaNode variant : ((ChoiceSchemaNode) node).getCases()) {
588 processChoiceNode(variant.getChildNodes(), parentName, definitions, definitionNames, isConfig,
589 stack, properties, oaversion);
593 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
595 properties.set(name, property);
601 private static void processElementCount(final Optional<ElementCountConstraint> constraint, final ObjectNode props) {
602 if (constraint.isPresent()) {
603 final ElementCountConstraint constr = constraint.get();
604 final Integer minElements = constr.getMinElements();
605 if (minElements != null) {
606 props.put(MIN_ITEMS, minElements);
608 final Integer maxElements = constr.getMaxElements();
609 if (maxElements != null) {
610 props.put(MAX_ITEMS, maxElements);
615 private static void processMandatory(final MandatoryAware node, final String nodeName, final ArrayNode required) {
616 if (node.isMandatory()) {
617 required.add(nodeName);
621 private ObjectNode processLeafNode(final LeafSchemaNode leafNode, final String jsonLeafName,
622 final ObjectNode properties, final ArrayNode required,
623 final SchemaInferenceStack stack, final ObjectNode definitions,
624 final DefinitionNames definitionNames, final OAversion oaversion) {
625 final ObjectNode property = JsonNodeFactory.instance.objectNode();
627 final String leafDescription = leafNode.getDescription().orElse("");
629 Description can't be added, because nothing allowed alongside $ref.
630 allOf is not an option, because ServiceNow can't parse it.
632 if (!(leafNode.getType() instanceof IdentityrefTypeDefinition)) {
633 property.put(DESCRIPTION_KEY, leafDescription);
636 processTypeDef(leafNode.getType(), leafNode, property, stack, definitions, definitionNames, oaversion);
637 properties.set(jsonLeafName, property);
638 property.set(XML_KEY, buildXmlParameter(leafNode));
639 processMandatory(leafNode, jsonLeafName, required);
644 private static ObjectNode processAnydataNode(final AnydataSchemaNode leafNode, final String name,
645 final ObjectNode properties, final ArrayNode required) {
646 final ObjectNode property = JsonNodeFactory.instance.objectNode();
648 final String leafDescription = leafNode.getDescription().orElse("");
649 property.put(DESCRIPTION_KEY, leafDescription);
651 final String localName = leafNode.getQName().getLocalName();
652 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
653 property.put(TYPE_KEY, STRING_TYPE);
654 property.set(XML_KEY, buildXmlParameter(leafNode));
655 processMandatory(leafNode, name, required);
656 properties.set(name, property);
661 private static ObjectNode processAnyXMLNode(final AnyxmlSchemaNode leafNode, final String name,
662 final ObjectNode properties, final ArrayNode required) {
663 final ObjectNode property = JsonNodeFactory.instance.objectNode();
665 final String leafDescription = leafNode.getDescription().orElse("");
666 property.put(DESCRIPTION_KEY, leafDescription);
668 final String localName = leafNode.getQName().getLocalName();
669 setDefaultValue(property, String.format("<%s> ... </%s>", localName, localName));
670 property.put(TYPE_KEY, STRING_TYPE);
671 property.set(XML_KEY, buildXmlParameter(leafNode));
672 processMandatory(leafNode, name, required);
673 properties.set(name, property);
678 private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
679 final ObjectNode property, final SchemaInferenceStack stack,
680 final ObjectNode definitions, final DefinitionNames definitionNames,
681 final OAversion oaversion) {
682 final String jsonType;
683 if (leafTypeDef instanceof BinaryTypeDefinition) {
684 jsonType = processBinaryType(property);
686 } else if (leafTypeDef instanceof BitsTypeDefinition) {
687 jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
689 } else if (leafTypeDef instanceof EnumTypeDefinition) {
690 jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
692 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
693 jsonType = processIdentityRefType((IdentityrefTypeDefinition) leafTypeDef, property, definitions,
694 definitionNames, oaversion, stack.getEffectiveModelContext());
696 } else if (leafTypeDef instanceof StringTypeDefinition) {
697 jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
699 } else if (leafTypeDef instanceof UnionTypeDefinition) {
700 jsonType = processUnionType((UnionTypeDefinition) leafTypeDef);
702 } else if (leafTypeDef instanceof EmptyTypeDefinition) {
703 jsonType = OBJECT_TYPE;
704 } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
705 return processTypeDef(stack.resolveLeafref((LeafrefTypeDefinition) leafTypeDef), node, property,
706 stack, definitions, definitionNames, oaversion);
707 } else if (leafTypeDef instanceof BooleanTypeDefinition) {
708 jsonType = BOOLEAN_TYPE;
709 setDefaultValue(property, true);
710 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
711 jsonType = processNumberType((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef, property);
712 } else if (leafTypeDef instanceof InstanceIdentifierTypeDefinition) {
713 jsonType = processInstanceIdentifierType(node, property, stack.getEffectiveModelContext());
715 jsonType = STRING_TYPE;
717 if (!(leafTypeDef instanceof IdentityrefTypeDefinition)) {
718 putIfNonNull(property, TYPE_KEY, jsonType);
719 if (leafTypeDef.getDefaultValue().isPresent()) {
720 final Object defaultValue = leafTypeDef.getDefaultValue().get();
721 if (defaultValue instanceof String) {
722 final String stringDefaultValue = (String) defaultValue;
723 if (leafTypeDef instanceof BooleanTypeDefinition) {
724 setDefaultValue(property, Boolean.valueOf(stringDefaultValue));
725 } else if (leafTypeDef instanceof DecimalTypeDefinition
726 || leafTypeDef instanceof Uint64TypeDefinition) {
727 setDefaultValue(property, new BigDecimal(stringDefaultValue));
728 } else if (leafTypeDef instanceof RangeRestrictedTypeDefinition) {
729 //uint8,16,32 int8,16,32,64
730 if (isHexadecimalOrOctal((RangeRestrictedTypeDefinition<?, ?>)leafTypeDef)) {
731 setDefaultValue(property, stringDefaultValue);
733 setDefaultValue(property, Long.valueOf(stringDefaultValue));
736 setDefaultValue(property, stringDefaultValue);
739 //we should never get here. getDefaultValue always gives us string
740 setDefaultValue(property, defaultValue.toString());
747 private static String processBinaryType(final ObjectNode property) {
748 property.put(FORMAT_KEY, "byte");
752 private static String processEnumType(final EnumTypeDefinition enumLeafType,
753 final ObjectNode property) {
754 final List<EnumPair> enumPairs = enumLeafType.getValues();
755 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
756 for (final EnumPair enumPair : enumPairs) {
757 enumNames.add(new TextNode(enumPair.getName()));
760 property.set(ENUM_KEY, enumNames);
761 setDefaultValue(property, enumLeafType.getValues().iterator().next().getName());
765 private String processIdentityRefType(final IdentityrefTypeDefinition leafTypeDef, final ObjectNode property,
766 final ObjectNode definitions, final DefinitionNames definitionNames,
767 final OAversion oaversion, final EffectiveModelContext schemaContext) {
768 final String definitionName;
769 if (isImported(leafTypeDef)) {
770 definitionName = addImportedIdentity(leafTypeDef, definitions, definitionNames, schemaContext);
772 final SchemaNode node = leafTypeDef.getIdentities().iterator().next();
773 definitionName = node.getQName().getLocalName() + definitionNames.getDiscriminator(node);
775 property.put(REF_KEY, getAppropriateModelPrefix(oaversion) + definitionName);
779 private static String addImportedIdentity(final IdentityrefTypeDefinition leafTypeDef,
780 final ObjectNode definitions, final DefinitionNames definitionNames,
781 final EffectiveModelContext context) {
782 final IdentitySchemaNode idNode = leafTypeDef.getIdentities().iterator().next();
783 final String identityName = idNode.getQName().getLocalName();
784 if (!definitionNames.isListedNode(idNode)) {
785 final ObjectNode identityObj = buildIdentityObject(idNode, context);
786 final String discriminator = definitionNames.pickDiscriminator(idNode, List.of(identityName));
787 final String name = identityName + discriminator;
788 definitions.set(name, identityObj);
791 return identityName + definitionNames.getDiscriminator(idNode);
795 private static ObjectNode buildIdentityObject(final IdentitySchemaNode idNode,
796 final EffectiveModelContext context) {
797 final ObjectNode identityObj = JsonNodeFactory.instance.objectNode();
798 final String identityName = idNode.getQName().getLocalName();
799 LOG.debug("Processing Identity: {}", identityName);
801 identityObj.put(TITLE_KEY, identityName);
802 identityObj.put(DESCRIPTION_KEY, idNode.getDescription().orElse(""));
804 final Collection<? extends IdentitySchemaNode> derivedIds = context.getDerivedIdentities(idNode);
806 final ArrayNode enumPayload = JsonNodeFactory.instance.arrayNode();
807 enumPayload.add(identityName);
808 populateEnumWithDerived(derivedIds, enumPayload, context);
809 identityObj.set(ENUM_KEY, enumPayload);
810 identityObj.put(TYPE_KEY, STRING_TYPE);
814 private boolean isImported(final IdentityrefTypeDefinition leafTypeDef) {
815 return !leafTypeDef.getQName().getModule().equals(topLevelModule.getQNameModule());
818 private static String processBitsType(final BitsTypeDefinition bitsType,
819 final ObjectNode property) {
820 property.put(MIN_ITEMS, 0);
821 property.put(UNIQUE_ITEMS_KEY, true);
822 final ArrayNode enumNames = new ArrayNode(JsonNodeFactory.instance);
823 final Collection<? extends Bit> bits = bitsType.getBits();
824 for (final Bit bit : bits) {
825 enumNames.add(new TextNode(bit.getName()));
827 property.set(ENUM_KEY, enumNames);
828 property.put(DEFAULT_KEY, enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1));
832 private static String processStringType(final TypeDefinition<?> stringType, final ObjectNode property,
833 final String nodeName) {
834 StringTypeDefinition type = (StringTypeDefinition) stringType;
835 Optional<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraint();
836 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
837 type = type.getBaseType();
838 lengthConstraints = type.getLengthConstraint();
841 if (lengthConstraints.isPresent()) {
842 final Range<Integer> range = lengthConstraints.get().getAllowedRanges().span();
843 putIfNonNull(property, MIN_LENGTH_KEY, range.lowerEndpoint());
844 putIfNonNull(property, MAX_LENGTH_KEY, range.upperEndpoint());
847 if (type.getPatternConstraints().iterator().hasNext()) {
848 final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
849 String regex = pattern.getJavaPatternString();
850 regex = regex.substring(1, regex.length() - 1);
851 String defaultValue = "";
853 final Generex generex = new Generex(regex);
854 defaultValue = generex.random();
855 } catch (IllegalArgumentException ex) {
856 LOG.warn("Cannot create example string for type: {} with regex: {}.", stringType.getQName(), regex);
858 setDefaultValue(property, defaultValue);
860 setDefaultValue(property, "Some " + nodeName);
865 private static String processNumberType(final RangeRestrictedTypeDefinition<?, ?> leafTypeDef,
866 final ObjectNode property) {
867 final Optional<Number> maybeLower = ((RangeRestrictedTypeDefinition<?, ?>) leafTypeDef).getRangeConstraint()
868 .map(RangeConstraint::getAllowedRanges).map(RangeSet::span).map(Range::lowerEndpoint);
870 if (isHexadecimalOrOctal(leafTypeDef)) {
874 if (leafTypeDef instanceof DecimalTypeDefinition) {
875 maybeLower.ifPresent(number -> setDefaultValue(property, (BigDecimal) number));
878 if (leafTypeDef instanceof Uint8TypeDefinition
879 || leafTypeDef instanceof Uint16TypeDefinition
880 || leafTypeDef instanceof Int8TypeDefinition
881 || leafTypeDef instanceof Int16TypeDefinition
882 || leafTypeDef instanceof Int32TypeDefinition) {
884 property.put(FORMAT_KEY, INT32_FORMAT);
885 maybeLower.ifPresent(number -> setDefaultValue(property, Integer.valueOf(number.toString())));
886 } else if (leafTypeDef instanceof Uint32TypeDefinition
887 || leafTypeDef instanceof Int64TypeDefinition) {
889 property.put(FORMAT_KEY, INT64_FORMAT);
890 maybeLower.ifPresent(number -> setDefaultValue(property, Long.valueOf(number.toString())));
893 setDefaultValue(property, 0);
898 private static boolean isHexadecimalOrOctal(final RangeRestrictedTypeDefinition<?, ?> typeDef) {
899 final Optional<?> optDefaultValue = typeDef.getDefaultValue();
900 if (optDefaultValue.isPresent()) {
901 final String defaultValue = (String)optDefaultValue.get();
902 return defaultValue.startsWith("0") || defaultValue.startsWith("-0");
907 private static String processInstanceIdentifierType(final DataSchemaNode node, final ObjectNode property,
908 final EffectiveModelContext schemaContext) {
909 // create example instance-identifier to the first container of node's module if exists or leave it empty
910 final var module = schemaContext.findModule(node.getQName().getModule());
911 if (module.isPresent()) {
912 final var container = module.get().getChildNodes().stream()
913 .filter(n -> n instanceof ContainerSchemaNode)
915 container.ifPresent(c -> setDefaultValue(property, String.format("/%s:%s", module.get().getPrefix(),
916 c.getQName().getLocalName())));
922 private static String processUnionType(final UnionTypeDefinition unionType) {
923 boolean isStringTakePlace = false;
924 boolean isNumberTakePlace = false;
925 boolean isBooleanTakePlace = false;
926 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
927 if (!isStringTakePlace) {
928 if (typeDef instanceof StringTypeDefinition
929 || typeDef instanceof BitsTypeDefinition
930 || typeDef instanceof BinaryTypeDefinition
931 || typeDef instanceof IdentityrefTypeDefinition
932 || typeDef instanceof EnumTypeDefinition
933 || typeDef instanceof LeafrefTypeDefinition
934 || typeDef instanceof UnionTypeDefinition) {
935 isStringTakePlace = true;
936 } else if (!isNumberTakePlace && typeDef instanceof RangeRestrictedTypeDefinition) {
937 isNumberTakePlace = true;
938 } else if (!isBooleanTakePlace && typeDef instanceof BooleanTypeDefinition) {
939 isBooleanTakePlace = true;
943 if (isStringTakePlace) {
946 if (isBooleanTakePlace) {
947 if (isNumberTakePlace) {
955 private static ObjectNode buildXmlParameter(final SchemaNode node) {
956 final ObjectNode xml = JsonNodeFactory.instance.objectNode();
957 final QName qName = node.getQName();
958 xml.put(NAME_KEY, qName.getLocalName());
959 xml.put(NAMESPACE_KEY, qName.getNamespace().toString());
963 private static void putIfNonNull(final ObjectNode property, final String key, final Number number) {
964 if (key != null && number != null) {
965 if (number instanceof Double) {
966 property.put(key, (Double) number);
967 } else if (number instanceof Float) {
968 property.put(key, (Float) number);
969 } else if (number instanceof Integer) {
970 property.put(key, (Integer) number);
971 } else if (number instanceof Short) {
972 property.put(key, (Short) number);
973 } else if (number instanceof Long) {
974 property.put(key, (Long) number);
979 private static void putIfNonNull(final ObjectNode property, final String key, final String value) {
980 if (key != null && value != null) {
981 property.put(key, value);
985 private static void setRequiredIfNotEmpty(final ObjectNode node, final ArrayNode required) {
986 if (required.size() > 0) {
987 node.set(REQUIRED_KEY, required);
991 private static void setDefaultValue(final ObjectNode property, final String value) {
992 property.put(DEFAULT_KEY, value);
995 private static void setDefaultValue(final ObjectNode property, final Integer value) {
996 property.put(DEFAULT_KEY, value);
999 private static void setDefaultValue(final ObjectNode property, final Long value) {
1000 property.put(DEFAULT_KEY, value);
1003 private static void setDefaultValue(final ObjectNode property, final BigDecimal value) {
1004 property.put(DEFAULT_KEY, value);
1007 private static void setDefaultValue(final ObjectNode property, final Boolean value) {
1008 property.put(DEFAULT_KEY, value);