Bug: 627
[controller.git] / opendaylight / md-sal / sal-rest-docgen / src / main / java / org / opendaylight / controller / sal / rest / doc / impl / ModelGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.sal.rest.doc.impl;
9
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;
20
21 import java.io.IOException;
22 import java.util.*;
23
24 /**
25  * Generates JSON Schema for data defined in Yang
26  */
27 public class ModelGenerator {
28
29   private static Logger _logger = LoggerFactory.getLogger(ModelGenerator.class);
30
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";
55
56   private static final Map<Class<? extends TypeDefinition<?>>, String> YANG_TYPE_TO_JSON_TYPE_MAPPING;
57
58   static {
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);
72     //TODO: Binary type
73
74     YANG_TYPE_TO_JSON_TYPE_MAPPING = Collections.unmodifiableMap(tempMap1);
75   }
76
77   public ModelGenerator(){
78   }
79
80   public JSONObject convertToJsonSchema(Module module) throws IOException, JSONException {
81     JSONObject models = new JSONObject();
82     processContainers(module, models);
83     processRPCs(module, models);
84
85     return models;
86   }
87
88
89
90   private void processContainers(Module module, JSONObject models) throws IOException, JSONException {
91
92     String moduleName = module.getName();
93     Set<DataSchemaNode> childNodes =  module.getChildNodes();
94
95     for(DataSchemaNode childNode : childNodes){
96       JSONObject moduleJSON=null;
97       String filename = childNode.getQName().getLocalName();
98                         /*
99                          * For every container in the module
100                          */
101       if(childNode instanceof ContainerSchemaNode) {
102         moduleJSON = processContainer((ContainerSchemaNode)childNode, moduleName, true, models);
103       }
104
105       if(moduleJSON!=null) {
106         _logger.debug("Adding model for [{}]", filename);
107         moduleJSON.put("id", filename);
108         models.put(filename, moduleJSON);
109       }
110     }
111
112   }
113
114
115   /**
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
120    *
121    * @param module
122    * @throws JSONException
123    * @throws IOException
124    */
125   private void processRPCs(Module module, JSONObject models) throws JSONException, IOException {
126
127     Set<RpcDefinition> rpcs =  module.getRpcs();
128     String moduleName = module.getName();
129     for(RpcDefinition rpc: rpcs) {
130
131       ContainerSchemaNode input = rpc.getInput();
132       if(input!=null) {
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);
138       }
139
140       ContainerSchemaNode output = rpc.getOutput();
141       if(output!=null) {
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);
146       }
147     }
148   }
149
150
151   /**
152    * Processes the container node and populates the moduleJSON
153    *
154    * @param container
155    * @param moduleName
156    * @throws JSONException
157    * @throws IOException
158    */
159   private JSONObject processContainer(ContainerSchemaNode container, String moduleName, boolean addSchemaStmt, JSONObject models) throws JSONException, IOException{
160     JSONObject moduleJSON = getSchemaTemplate();
161     if(addSchemaStmt) {
162       moduleJSON = getSchemaTemplate();
163     } else {
164       moduleJSON = new JSONObject();
165     }
166     moduleJSON.put(TYPE_KEY, OBJECT_TYPE);
167
168     String containerDescription = container.getDescription();
169     moduleJSON.put(DESCRIPTION_KEY, containerDescription);
170
171     Set<DataSchemaNode> containerChildren = ((ContainerSchemaNode)container).getChildNodes();
172     JSONObject properties = processChildren(containerChildren, moduleName, models);
173     moduleJSON.put(PROPERTIES_KEY, properties);
174     return moduleJSON;
175   }
176
177   /**
178    * Processes the nodes
179    * @param nodes
180    * @param moduleName
181    * @return
182    * @throws JSONException
183    * @throws IOException
184    */
185   private JSONObject processChildren(Set<DataSchemaNode> nodes, String moduleName, JSONObject models) throws JSONException, IOException {
186
187     JSONObject properties = new JSONObject();
188
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);
196
197       } else if (node instanceof LeafListSchemaNode) {
198         property = processLeafListNode((LeafListSchemaNode)node);
199
200       } else if (node instanceof ChoiceNode) {
201         property = processChoiceNode((ChoiceNode)node, moduleName, models);
202
203       } else if (node instanceof AnyXmlSchemaNode) {
204         property = processAnyXMLNode((AnyXmlSchemaNode)node);
205
206       } else if (node instanceof ContainerSchemaNode) {
207         property = processContainer((ContainerSchemaNode)node, moduleName, false, models);
208
209       } else {
210         throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
211       }
212
213       property.putOpt(DESCRIPTION_KEY, node.getDescription());
214       properties.put(name, property);
215     }
216     return properties;
217   }
218
219   /**
220    *
221    * @param listNode
222    * @throws JSONException
223    */
224   private JSONObject processLeafListNode(LeafListSchemaNode listNode) throws JSONException {
225     JSONObject props = new JSONObject();
226     props.put(TYPE_KEY, ARRAY_TYPE);
227
228     JSONObject itemsVal = new JSONObject();
229     processTypeDef(listNode.getType(), itemsVal);
230     props.put(ITEMS_KEY, itemsVal);
231
232     ConstraintDefinition constraints = listNode.getConstraints();
233     processConstraints(constraints, props);
234
235     return props;
236   }
237
238   /**
239    *
240    * @param choiceNode
241    * @param moduleName
242    * @throws JSONException
243    * @throws IOException
244    */
245   private JSONObject processChoiceNode(ChoiceNode choiceNode, String moduleName, JSONObject models) throws JSONException, IOException {
246
247     Set<ChoiceCaseNode> cases = choiceNode.getCases();
248
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);
257     }
258
259     JSONObject oneOfProps = new JSONObject();
260     oneOfProps.put(ONE_OF_KEY, choiceProps);
261     oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
262
263     return oneOfProps;
264   }
265
266
267   /**
268    *
269    * @param constraints
270    * @param props
271    * @throws JSONException
272    */
273   private void processConstraints(ConstraintDefinition constraints, JSONObject props) throws JSONException {
274     boolean isMandatory = constraints.isMandatory();
275     props.put(REQUIRED_KEY, isMandatory);
276
277     Integer minElements = constraints.getMinElements();
278     Integer maxElements = constraints.getMaxElements();
279     if(minElements !=null) {
280       props.put(MIN_ITEMS, minElements);
281     }
282     if(maxElements !=null) {
283       props.put(MAX_ITEMS, maxElements);
284     }
285   }
286
287   /**
288    * Parses a ListSchema node.
289    *
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.
293    *
294    * @param listNode
295    * @param moduleName
296    * @return
297    * @throws JSONException
298    * @throws IOException
299    */
300   private JSONObject processListSchemaNode(ListSchemaNode listNode, String moduleName, JSONObject models) throws JSONException, IOException {
301
302     Set<DataSchemaNode> listChildren = listNode.getChildNodes();
303     String fileName = listNode.getQName().getLocalName();
304
305     JSONObject childSchemaProperties = processChildren(listChildren, moduleName, models);
306     JSONObject childSchema = getSchemaTemplate();
307     childSchema.put(TYPE_KEY, OBJECT_TYPE);
308     childSchema.put(PROPERTIES_KEY, childSchemaProperties);
309
310                 /*
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.
314                  */
315     //writeToFile(fileName, childSchema.toString(2), moduleName);
316     childSchema.put("id", fileName);
317     models.put(fileName, childSchema);
318
319
320     JSONObject listNodeProperties = new JSONObject();
321     listNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
322
323     JSONObject items = new JSONObject();
324     items.put(REF_KEY,fileName );
325     listNodeProperties.put(ITEMS_KEY, items);
326
327     return listNodeProperties;
328
329   }
330
331   /**
332    *
333    * @param leafNode
334    * @return
335    * @throws JSONException
336    */
337   private JSONObject processLeafNode(LeafSchemaNode leafNode) throws JSONException {
338     JSONObject property = new JSONObject();
339
340     String leafDescription = leafNode.getDescription();
341     property.put(DESCRIPTION_KEY, leafDescription);
342
343     processConstraints(leafNode.getConstraints(), property);
344     processTypeDef(leafNode.getType(), property);
345
346     return property;
347   }
348
349   /**
350    *
351    * @param leafNode
352    * @return
353    * @throws JSONException
354    */
355   private JSONObject processAnyXMLNode(AnyXmlSchemaNode leafNode) throws JSONException {
356     JSONObject property = new JSONObject();
357
358     String leafDescription = leafNode.getDescription();
359     property.put(DESCRIPTION_KEY, leafDescription);
360
361     processConstraints(leafNode.getConstraints(), property);
362
363     return property;
364   }
365
366   /**
367    * @param property
368    * @throws JSONException
369    */
370   private void processTypeDef(TypeDefinition<?> leafTypeDef, JSONObject property) throws JSONException {
371
372     if(leafTypeDef instanceof ExtendedType){
373       processExtendedType(leafTypeDef, property);
374     } else if (leafTypeDef instanceof EnumerationType) {
375       processEnumType((EnumerationType)leafTypeDef, property);
376
377     } else if (leafTypeDef instanceof BitsTypeDefinition) {
378       processBitsType((BitsTypeDefinition)leafTypeDef, property);
379
380     } else if (leafTypeDef instanceof UnionTypeDefinition) {
381       processUnionType((UnionTypeDefinition)leafTypeDef, property);
382
383     } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
384       property.putOpt(TYPE_KEY, "object");
385     } else if (leafTypeDef instanceof BinaryTypeDefinition) {
386       processBinaryType((BinaryTypeDefinition)leafTypeDef, property);
387     } else {
388       //System.out.println("In else: " + leafTypeDef.getClass());
389       String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafTypeDef.getClass());
390       if(jsonType==null) {
391         jsonType = "object";
392       }
393       property.putOpt(TYPE_KEY, jsonType);
394     }
395   }
396
397   /**
398    *
399    * @param leafTypeDef
400    * @param property
401    * @throws JSONException
402    */
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);
408     } else {
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);
415       }
416       String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafBaseType.getClass());
417       property.putOpt(TYPE_KEY,jsonType );
418     }
419
420   }
421
422   /*
423    *
424    */
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);
430   }
431
432   /**
433    *
434    * @param enumLeafType
435    * @param property
436    * @throws JSONException
437    */
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());
443     }
444     property.putOpt(ENUM, new JSONArray(enumNames));
445   }
446
447   /**
448    *
449    * @param bitsType
450    * @param property
451    * @throws JSONException
452    */
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();
458
459     List<Bit> bits = bitsType.getBits();
460     for(Bit bit: bits) {
461       enumValues.put(bit.getName());
462     }
463     JSONObject itemsValue = new JSONObject();
464     itemsValue.put(ENUM, enumValues);
465     property.put(ITEMS_KEY, itemsValue);
466   }
467
468
469   /**
470    *
471    * @param unionType
472    * @param property
473    * @throws JSONException
474    */
475   private void processUnionType(UnionTypeDefinition unionType, JSONObject property) throws JSONException{
476
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()));
481     }
482     property.put(TYPE_KEY, unionArray);
483   }
484
485
486   /**
487    * Helper method to generate a pre-filled
488    * JSON schema object.
489    * @return
490    * @throws JSONException
491    */
492   private JSONObject getSchemaTemplate() throws JSONException {
493     JSONObject schemaJSON = new JSONObject();
494     schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);
495
496     return schemaJSON;
497   }
498 }