bba5e89521995d1f4dda762df06049af62e0fd4c
[mdsal.git] / binding / maven-sal-api-gen-plugin / src / main / java / org / opendaylight / mdsal / binding / yang / wadl / generator / WadlRestconfGenerator.xtend
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.mdsal.binding.yang.wadl.generator
9
10 import static com.google.common.base.Preconditions.checkState;
11
12 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13 import java.io.BufferedWriter
14 import java.io.File
15 import java.io.IOException
16 import java.io.OutputStreamWriter
17 import java.nio.charset.StandardCharsets
18 import java.util.ArrayList
19 import java.util.Collection
20 import java.util.HashSet
21 import java.util.List
22 import org.opendaylight.yangtools.yang.common.XMLNamespace
23 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
24 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
25 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
26 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext
27 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
28 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
29 import org.opendaylight.yangtools.yang.model.api.Module
30 import org.slf4j.Logger
31 import org.slf4j.LoggerFactory
32 import org.sonatype.plexus.build.incremental.BuildContext
33
34 class WadlRestconfGenerator {
35
36     static val Logger LOG = LoggerFactory.getLogger(WadlRestconfGenerator)
37
38     static val PATH_DELIMETER = '/'
39     val BuildContext buildContext;
40     val File path
41     var EffectiveModelContext context;
42     var List<DataSchemaNode> configData;
43     var List<DataSchemaNode> operationalData;
44     var Module module;
45     var List<LeafSchemaNode> pathListParams;
46
47     new(BuildContext buildContext, File targetPath) {
48         if (!targetPath.exists) {
49             checkState(targetPath.mkdirs, "Unable to create directory: %s", targetPath);
50         }
51         path = targetPath
52         this.buildContext = buildContext
53     }
54
55     def generate(EffectiveModelContext context, Collection<? extends Module> modules) {
56         val result = new HashSet;
57         this.context = context
58         for (module : modules) {
59             val dataContainers = module.childNodes.filter[it|it.listOrContainer]
60             if (!dataContainers.empty || !module.rpcs.nullOrEmpty) {
61                 configData = new ArrayList
62                 operationalData = new ArrayList
63
64                 for (data : dataContainers) {
65                     if (data.configuration) {
66                         configData.add(data)
67                     } else {
68                         operationalData.add(data)
69                     }
70                 }
71
72                 this.module = module
73                 val destination = new File(path, '''«module.name».wadl''')
74                 var OutputStreamWriter fw
75                 var BufferedWriter bw
76                 try {
77                     fw = new OutputStreamWriter(buildContext.newFileOutputStream(destination), StandardCharsets.UTF_8)
78                     bw = new BufferedWriter(fw)
79                     bw.append(application);
80                 } catch (IOException e) {
81                     LOG.error("Failed to emit file {}", destination, e);
82                 } finally {
83                     if (bw !== null) {
84                         bw.close();
85                     }
86                     if (fw !== null) {
87                         fw.close();
88                     }
89                 }
90                 result.add(destination)
91             }
92         }
93         return result
94     }
95
96     private def application() '''
97         <?xml version="1.0"?>
98         <application xmlns="http://wadl.dev.java.net/2009/02" «module.importsAsNamespaces» xmlns:«module.prefix»="«module.namespace»">
99
100             <grammars>
101                 <include href="«module.name».yang"/>
102                 «FOR imprt : module.imports»
103                     <include href="«imprt.moduleName».yang"/>
104                 «ENDFOR»
105             </grammars>
106
107             <resources base="http://localhost:9998/restconf">
108                 «IF !operationalData.nullOrEmpty»
109                 <resource path="operational">
110                     «FOR schemaNode : operationalData»
111                         «schemaNode.firstResource(false)»
112                     «ENDFOR»
113                 </resource>
114                 «ENDIF»
115                 «IF !configData.nullOrEmpty»
116                 <resource path="config">
117                     «FOR schemaNode : configData»
118                         «schemaNode.mehodPost»
119                     «ENDFOR»
120                     «FOR schemaNode : configData»
121                         «schemaNode.firstResource(true)»
122                     «ENDFOR»
123                 </resource>
124                 «ENDIF»
125                 «IF !module.rpcs.nullOrEmpty»
126                 <resource path="operations">
127                     «FOR rpc : module.rpcs»
128                         <resource path="«module.name»:«rpc.QName.localName»">
129                             «methodPostRpc(rpc.input !== null, rpc.output !== null)»
130                         </resource>
131                     «ENDFOR»
132                 </resource>
133                 «ENDIF»
134             </resources>
135         </application>
136     '''
137
138     private def importsAsNamespaces(Module module) '''
139         «FOR imprt : module.imports»
140             xmlns:«imprt.prefix»="«context.findModule(imprt.moduleName, imprt.revision).get.namespace»"
141         «ENDFOR»
142     '''
143
144     private def String firstResource(DataSchemaNode schemaNode, boolean config) '''
145         <resource path="«module.name»:«schemaNode.createPath»">
146             «resourceBody(schemaNode, config)»
147         </resource>
148     '''
149
150     private def String resource(DataSchemaNode schemaNode, boolean config) '''
151         <resource path="«schemaNode.createPath»">
152             «resourceBody(schemaNode, config)»
153         </resource>
154     '''
155
156     private def String createPath(DataSchemaNode schemaNode) {
157         pathListParams = new ArrayList
158         var StringBuilder path = new StringBuilder
159         path.append(schemaNode.QName.localName)
160         if (schemaNode instanceof ListSchemaNode) {
161             for (listKey : schemaNode.keyDefinition) {
162                 pathListParams.add((schemaNode as DataNodeContainer).getDataChildByName(listKey) as LeafSchemaNode)
163                 path.append(PATH_DELIMETER)
164                 path.append('{')
165                 path.append(listKey.localName)
166                 path.append('}')
167             }
168         }
169         return path.toString
170     }
171
172     private def String resourceBody(DataSchemaNode schemaNode, boolean config) '''
173         «IF !pathListParams.nullOrEmpty»
174             «resourceParams»
175         «ENDIF»
176         «schemaNode.methodGet»
177         «val children = (schemaNode as DataNodeContainer).childNodes.filter[it|it.listOrContainer]»
178         «IF config»
179             «schemaNode.methodDelete»
180             «schemaNode.mehodPut»
181             «FOR child : children»
182                 «child.mehodPost»
183             «ENDFOR»
184         «ENDIF»
185         «FOR child : children»
186             «child.resource(config)»
187         «ENDFOR»
188     '''
189
190     private def resourceParams() '''
191         «FOR pathParam : pathListParams»
192             «IF pathParam !== null»
193             «val type = pathParam.type.QName.localName»
194             <param required="true" style="template" name="«pathParam.QName.localName»" type="«type»"/>
195             «ENDIF»
196         «ENDFOR»
197     '''
198
199     private def methodGet(DataSchemaNode schemaNode) '''
200         <method name="GET">
201             <response>
202                 «representation(schemaNode.QName.namespace, schemaNode.QName.localName)»
203             </response>
204         </method>
205     '''
206
207     private def mehodPut(DataSchemaNode schemaNode) '''
208         <method name="PUT">
209             <request>
210                 «representation(schemaNode.QName.namespace, schemaNode.QName.localName)»
211             </request>
212         </method>
213     '''
214
215     private def mehodPost(DataSchemaNode schemaNode) '''
216         <method name="POST">
217             <request>
218                 «representation(schemaNode.QName.namespace, schemaNode.QName.localName)»
219             </request>
220         </method>
221     '''
222
223     private def methodPostRpc(boolean input, boolean output) '''
224         <method name="POST">
225             «IF input»
226             <request>
227                 «representation(null, "input")»
228             </request>
229             «ENDIF»
230             «IF output»
231             <response>
232                 «representation(null, "output")»
233             </response>
234             «ENDIF»
235         </method>
236     '''
237
238     private def methodDelete(DataSchemaNode schemaNode) '''
239         <method name="DELETE" />
240     '''
241
242     private def representation(XMLNamespace prefix, String name) '''
243         «val elementData = name»
244         <representation mediaType="application/xml" element="«elementData»"/>
245         <representation mediaType="text/xml" element="«elementData»"/>
246         <representation mediaType="application/json" element="«elementData»"/>
247         <representation mediaType="application/yang.data+xml" element="«elementData»"/>
248         <representation mediaType="application/yang.data+json" element="«elementData»"/>
249     '''
250
251     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
252                 justification = "https://github.com/spotbugs/spotbugs/issues/811")
253     private def boolean isListOrContainer(DataSchemaNode schemaNode) {
254         return (schemaNode instanceof ListSchemaNode || schemaNode instanceof ContainerSchemaNode)
255     }
256
257 }