2 * Copyright (c) 2014 Cisco Systems, Inc. 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.util.RestDocgenUtil.resolveNodesName;
12 import com.google.common.base.Preconditions;
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.List;
17 import javax.annotation.concurrent.NotThreadSafe;
18 import org.json.JSONArray;
19 import org.json.JSONException;
20 import org.json.JSONObject;
21 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder;
22 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post;
23 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
25 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
27 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
29 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.Module;
35 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
36 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
37 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
40 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
41 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
42 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
44 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
46 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
49 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
56 * Generates JSON Schema for data defined in YANG.
59 public class ModelGenerator {
61 private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class);
63 private static final String BASE_64 = "base64";
64 private static final String BINARY_ENCODING_KEY = "binaryEncoding";
65 private static final String MEDIA_KEY = "media";
66 private static final String ONE_OF_KEY = "oneOf";
67 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
68 private static final String MAX_ITEMS = "maxItems";
69 private static final String MIN_ITEMS = "minItems";
70 private static final String SCHEMA_URL = "http://json-schema.org/draft-04/schema";
71 private static final String SCHEMA_KEY = "$schema";
72 private static final String MAX_LENGTH_KEY = "maxLength";
73 private static final String MIN_LENGTH_KEY = "minLength";
74 private static final String REQUIRED_KEY = "required";
75 private static final String REF_KEY = "$ref";
76 private static final String ITEMS_KEY = "items";
77 private static final String TYPE_KEY = "type";
78 private static final String PROPERTIES_KEY = "properties";
79 private static final String DESCRIPTION_KEY = "description";
80 private static final String OBJECT_TYPE = "object";
81 private static final String ARRAY_TYPE = "array";
82 private static final String ENUM = "enum";
83 private static final String INTEGER = "integer";
84 private static final String NUMBER = "number";
85 private static final String BOOLEAN = "boolean";
86 private static final String STRING = "string";
87 private static final String ID_KEY = "id";
88 private static final String SUB_TYPES_KEY = "subTypes";
90 private Module topLevelModule;
92 public ModelGenerator() {
95 private static String jsonTypeFor(final TypeDefinition<?> type) {
96 if (type instanceof BooleanTypeDefinition) {
98 } else if (type instanceof DecimalTypeDefinition) {
100 } else if (type instanceof EnumTypeDefinition) {
102 } else if (type instanceof IntegerTypeDefinition) {
104 } else if (type instanceof UnsignedIntegerTypeDefinition) {
106 } else if (type instanceof StringTypeDefinition) {
114 public JSONObject convertToJsonSchema(final Module module, final SchemaContext schemaContext) throws IOException, JSONException {
115 final JSONObject models = new JSONObject();
116 topLevelModule = module;
117 processModules(module, models);
118 processContainersAndLists(module, models, schemaContext);
119 processRPCs(module, models, schemaContext);
120 processIdentities(module, models);
124 private void processModules(final Module module, final JSONObject models) throws JSONException {
125 createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX, createPropertiesForPost(module));
128 private void processContainersAndLists(final Module module, final JSONObject models, final SchemaContext schemaContext)
129 throws IOException, JSONException {
131 final String moduleName = module.getName();
133 for (final DataSchemaNode childNode : module.getChildNodes()) {
134 // For every container and list in the module
135 if (childNode instanceof ContainerSchemaNode || childNode instanceof ListSchemaNode) {
136 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, true, schemaContext);
137 processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, false, schemaContext);
143 * Process the RPCs for a Module Spits out a file each of the name <rpcName>-input.json and <rpcName>-output.json
144 * for each RPC that contains input & output elements
147 * @throws JSONException
148 * @throws IOException
150 private void processRPCs(final Module module, final JSONObject models, final SchemaContext schemaContext) throws JSONException,
152 final Set<RpcDefinition> rpcs = module.getRpcs();
153 final String moduleName = module.getName();
154 for (final RpcDefinition rpc : rpcs) {
155 final ContainerSchemaNode input = rpc.getInput();
157 final JSONObject properties = processChildren(input.getChildNodes(), moduleName, models, true, schemaContext);
159 final String filename = "(" + rpc.getQName().getLocalName() + ")input";
160 final JSONObject childSchema = getSchemaTemplate();
161 childSchema.put(TYPE_KEY, OBJECT_TYPE);
162 childSchema.put(PROPERTIES_KEY, properties);
163 childSchema.put("id", filename);
164 models.put(filename, childSchema);
166 processTopData(filename, models, input);
169 final ContainerSchemaNode output = rpc.getOutput();
170 if (output != null) {
171 final JSONObject properties = processChildren(output.getChildNodes(), moduleName, models, true, schemaContext);
172 final String filename = "(" + rpc.getQName().getLocalName() + ")output";
173 final JSONObject childSchema = getSchemaTemplate();
174 childSchema.put(TYPE_KEY, OBJECT_TYPE);
175 childSchema.put(PROPERTIES_KEY, properties);
176 childSchema.put("id", filename);
177 models.put(filename, childSchema);
179 processTopData(filename, models, output);
184 private static JSONObject processTopData(final String filename, final JSONObject models, final SchemaNode schemaNode) {
185 final JSONObject items = new JSONObject();
187 items.put(REF_KEY, filename);
188 final JSONObject dataNodeProperties = new JSONObject();
189 dataNodeProperties.put(TYPE_KEY, schemaNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
190 dataNodeProperties.put(ITEMS_KEY, items);
192 dataNodeProperties.putOpt(DESCRIPTION_KEY, schemaNode.getDescription());
193 final JSONObject properties = new JSONObject();
194 properties.put(schemaNode.getQName().getLocalName(), dataNodeProperties);
195 final JSONObject finalChildSchema = getSchemaTemplate();
196 finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
197 finalChildSchema.put(PROPERTIES_KEY, properties);
198 finalChildSchema.put(ID_KEY, filename + OperationBuilder.TOP);
199 models.put(filename + OperationBuilder.TOP, finalChildSchema);
201 return dataNodeProperties;
205 * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
208 * The module from which the identity stmt will be processed
210 * The JSONObject in which the parsed identity will be put as a 'model' obj
212 private static void processIdentities(final Module module, final JSONObject models) throws JSONException {
214 final String moduleName = module.getName();
215 final Set<IdentitySchemaNode> idNodes = module.getIdentities();
216 LOG.debug("Processing Identities for module {} . Found {} identity statements", moduleName, idNodes.size());
218 for (final IdentitySchemaNode idNode : idNodes) {
219 final JSONObject identityObj = new JSONObject();
220 final String identityName = idNode.getQName().getLocalName();
221 LOG.debug("Processing Identity: {}", identityName);
223 identityObj.put(ID_KEY, identityName);
224 identityObj.put(DESCRIPTION_KEY, idNode.getDescription());
226 final JSONObject props = new JSONObject();
227 final IdentitySchemaNode baseId = idNode.getBaseIdentity();
229 if (baseId == null) {
231 * This is a base identity. So lets see if it has sub types. If it does, then add them to the model
234 final Set<IdentitySchemaNode> derivedIds = idNode.getDerivedIdentities();
236 if (derivedIds != null) {
237 final JSONArray subTypes = new JSONArray();
238 for (final IdentitySchemaNode derivedId : derivedIds) {
239 subTypes.put(derivedId.getQName().getLocalName());
241 identityObj.put(SUB_TYPES_KEY, subTypes);
245 * This is a derived entity. Add it's base type & move on.
247 props.put(TYPE_KEY, baseId.getQName().getLocalName());
250 // Add the properties. For a base type, this will be an empty object as required by the Swagger spec.
251 identityObj.put(PROPERTIES_KEY, props);
252 models.put(identityName, identityObj);
256 private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String moduleName, final JSONObject models,
257 final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException {
258 if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
259 Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node");
260 final Iterable<DataSchemaNode> containerChildren = dataNode.getChildNodes();
261 final JSONObject properties = processChildren(containerChildren, moduleName, models, isConfig, schemaContext);
263 final String nodeName = (isConfig ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL)
264 + ((SchemaNode) dataNode).getQName().getLocalName();
266 final JSONObject childSchema = getSchemaTemplate();
267 childSchema.put(TYPE_KEY, OBJECT_TYPE);
268 childSchema.put(PROPERTIES_KEY, properties);
270 childSchema.put("id", nodeName);
271 models.put(nodeName, childSchema);
274 createConcreteModelForPost(models, ((SchemaNode) dataNode).getQName().getLocalName(),
275 createPropertiesForPost(dataNode));
278 return processTopData(nodeName, models, (SchemaNode) dataNode);
283 private static void createConcreteModelForPost(final JSONObject models, final String localName,
284 final JSONObject properties) throws JSONException {
285 final String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME;
286 final JSONObject postSchema = getSchemaTemplate();
287 postSchema.put(TYPE_KEY, OBJECT_TYPE);
288 postSchema.put("id", nodePostName);
289 postSchema.put(PROPERTIES_KEY, properties);
290 models.put(nodePostName, postSchema);
293 private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer) throws JSONException {
294 final JSONObject properties = new JSONObject();
295 for (final DataSchemaNode childNode : dataNodeContainer.getChildNodes()) {
296 if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
297 final JSONObject items = new JSONObject();
298 items.put(REF_KEY, "(config)" + childNode.getQName().getLocalName());
299 final JSONObject property = new JSONObject();
300 property.put(TYPE_KEY, childNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE);
301 property.put(ITEMS_KEY, items);
302 properties.put(childNode.getQName().getLocalName(), property);
303 } else if (childNode instanceof LeafSchemaNode) {
304 final JSONObject property = processLeafNode((LeafSchemaNode) childNode);
305 properties.put(childNode.getQName().getLocalName(), property);
311 private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final String moduleName,
312 final JSONObject models, final SchemaContext schemaContext) throws JSONException, IOException {
313 return processChildren(nodes, moduleName, models, true, schemaContext);
317 * Processes the nodes.
319 private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final String moduleName, final JSONObject models,
320 final boolean isConfig, final SchemaContext schemaContext)
321 throws JSONException, IOException {
322 final JSONObject properties = new JSONObject();
323 for (final DataSchemaNode node : nodes) {
324 if (node.isConfiguration() == isConfig) {
325 final String name = resolveNodesName(node, topLevelModule, schemaContext);
326 final JSONObject property;
327 if (node instanceof LeafSchemaNode) {
328 property = processLeafNode((LeafSchemaNode) node);
330 } else if (node instanceof ListSchemaNode) {
331 property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig,
334 } else if (node instanceof LeafListSchemaNode) {
335 property = processLeafListNode((LeafListSchemaNode) node);
337 } else if (node instanceof ChoiceSchemaNode) {
338 property = processChoiceNode((ChoiceSchemaNode) node, moduleName, models, schemaContext);
340 } else if (node instanceof AnyXmlSchemaNode) {
341 property = processAnyXMLNode((AnyXmlSchemaNode) node);
343 } else if (node instanceof ContainerSchemaNode) {
344 property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig,
348 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
350 property.putOpt(DESCRIPTION_KEY, node.getDescription());
351 properties.put(name, property);
357 private JSONObject processLeafListNode(final LeafListSchemaNode listNode) throws JSONException {
358 final JSONObject props = new JSONObject();
359 props.put(TYPE_KEY, ARRAY_TYPE);
361 final JSONObject itemsVal = new JSONObject();
362 processTypeDef(listNode.getType(), itemsVal);
363 props.put(ITEMS_KEY, itemsVal);
365 final ConstraintDefinition constraints = listNode.getConstraints();
366 processConstraints(constraints, props);
371 private JSONObject processChoiceNode(final ChoiceSchemaNode choiceNode, final String moduleName, final JSONObject models,
372 final SchemaContext schemaContext) throws JSONException, IOException {
374 final Set<ChoiceCaseNode> cases = choiceNode.getCases();
376 final JSONArray choiceProps = new JSONArray();
377 for (final ChoiceCaseNode choiceCase : cases) {
378 final String choiceName = choiceCase.getQName().getLocalName();
379 final JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), moduleName, models, schemaContext);
380 final JSONObject choiceObj = new JSONObject();
381 choiceObj.put(choiceName, choiceProp);
382 choiceObj.put(TYPE_KEY, OBJECT_TYPE);
383 choiceProps.put(choiceObj);
386 final JSONObject oneOfProps = new JSONObject();
387 oneOfProps.put(ONE_OF_KEY, choiceProps);
388 oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
393 private static void processConstraints(final ConstraintDefinition constraints, final JSONObject props) throws JSONException {
394 final boolean isMandatory = constraints.isMandatory();
395 props.put(REQUIRED_KEY, isMandatory);
397 final Integer minElements = constraints.getMinElements();
398 final Integer maxElements = constraints.getMaxElements();
399 if (minElements != null) {
400 props.put(MIN_ITEMS, minElements);
402 if (maxElements != null) {
403 props.put(MAX_ITEMS, maxElements);
407 private JSONObject processLeafNode(final LeafSchemaNode leafNode) throws JSONException {
408 final JSONObject property = new JSONObject();
410 final String leafDescription = leafNode.getDescription();
411 property.put(DESCRIPTION_KEY, leafDescription);
413 processConstraints(leafNode.getConstraints(), property);
414 processTypeDef(leafNode.getType(), property);
419 private static JSONObject processAnyXMLNode(final AnyXmlSchemaNode leafNode) throws JSONException {
420 final JSONObject property = new JSONObject();
422 final String leafDescription = leafNode.getDescription();
423 property.put(DESCRIPTION_KEY, leafDescription);
425 processConstraints(leafNode.getConstraints(), property);
430 private void processTypeDef(final TypeDefinition<?> leafTypeDef, final JSONObject property) throws JSONException {
431 if (leafTypeDef instanceof BinaryTypeDefinition) {
432 processBinaryType((BinaryTypeDefinition) leafTypeDef, property);
433 } else if (leafTypeDef instanceof BitsTypeDefinition) {
434 processBitsType((BitsTypeDefinition) leafTypeDef, property);
435 } else if (leafTypeDef instanceof EnumTypeDefinition) {
436 processEnumType((EnumTypeDefinition) leafTypeDef, property);
437 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
438 property.putOpt(TYPE_KEY,
439 ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName());
440 } else if (leafTypeDef instanceof StringTypeDefinition) {
441 processStringType((StringTypeDefinition) leafTypeDef, property);
442 } else if (leafTypeDef instanceof UnionTypeDefinition) {
443 processUnionType((UnionTypeDefinition) leafTypeDef, property);
445 String jsonType = jsonTypeFor(leafTypeDef);
446 if (jsonType == null) {
449 property.putOpt(TYPE_KEY, jsonType);
453 private static void processBinaryType(final BinaryTypeDefinition binaryType, final JSONObject property) throws JSONException {
454 property.put(TYPE_KEY, STRING);
455 final JSONObject media = new JSONObject();
456 media.put(BINARY_ENCODING_KEY, BASE_64);
457 property.put(MEDIA_KEY, media);
460 private static void processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
461 final List<EnumPair> enumPairs = enumLeafType.getValues();
462 final List<String> enumNames = new ArrayList<>();
463 for (final EnumPair enumPair : enumPairs) {
464 enumNames.add(enumPair.getName());
466 property.putOpt(ENUM, new JSONArray(enumNames));
469 private static void processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
470 property.put(TYPE_KEY, ARRAY_TYPE);
471 property.put(MIN_ITEMS, 0);
472 property.put(UNIQUE_ITEMS_KEY, true);
473 final JSONArray enumValues = new JSONArray();
475 final List<Bit> bits = bitsType.getBits();
476 for (final Bit bit : bits) {
477 enumValues.put(bit.getName());
479 final JSONObject itemsValue = new JSONObject();
480 itemsValue.put(ENUM, enumValues);
481 property.put(ITEMS_KEY, itemsValue);
484 private static void processStringType(final StringTypeDefinition stringType, final JSONObject property) throws JSONException {
485 StringTypeDefinition type = stringType;
486 List<LengthConstraint> lengthConstraints = stringType.getLengthConstraints();
487 while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
488 type = type.getBaseType();
489 lengthConstraints = type.getLengthConstraints();
492 // FIXME: json-schema is not expressive enough to capture min/max laternatives. We should find the true minimum
493 // and true maximum implied by the constraints and use that.
494 for (final LengthConstraint lengthConstraint : lengthConstraints) {
495 final Number min = lengthConstraint.getMin();
496 final Number max = lengthConstraint.getMax();
497 property.putOpt(MIN_LENGTH_KEY, min);
498 property.putOpt(MAX_LENGTH_KEY, max);
501 property.put(TYPE_KEY, STRING);
504 private static void processUnionType(final UnionTypeDefinition unionType, final JSONObject property) throws JSONException {
505 final StringBuilder type = new StringBuilder();
506 for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
507 if (type.length() > 0) {
510 type.append(jsonTypeFor(typeDef));
513 property.put(TYPE_KEY, type);
517 * Helper method to generate a pre-filled JSON schema object.
519 private static JSONObject getSchemaTemplate() throws JSONException {
520 final JSONObject schemaJSON = new JSONObject();
521 schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);