Merge "Implement basic ShardTransactionChain#CloseTransactionChain"
[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 java.io.IOException;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17
18 import org.json.JSONArray;
19 import org.json.JSONException;
20 import org.json.JSONObject;
21 import org.opendaylight.controller.sal.rest.doc.model.builder.OperationBuilder;
22 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
23 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
24 import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
25 import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
26 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.Module;
32 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
33 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
34 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
35 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
36 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
37 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
38 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
39 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
40 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
41 import org.opendaylight.yangtools.yang.model.util.BooleanType;
42 import org.opendaylight.yangtools.yang.model.util.Decimal64;
43 import org.opendaylight.yangtools.yang.model.util.EnumerationType;
44 import org.opendaylight.yangtools.yang.model.util.ExtendedType;
45 import org.opendaylight.yangtools.yang.model.util.Int16;
46 import org.opendaylight.yangtools.yang.model.util.Int32;
47 import org.opendaylight.yangtools.yang.model.util.Int64;
48 import org.opendaylight.yangtools.yang.model.util.Int8;
49 import org.opendaylight.yangtools.yang.model.util.StringType;
50 import org.opendaylight.yangtools.yang.model.util.Uint16;
51 import org.opendaylight.yangtools.yang.model.util.Uint32;
52 import org.opendaylight.yangtools.yang.model.util.Uint64;
53 import org.opendaylight.yangtools.yang.model.util.Uint8;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * Generates JSON Schema for data defined in Yang
59  */
60 public class ModelGenerator {
61
62     private static Logger _logger = LoggerFactory.getLogger(ModelGenerator.class);
63
64     private static final String BASE_64 = "base64";
65     private static final String BINARY_ENCODING_KEY = "binaryEncoding";
66     private static final String MEDIA_KEY = "media";
67     private static final String ONE_OF_KEY = "oneOf";
68     private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
69     private static final String MAX_ITEMS = "maxItems";
70     private static final String MIN_ITEMS = "minItems";
71     private static final String SCHEMA_URL = "http://json-schema.org/draft-04/schema";
72     private static final String SCHEMA_KEY = "$schema";
73     private static final String MAX_LENGTH_KEY = "maxLength";
74     private static final String MIN_LENGTH_KEY = "minLength";
75     private static final String REQUIRED_KEY = "required";
76     private static final String REF_KEY = "$ref";
77     private static final String ITEMS_KEY = "items";
78     private static final String TYPE_KEY = "type";
79     private static final String PROPERTIES_KEY = "properties";
80     private static final String DESCRIPTION_KEY = "description";
81     private static final String OBJECT_TYPE = "object";
82     private static final String ARRAY_TYPE = "array";
83     private static final String ENUM = "enum";
84     private static final String INTEGER = "integer";
85     private static final String NUMBER = "number";
86     private static final String BOOLEAN = "boolean";
87     private static final String STRING = "string";
88
89     private static final Map<Class<? extends TypeDefinition<?>>, String> YANG_TYPE_TO_JSON_TYPE_MAPPING;
90
91     static {
92         Map<Class<? extends TypeDefinition<?>>, String> tempMap1 = new HashMap<Class<? extends TypeDefinition<?>>, String>(
93                 10);
94         tempMap1.put(StringType.class, STRING);
95         tempMap1.put(BooleanType.class, BOOLEAN);
96         tempMap1.put(Int8.class, INTEGER);
97         tempMap1.put(Int16.class, INTEGER);
98         tempMap1.put(Int32.class, INTEGER);
99         tempMap1.put(Int64.class, INTEGER);
100         tempMap1.put(Uint16.class, INTEGER);
101         tempMap1.put(Uint32.class, INTEGER);
102         tempMap1.put(Uint64.class, INTEGER);
103         tempMap1.put(Uint8.class, INTEGER);
104         tempMap1.put(Decimal64.class, NUMBER);
105         tempMap1.put(EnumerationType.class, ENUM);
106         // TODO: Binary type
107
108         YANG_TYPE_TO_JSON_TYPE_MAPPING = Collections.unmodifiableMap(tempMap1);
109     }
110
111     public ModelGenerator() {
112     }
113
114     public JSONObject convertToJsonSchema(Module module) throws IOException, JSONException {
115         JSONObject models = new JSONObject();
116         processContainers(module, models);
117         processRPCs(module, models);
118         return models;
119     }
120
121     private void processContainers(Module module, JSONObject models) throws IOException,
122             JSONException {
123
124         String moduleName = module.getName();
125         Set<DataSchemaNode> childNodes = module.getChildNodes();
126
127         for (DataSchemaNode childNode : childNodes) {
128             JSONObject configModuleJSON = null;
129             JSONObject operationalModuleJSON = null;
130
131             String childNodeName = childNode.getQName().getLocalName();
132             /*
133              * For every container in the module
134              */
135             if (childNode instanceof ContainerSchemaNode) {
136                 configModuleJSON = processContainer((ContainerSchemaNode) childNode, moduleName,
137                         true, models, true);
138                 operationalModuleJSON = processContainer((ContainerSchemaNode) childNode,
139                         moduleName, true, models, false);
140             }
141
142             if (configModuleJSON != null) {
143                 _logger.debug("Adding model for [{}]", OperationBuilder.CONFIG + childNodeName);
144                 configModuleJSON.put("id", OperationBuilder.CONFIG + childNodeName);
145                 models.put(OperationBuilder.CONFIG + childNodeName, configModuleJSON);
146             }
147             if (operationalModuleJSON != null) {
148                 _logger.debug("Adding model for [{}]", OperationBuilder.OPERATIONAL + childNodeName);
149                 operationalModuleJSON.put("id", OperationBuilder.OPERATIONAL + childNodeName);
150                 models.put(OperationBuilder.OPERATIONAL + childNodeName, operationalModuleJSON);
151             }
152         }
153
154     }
155
156     /**
157      * Process the RPCs for a Module Spits out a file each of the name
158      * <rpcName>-input.json and <rpcName>-output.json for each RPC that contains
159      * input & output elements
160      *
161      * @param module
162      * @throws JSONException
163      * @throws IOException
164      */
165     private void processRPCs(Module module, JSONObject models) throws JSONException, IOException {
166
167         Set<RpcDefinition> rpcs = module.getRpcs();
168         String moduleName = module.getName();
169         for (RpcDefinition rpc : rpcs) {
170
171             ContainerSchemaNode input = rpc.getInput();
172             if (input != null) {
173                 JSONObject inputJSON = processContainer(input, moduleName, true, models);
174                 String filename = "(" + rpc.getQName().getLocalName() + ")input";
175                 inputJSON.put("id", filename);
176                 // writeToFile(filename, inputJSON.toString(2), moduleName);
177                 models.put(filename, inputJSON);
178             }
179
180             ContainerSchemaNode output = rpc.getOutput();
181             if (output != null) {
182                 JSONObject outputJSON = processContainer(output, moduleName, true, models);
183                 String filename = "(" + rpc.getQName().getLocalName() + ")output";
184                 outputJSON.put("id", filename);
185                 models.put(filename, outputJSON);
186             }
187         }
188     }
189
190     /**
191      * Processes the container node and populates the moduleJSON
192      *
193      * @param container
194      * @param moduleName
195      * @param isConfig
196      * @throws JSONException
197      * @throws IOException
198      */
199     private JSONObject processContainer(ContainerSchemaNode container, String moduleName,
200             boolean addSchemaStmt, JSONObject models) throws JSONException, IOException {
201         return processContainer(container, moduleName, addSchemaStmt, models, (Boolean) null);
202     }
203
204     private JSONObject processContainer(ContainerSchemaNode container, String moduleName,
205             boolean addSchemaStmt, JSONObject models, Boolean isConfig) throws JSONException,
206             IOException {
207         JSONObject moduleJSON = getSchemaTemplate();
208         if (addSchemaStmt) {
209             moduleJSON = getSchemaTemplate();
210         } else {
211             moduleJSON = new JSONObject();
212         }
213         moduleJSON.put(TYPE_KEY, OBJECT_TYPE);
214
215         String containerDescription = container.getDescription();
216         moduleJSON.put(DESCRIPTION_KEY, containerDescription);
217
218         Set<DataSchemaNode> containerChildren = container.getChildNodes();
219         JSONObject properties = processChildren(containerChildren, moduleName, models, isConfig);
220         moduleJSON.put(PROPERTIES_KEY, properties);
221         return moduleJSON;
222     }
223
224     private JSONObject processChildren(Set<DataSchemaNode> nodes, String moduleName,
225             JSONObject models) throws JSONException, IOException {
226         return processChildren(nodes, moduleName, models, null);
227     }
228
229     /**
230      * Processes the nodes
231      *
232      * @param nodes
233      * @param moduleName
234      * @param isConfig
235      * @return
236      * @throws JSONException
237      * @throws IOException
238      */
239     private JSONObject processChildren(Set<DataSchemaNode> nodes, String moduleName,
240             JSONObject models, Boolean isConfig) throws JSONException, IOException {
241
242         JSONObject properties = new JSONObject();
243
244         for (DataSchemaNode node : nodes) {
245             if (isConfig == null || node.isConfiguration() == isConfig) {
246
247                 String name = node.getQName().getLocalName();
248                 JSONObject property = null;
249                 if (node instanceof LeafSchemaNode) {
250                     property = processLeafNode((LeafSchemaNode) node);
251                 } else if (node instanceof ListSchemaNode) {
252                     property = processListSchemaNode((ListSchemaNode) node, moduleName, models);
253
254                 } else if (node instanceof LeafListSchemaNode) {
255                     property = processLeafListNode((LeafListSchemaNode) node);
256
257                 } else if (node instanceof ChoiceNode) {
258                     property = processChoiceNode((ChoiceNode) node, moduleName, models);
259
260                 } else if (node instanceof AnyXmlSchemaNode) {
261                     property = processAnyXMLNode((AnyXmlSchemaNode) node);
262
263                 } else if (node instanceof ContainerSchemaNode) {
264                     property = processContainer((ContainerSchemaNode) node, moduleName, false,
265                             models, isConfig);
266
267                 } else {
268                     throw new IllegalArgumentException("Unknown DataSchemaNode type: "
269                             + node.getClass());
270                 }
271
272                 property.putOpt(DESCRIPTION_KEY, node.getDescription());
273                 properties.put(name, property);
274             }
275         }
276         return properties;
277     }
278
279     /**
280      *
281      * @param listNode
282      * @throws JSONException
283      */
284     private JSONObject processLeafListNode(LeafListSchemaNode listNode) throws JSONException {
285         JSONObject props = new JSONObject();
286         props.put(TYPE_KEY, ARRAY_TYPE);
287
288         JSONObject itemsVal = new JSONObject();
289         processTypeDef(listNode.getType(), itemsVal);
290         props.put(ITEMS_KEY, itemsVal);
291
292         ConstraintDefinition constraints = listNode.getConstraints();
293         processConstraints(constraints, props);
294
295         return props;
296     }
297
298     /**
299      *
300      * @param choiceNode
301      * @param moduleName
302      * @throws JSONException
303      * @throws IOException
304      */
305     private JSONObject processChoiceNode(ChoiceNode choiceNode, String moduleName, JSONObject models)
306             throws JSONException, IOException {
307
308         Set<ChoiceCaseNode> cases = choiceNode.getCases();
309
310         JSONArray choiceProps = new JSONArray();
311         for (ChoiceCaseNode choiceCase : cases) {
312             String choiceName = choiceCase.getQName().getLocalName();
313             JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), moduleName, models);
314             JSONObject choiceObj = new JSONObject();
315             choiceObj.put(choiceName, choiceProp);
316             choiceObj.put(TYPE_KEY, OBJECT_TYPE);
317             choiceProps.put(choiceObj);
318         }
319
320         JSONObject oneOfProps = new JSONObject();
321         oneOfProps.put(ONE_OF_KEY, choiceProps);
322         oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
323
324         return oneOfProps;
325     }
326
327     /**
328      *
329      * @param constraints
330      * @param props
331      * @throws JSONException
332      */
333     private void processConstraints(ConstraintDefinition constraints, JSONObject props)
334             throws JSONException {
335         boolean isMandatory = constraints.isMandatory();
336         props.put(REQUIRED_KEY, isMandatory);
337
338         Integer minElements = constraints.getMinElements();
339         Integer maxElements = constraints.getMaxElements();
340         if (minElements != null) {
341             props.put(MIN_ITEMS, minElements);
342         }
343         if (maxElements != null) {
344             props.put(MAX_ITEMS, maxElements);
345         }
346     }
347
348     /**
349      * Parses a ListSchema node.
350      *
351      * Due to a limitation of the RAML--->JAX-RS tool, sub-properties must be in
352      * a separate JSON schema file. Hence, we have to write some properties to a
353      * new file, while continuing to process the rest.
354      *
355      * @param listNode
356      * @param moduleName
357      * @return
358      * @throws JSONException
359      * @throws IOException
360      */
361     private JSONObject processListSchemaNode(ListSchemaNode listNode, String moduleName,
362             JSONObject models) throws JSONException, IOException {
363
364         Set<DataSchemaNode> listChildren = listNode.getChildNodes();
365         String fileName = listNode.getQName().getLocalName();
366
367         JSONObject childSchemaProperties = processChildren(listChildren, moduleName, models);
368         JSONObject childSchema = getSchemaTemplate();
369         childSchema.put(TYPE_KEY, OBJECT_TYPE);
370         childSchema.put(PROPERTIES_KEY, childSchemaProperties);
371
372         /*
373          * Due to a limitation of the RAML--->JAX-RS tool, sub-properties must
374          * be in a separate JSON schema file. Hence, we have to write some
375          * properties to a new file, while continuing to process the rest.
376          */
377         // writeToFile(fileName, childSchema.toString(2), moduleName);
378         childSchema.put("id", fileName);
379         models.put(fileName, childSchema);
380
381         JSONObject listNodeProperties = new JSONObject();
382         listNodeProperties.put(TYPE_KEY, ARRAY_TYPE);
383
384         JSONObject items = new JSONObject();
385         items.put(REF_KEY, fileName);
386         listNodeProperties.put(ITEMS_KEY, items);
387
388         return listNodeProperties;
389
390     }
391
392     /**
393      *
394      * @param leafNode
395      * @return
396      * @throws JSONException
397      */
398     private JSONObject processLeafNode(LeafSchemaNode leafNode) throws JSONException {
399         JSONObject property = new JSONObject();
400
401         String leafDescription = leafNode.getDescription();
402         property.put(DESCRIPTION_KEY, leafDescription);
403
404         processConstraints(leafNode.getConstraints(), property);
405         processTypeDef(leafNode.getType(), property);
406
407         return property;
408     }
409
410     /**
411      *
412      * @param leafNode
413      * @return
414      * @throws JSONException
415      */
416     private JSONObject processAnyXMLNode(AnyXmlSchemaNode leafNode) throws JSONException {
417         JSONObject property = new JSONObject();
418
419         String leafDescription = leafNode.getDescription();
420         property.put(DESCRIPTION_KEY, leafDescription);
421
422         processConstraints(leafNode.getConstraints(), property);
423
424         return property;
425     }
426
427     /**
428      * @param property
429      * @throws JSONException
430      */
431     private void processTypeDef(TypeDefinition<?> leafTypeDef, JSONObject property)
432             throws JSONException {
433
434         if (leafTypeDef instanceof ExtendedType) {
435             processExtendedType(leafTypeDef, property);
436         } else if (leafTypeDef instanceof EnumerationType) {
437             processEnumType((EnumerationType) leafTypeDef, property);
438
439         } else if (leafTypeDef instanceof BitsTypeDefinition) {
440             processBitsType((BitsTypeDefinition) leafTypeDef, property);
441
442         } else if (leafTypeDef instanceof UnionTypeDefinition) {
443             processUnionType((UnionTypeDefinition) leafTypeDef, property);
444
445         } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
446             property.putOpt(TYPE_KEY, "object");
447         } else if (leafTypeDef instanceof BinaryTypeDefinition) {
448             processBinaryType((BinaryTypeDefinition) leafTypeDef, property);
449         } else {
450             // System.out.println("In else: " + leafTypeDef.getClass());
451             String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafTypeDef.getClass());
452             if (jsonType == null) {
453                 jsonType = "object";
454             }
455             property.putOpt(TYPE_KEY, jsonType);
456         }
457     }
458
459     /**
460      *
461      * @param leafTypeDef
462      * @param property
463      * @throws JSONException
464      */
465     private void processExtendedType(TypeDefinition<?> leafTypeDef, JSONObject property)
466             throws JSONException {
467         Object leafBaseType = leafTypeDef.getBaseType();
468         if (leafBaseType instanceof ExtendedType) {
469             // recursively process an extended type until we hit a base type
470             processExtendedType((TypeDefinition<?>) leafBaseType, property);
471         } else {
472             List<LengthConstraint> lengthConstraints = ((ExtendedType) leafTypeDef)
473                     .getLengthConstraints();
474             for (LengthConstraint lengthConstraint : lengthConstraints) {
475                 Number min = lengthConstraint.getMin();
476                 Number max = lengthConstraint.getMax();
477                 property.putOpt(MIN_LENGTH_KEY, min);
478                 property.putOpt(MAX_LENGTH_KEY, max);
479             }
480             String jsonType = YANG_TYPE_TO_JSON_TYPE_MAPPING.get(leafBaseType.getClass());
481             property.putOpt(TYPE_KEY, jsonType);
482         }
483
484     }
485
486     /*
487    *
488    */
489     private void processBinaryType(BinaryTypeDefinition binaryType, JSONObject property)
490             throws JSONException {
491         property.put(TYPE_KEY, STRING);
492         JSONObject media = new JSONObject();
493         media.put(BINARY_ENCODING_KEY, BASE_64);
494         property.put(MEDIA_KEY, media);
495     }
496
497     /**
498      *
499      * @param enumLeafType
500      * @param property
501      * @throws JSONException
502      */
503     private void processEnumType(EnumerationType enumLeafType, JSONObject property)
504             throws JSONException {
505         List<EnumPair> enumPairs = enumLeafType.getValues();
506         List<String> enumNames = new ArrayList<String>();
507         for (EnumPair enumPair : enumPairs) {
508             enumNames.add(enumPair.getName());
509         }
510         property.putOpt(ENUM, new JSONArray(enumNames));
511     }
512
513     /**
514      *
515      * @param bitsType
516      * @param property
517      * @throws JSONException
518      */
519     private void processBitsType(BitsTypeDefinition bitsType, JSONObject property)
520             throws JSONException {
521         property.put(TYPE_KEY, ARRAY_TYPE);
522         property.put(MIN_ITEMS, 0);
523         property.put(UNIQUE_ITEMS_KEY, true);
524         JSONArray enumValues = new JSONArray();
525
526         List<Bit> bits = bitsType.getBits();
527         for (Bit bit : bits) {
528             enumValues.put(bit.getName());
529         }
530         JSONObject itemsValue = new JSONObject();
531         itemsValue.put(ENUM, enumValues);
532         property.put(ITEMS_KEY, itemsValue);
533     }
534
535     /**
536      *
537      * @param unionType
538      * @param property
539      * @throws JSONException
540      */
541     private void processUnionType(UnionTypeDefinition unionType, JSONObject property)
542             throws JSONException {
543
544         List<TypeDefinition<?>> unionTypes = unionType.getTypes();
545         JSONArray unionArray = new JSONArray();
546         for (TypeDefinition<?> typeDef : unionTypes) {
547             unionArray.put(YANG_TYPE_TO_JSON_TYPE_MAPPING.get(typeDef.getClass()));
548         }
549         property.put(TYPE_KEY, unionArray);
550     }
551
552     /**
553      * Helper method to generate a pre-filled JSON schema object.
554      *
555      * @return
556      * @throws JSONException
557      */
558     private JSONObject getSchemaTemplate() throws JSONException {
559         JSONObject schemaJSON = new JSONObject();
560         schemaJSON.put(SCHEMA_KEY, SCHEMA_URL);
561
562         return schemaJSON;
563     }
564 }