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.controller.sal.rest.doc.impl;
10 import org.json.JSONArray;
11 import org.json.JSONException;
12 import org.json.JSONObject;
13 import org.opendaylight.yangtools.yang.model.api.*;
14 import org.opendaylight.yangtools.yang.model.api.type.*;
15 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
16 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
17 import org.opendaylight.yangtools.yang.model.util.*;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
21 import java.io.IOException;
25 * Generates JSON Schema for data defined in Yang
27 public class ModelGenerator {
29 private static Logger _logger = LoggerFactory.getLogger(ModelGenerator.class);
31 private static final String BASE_64 = "base64";
32 private static final String BINARY_ENCODING_KEY = "binaryEncoding";
33 private static final String MEDIA_KEY = "media";
34 private static final String ONE_OF_KEY = "oneOf";
35 private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
36 private static final String MAX_ITEMS = "maxItems";
37 private static final String MIN_ITEMS = "minItems";
38 private static final String SCHEMA_URL = "http://json-schema.org/draft-04/schema";
39 private static final String SCHEMA_KEY = "$schema";
40 private static final String MAX_LENGTH_KEY = "maxLength";
41 private static final String MIN_LENGTH_KEY = "minLength";
42 private static final String REQUIRED_KEY = "required";
43 private static final String REF_KEY = "$ref";
44 private static final String ITEMS_KEY = "items";
45 private static final String TYPE_KEY = "type";
46 private static final String PROPERTIES_KEY = "properties";
47 private static final String DESCRIPTION_KEY = "description";
48 private static final String OBJECT_TYPE = "object";
49 private static final String ARRAY_TYPE = "array";
50 private static final String ENUM = "enum";
51 private static final String INTEGER = "integer";
52 private static final String NUMBER = "number";
53 private static final String BOOLEAN = "boolean";
54 private static final String STRING = "string";
56 private static final Map<Class<? extends TypeDefinition<?>>, String> YANG_TYPE_TO_JSON_TYPE_MAPPING;
59 Map<Class<? extends TypeDefinition<?>>, String> tempMap1 = new HashMap<Class<? extends TypeDefinition<?>>, String>(10);
60 tempMap1.put(StringType.class , STRING);
61 tempMap1.put(BooleanType.class , BOOLEAN);
62 tempMap1.put(Int8.class , INTEGER);
63 tempMap1.put(Int16.class , INTEGER);
64 tempMap1.put(Int32.class , INTEGER);
65 tempMap1.put(Int64.class , INTEGER);
66 tempMap1.put(Uint16.class , INTEGER);
67 tempMap1.put(Uint32.class , INTEGER);
68 tempMap1.put(Uint64.class , INTEGER);
69 tempMap1.put(Uint8.class , INTEGER);
70 tempMap1.put(Decimal64.class , NUMBER);
71 tempMap1.put(EnumerationType.class , ENUM);
74 YANG_TYPE_TO_JSON_TYPE_MAPPING = Collections.unmodifiableMap(tempMap1);
77 public ModelGenerator(){
80 public JSONObject convertToJsonSchema(Module module) throws IOException, JSONException {
81 JSONObject models = new JSONObject();
82 processContainers(module, models);
83 processRPCs(module, models);
90 private void processContainers(Module module, JSONObject models) throws IOException, JSONException {
92 String moduleName = module.getName();
93 Set<DataSchemaNode> childNodes = module.getChildNodes();
95 for(DataSchemaNode childNode : childNodes){
96 JSONObject moduleJSON=null;
97 String filename = childNode.getQName().getLocalName();
99 * For every container in the module
101 if(childNode instanceof ContainerSchemaNode) {
102 moduleJSON = processContainer((ContainerSchemaNode)childNode, moduleName, true, models);
105 if(moduleJSON!=null) {
106 _logger.debug("Adding model for [{}]", filename);
107 moduleJSON.put("id", filename);
108 models.put(filename, moduleJSON);
116 * Process the RPCs for a Module
117 * Spits out a file each of the name <rpcName>-input.json
118 * and <rpcName>-output.json for each RPC that contains
119 * input & output elements
122 * @throws JSONException
123 * @throws IOException
125 private void processRPCs(Module module, JSONObject models) throws JSONException, IOException {
127 Set<RpcDefinition> rpcs = module.getRpcs();
128 String moduleName = module.getName();
129 for(RpcDefinition rpc: rpcs) {
131 ContainerSchemaNode input = rpc.getInput();
133 JSONObject inputJSON = processContainer(input, moduleName, true, models);
134 String filename = rpc.getQName().getLocalName() + "-input";
135 inputJSON.put("id", filename);
136 //writeToFile(filename, inputJSON.toString(2), moduleName);
137 models.put(filename, inputJSON);
140 ContainerSchemaNode output = rpc.getOutput();
142 JSONObject outputJSON = processContainer(output, moduleName, true, models);
143 String filename = rpc.getQName().getLocalName() + "-output";
144 outputJSON.put("id", filename);
145 models.put(filename, outputJSON);
152 * Processes the container node and populates the moduleJSON
156 * @throws JSONException
157 * @throws IOException
159 private JSONObject processContainer(ContainerSchemaNode container, String moduleName, boolean addSchemaStmt, JSONObject models) throws JSONException, IOException{
160 JSONObject moduleJSON = getSchemaTemplate();
162 moduleJSON = getSchemaTemplate();
164 moduleJSON = new JSONObject();
166 moduleJSON.put(TYPE_KEY, OBJECT_TYPE);
168 String containerDescription = container.getDescription();
169 moduleJSON.put(DESCRIPTION_KEY, containerDescription);
171 Set<DataSchemaNode> containerChildren = ((ContainerSchemaNode)container).getChildNodes();
172 JSONObject properties = processChildren(containerChildren, moduleName, models);
173 moduleJSON.put(PROPERTIES_KEY, properties);
178 * Processes the nodes
182 * @throws JSONException
183 * @throws IOException
185 private JSONObject processChildren(Set<DataSchemaNode> nodes, String moduleName, JSONObject models) throws JSONException, IOException {
187 JSONObject properties = new JSONObject();
189 for(DataSchemaNode node : nodes){
190 String name = node.getQName().getLocalName();
191 JSONObject property = null;
192 if(node instanceof LeafSchemaNode) {
193 property = processLeafNode((LeafSchemaNode)node);
194 } else if (node instanceof ListSchemaNode) {
195 property = processListSchemaNode((ListSchemaNode)node, moduleName, models);
197 } else if (node instanceof LeafListSchemaNode) {
198 property = processLeafListNode((LeafListSchemaNode)node);
200 } else if (node instanceof ChoiceNode) {
201 property = processChoiceNode((ChoiceNode)node, moduleName, models);
203 } else if (node instanceof AnyXmlSchemaNode) {
204 property = processAnyXMLNode((AnyXmlSchemaNode)node);
206 } else if (node instanceof ContainerSchemaNode) {
207 property = processContainer((ContainerSchemaNode)node, moduleName, false, models);
210 throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
213 property.putOpt(DESCRIPTION_KEY, node.getDescription());
214 properties.put(name, property);
222 * @throws JSONException
224 private JSONObject processLeafListNode(LeafListSchemaNode listNode) throws JSONException {
225 JSONObject props = new JSONObject();
226 props.put(TYPE_KEY, ARRAY_TYPE);
228 JSONObject itemsVal = new JSONObject();
229 processTypeDef(listNode.getType(), itemsVal);
230 props.put(ITEMS_KEY, itemsVal);
232 ConstraintDefinition constraints = listNode.getConstraints();
233 processConstraints(constraints, props);
242 * @throws JSONException
243 * @throws IOException
245 private JSONObject processChoiceNode(ChoiceNode choiceNode, String moduleName, JSONObject models) throws JSONException, IOException {
247 Set<ChoiceCaseNode> cases = choiceNode.getCases();
249 JSONArray choiceProps = new JSONArray();
250 for(ChoiceCaseNode choiceCase: cases) {
251 String choiceName = choiceCase.getQName().getLocalName();
252 JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), moduleName, models);
253 JSONObject choiceObj = new JSONObject();
254 choiceObj.put(choiceName, choiceProp);
255 choiceObj.put(TYPE_KEY, OBJECT_TYPE);
256 choiceProps.put(choiceObj);
259 JSONObject oneOfProps = new JSONObject();
260 oneOfProps.put(ONE_OF_KEY, choiceProps);
261 oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
271 * @throws JSONException
273 private void processConstraints(ConstraintDefinition constraints, JSONObject props) throws JSONException {
274 boolean isMandatory = constraints.isMandatory();
275 props.put(REQUIRED_KEY, isMandatory);
277 Integer minElements = constraints.getMinElements();
278 Integer maxElements = constraints.getMaxElements();
279 if(minElements !=null) {
280 props.put(MIN_ITEMS, minElements);
282 if(maxElements !=null) {
283 props.put(MAX_ITEMS, maxElements);
288 * Parses a ListSchema node.
290 * Due to a limitation of the RAML--->JAX-RS tool, sub-properties
291 * must be in a separate JSON schema file. Hence, we have to write
292 * some properties to a new file, while continuing to process the rest.
297 * @throws JSONException
298 * @throws IOException
300 private JSONObject processListSchemaNode(ListSchemaNode listNode, String moduleName, JSONObject models) throws JSONException, IOException {
302 Set<DataSchemaNode> listChildren = listNode.getChildNodes();
303 String fileName = listNode.getQName().getLocalName();
305 JSONObject childSchemaProperties = processChildren(listChildren, moduleName, models);
306 JSONObject childSchema = getSchemaTemplate();
307 childSchema.put(TYPE_KEY, OBJECT_TYPE);
308 childSchema.put(PROPERTIES_KEY, childSchemaProperties);
311 * Due to a limitation of the RAML--->JAX-RS tool, sub-properties
312 * must be in a separate JSON schema file. Hence, we have to write
313 * some properties to a new file, while continuing to process the rest.
315 //writeToFile(fileName, childSchema.toString(2), moduleName);
316 childSchema.put("id", fileName);
317 models.put(fileName, childSchema);
320 JSONObject listNodeProperties = new JSONObject();
321 listNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
323 JSONObject items = new JSONObject();
324 items.put(REF_KEY,fileName );
325 listNodeProperties.put(ITEMS_KEY, items);
327 return listNodeProperties;
335 * @throws JSONException
337 private JSONObject processLeafNode(LeafSchemaNode leafNode) throws JSONException {
338 JSONObject property = new JSONObject();
340 String leafDescription = leafNode.getDescription();
341 property.put(DESCRIPTION_KEY, leafDescription);
343 processConstraints(leafNode.getConstraints(), property);
344 processTypeDef(leafNode.getType(), property);
353 * @throws JSONException
355 private JSONObject processAnyXMLNode(AnyXmlSchemaNode leafNode) throws JSONException {
356 JSONObject property = new JSONObject();
358 String leafDescription = leafNode.getDescription();
359 property.put(DESCRIPTION_KEY, leafDescription);
361 processConstraints(leafNode.getConstraints(), property);
368 * @throws JSONException
370 private void processTypeDef(TypeDefinition<?> leafTypeDef, JSONObject property) throws JSONException {
372 if(leafTypeDef instanceof ExtendedType){
373 processExtendedType(leafTypeDef, property);
374 } else if (leafTypeDef instanceof EnumerationType) {
375 processEnumType((EnumerationType)leafTypeDef, property);
377 } else if (leafTypeDef instanceof BitsTypeDefinition) {
378 processBitsType((BitsTypeDefinition)leafTypeDef, property);
380 } else if (leafTypeDef instanceof UnionTypeDefinition) {
381 processUnionType((UnionTypeDefinition)leafTypeDef, property);
383 } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
384 property.putOpt(TYPE_KEY, "object");
385 } else if (leafTypeDef instanceof BinaryTypeDefinition) {
386 processBinaryType((BinaryTypeDefinition)leafTypeDef, property);
388 //System.out.println("In else: " + leafTypeDef.getClass());
389 String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafTypeDef.getClass());
393 property.putOpt(TYPE_KEY, jsonType);
401 * @throws JSONException
403 private void processExtendedType(TypeDefinition<?> leafTypeDef, JSONObject property) throws JSONException {
404 Object leafBaseType = leafTypeDef.getBaseType();
405 if(leafBaseType instanceof ExtendedType){
406 //recursively process an extended type until we hit a base type
407 processExtendedType((TypeDefinition<?>)leafBaseType, property);
409 List<LengthConstraint> lengthConstraints = ((ExtendedType) leafTypeDef).getLengthConstraints();
410 for(LengthConstraint lengthConstraint: lengthConstraints) {
411 Number min = lengthConstraint.getMin();
412 Number max = lengthConstraint.getMax();
413 property.putOpt(MIN_LENGTH_KEY, min);
414 property.putOpt(MAX_LENGTH_KEY, max);
416 String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafBaseType.getClass());
417 property.putOpt(TYPE_KEY,jsonType );
425 private void processBinaryType(BinaryTypeDefinition binaryType, JSONObject property) throws JSONException {
426 property.put(TYPE_KEY, STRING);
427 JSONObject media = new JSONObject();
428 media.put(BINARY_ENCODING_KEY, BASE_64);
429 property.put(MEDIA_KEY, media);
434 * @param enumLeafType
436 * @throws JSONException
438 private void processEnumType(EnumerationType enumLeafType, JSONObject property) throws JSONException {
439 List<EnumPair> enumPairs = enumLeafType.getValues();
440 List<String> enumNames = new ArrayList<String>();
441 for(EnumPair enumPair: enumPairs) {
442 enumNames.add(enumPair.getName());
444 property.putOpt(ENUM, new JSONArray(enumNames));
451 * @throws JSONException
453 private void processBitsType(BitsTypeDefinition bitsType, JSONObject property) throws JSONException{
454 property.put(TYPE_KEY, ARRAY_TYPE);
455 property.put(MIN_ITEMS, 0);
456 property.put(UNIQUE_ITEMS_KEY, true);
457 JSONArray enumValues = new JSONArray();
459 List<Bit> bits = bitsType.getBits();
461 enumValues.put(bit.getName());
463 JSONObject itemsValue = new JSONObject();
464 itemsValue.put(ENUM, enumValues);
465 property.put(ITEMS_KEY, itemsValue);
473 * @throws JSONException
475 private void processUnionType(UnionTypeDefinition unionType, JSONObject property) throws JSONException{
477 List<TypeDefinition<?>> unionTypes = unionType.getTypes();
478 JSONArray unionArray = new JSONArray();
479 for(TypeDefinition<?> typeDef: unionTypes) {
480 unionArray.put(YANG_TYPE_TO_JSON_TYPE_MAPPING.get(typeDef.getClass()));
482 property.put(TYPE_KEY, unionArray);
487 * Helper method to generate a pre-filled
488 * JSON schema object.
490 * @throws JSONException
492 private JSONObject getSchemaTemplate() throws JSONException {
493 JSONObject schemaJSON = new JSONObject();
494 schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);