Merge "fix of Bug 314"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / RestconfImpl.xtend
1 package org.opendaylight.controller.sal.restconf.impl
2
3 import com.google.common.base.Preconditions
4 import java.net.URI
5 import java.util.ArrayList
6 import java.util.HashMap
7 import java.util.List
8 import java.util.Set
9 import javax.ws.rs.core.Response
10 import org.opendaylight.controller.md.sal.common.api.TransactionStatus
11 import org.opendaylight.controller.sal.core.api.mount.MountInstance
12 import org.opendaylight.controller.sal.rest.api.RestconfService
13 import org.opendaylight.yangtools.yang.common.QName
14 import org.opendaylight.yangtools.yang.common.RpcResult
15 import org.opendaylight.yangtools.yang.data.api.CompositeNode
16 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
17 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
18 import org.opendaylight.yangtools.yang.data.api.Node
19 import org.opendaylight.yangtools.yang.data.impl.NodeFactory
20 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
21 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
22 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
23 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
24 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
25 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
26 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
27 import org.opendaylight.yangtools.yang.model.api.Module
28 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
29 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
30 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
31
32 import static javax.ws.rs.core.Response.Status.*
33 import org.opendaylight.yangtools.yang.model.api.SchemaContext
34
35 class RestconfImpl implements RestconfService {
36
37     val static RestconfImpl INSTANCE = new RestconfImpl
38     val static MOUNT_POINT_MODULE_NAME = "ietf-netconf"
39
40     @Property
41     BrokerFacade broker
42
43     @Property
44     extension ControllerContext controllerContext
45
46     private new() {
47         if (INSTANCE !== null) {
48             throw new IllegalStateException("Already instantiated");
49         }
50     }
51
52     static def getInstance() {
53         return INSTANCE
54     }
55
56     override readAllData() {
57 //        return broker.readOperationalData("".toInstanceIdentifier.getInstanceIdentifier);
58         throw new UnsupportedOperationException("Reading all data is currently not supported.")
59     }
60
61     override getModules() {
62         throw new UnsupportedOperationException("TODO: auto-generated method stub")
63     }
64
65     override getRoot() {
66         return null;
67     }
68
69     override invokeRpc(String identifier, CompositeNode payload) {
70         return callRpc(identifier.rpcDefinition, payload)
71     }
72     
73     override invokeRpc(String identifier) {
74         return callRpc(identifier.rpcDefinition, null)
75     }
76     
77     private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
78         if (rpc === null) {
79             throw new ResponseException(NOT_FOUND, "RPC does not exist.");
80         }
81         var CompositeNode rpcRequest;
82         if (payload === null) {
83             rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
84         } else {
85             val value = normalizeNode(payload, rpc.input, null)
86             val List<Node<?>> input = new ArrayList
87             input.add(value)
88             rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
89         }
90         val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
91         if (!rpcResult.successful) {
92             throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
93         }
94         if (rpcResult.result === null) {
95             return null
96         }
97         return new StructuredData(rpcResult.result, rpc.output, null)
98     }
99
100     override readData(String identifier) {
101         val iiWithData = identifier.toInstanceIdentifier
102         var CompositeNode data = null;
103         if (iiWithData.mountPoint !== null) {
104             data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.instanceIdentifier)
105         } else {
106             data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
107         }
108         return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
109     }
110
111     override readConfigurationData(String identifier) {
112         val iiWithData = identifier.toInstanceIdentifier
113         var CompositeNode data = null;
114         if (iiWithData.mountPoint !== null) {
115             data = broker.readConfigurationDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
116         } else {
117             data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
118         }
119         return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
120     }
121
122     override readOperationalData(String identifier) {
123         val iiWithData = identifier.toInstanceIdentifier
124         var CompositeNode data = null;
125         if (iiWithData.mountPoint !== null) {
126             data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
127         } else {
128             data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
129         }
130         return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
131     }
132
133     override updateConfigurationDataLegacy(String identifier, CompositeNode payload) {
134         updateConfigurationData(identifier, payload);
135     }
136
137     override updateConfigurationData(String identifier, CompositeNode payload) {
138         val iiWithData = identifier.toInstanceIdentifier
139         val value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
140         var RpcResult<TransactionStatus> status = null
141         if (iiWithData.mountPoint !== null) {
142             status = broker.commitConfigurationDataPutBehindMountPoint(iiWithData.mountPoint,
143                 iiWithData.instanceIdentifier, value).get()
144         } else {
145             status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
146         }
147         switch status.result {
148             case TransactionStatus.COMMITED: Response.status(OK).build
149             default: Response.status(INTERNAL_SERVER_ERROR).build
150         }
151     }
152
153     override createConfigurationDataLegacy(String identifier, CompositeNode payload) {
154         createConfigurationData(identifier, payload);
155     }
156
157     override createConfigurationData(String identifier, CompositeNode payload) {
158         if (payload.namespace === null) {
159             throw new ResponseException(BAD_REQUEST,
160                 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
161         }
162         var InstanceIdWithSchemaNode iiWithData;
163         var CompositeNode value;
164         if (payload.representsMountPointRootData) { // payload represents mount point data and URI represents path to the mount point
165             if (identifier.endsWithMountPoint) {
166                 throw new ResponseException(BAD_REQUEST,
167                 "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + "\" for POST operation.");
168             }
169             val completIdentifier = identifier.addMountPointIdentifier
170             iiWithData = completIdentifier.toInstanceIdentifier
171             value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
172         } else {
173             val uncompleteInstIdWithData = identifier.toInstanceIdentifier
174             val parentSchema = uncompleteInstIdWithData.schemaNode as DataNodeContainer
175             val namespace = uncompleteInstIdWithData.mountPoint.findModule(payload)?.namespace
176             val schemaNode = parentSchema.findInstanceDataChild(payload.name, namespace)
177             value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
178             iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
179         }
180         var RpcResult<TransactionStatus> status = null
181         if (iiWithData.mountPoint !== null) {
182             status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
183                 iiWithData.instanceIdentifier, value)?.get();
184         } else {
185             status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
186         }
187         if (status === null) {
188             return Response.status(ACCEPTED).build
189         }
190         switch status.result {
191             case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
192             default: Response.status(INTERNAL_SERVER_ERROR).build
193         }
194     }
195     
196     override createConfigurationData(CompositeNode payload) {
197         if (payload.namespace === null) {
198             throw new ResponseException(BAD_REQUEST,
199                 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
200         }
201         val module = findModule(null, payload)
202         if (module === null) {
203             throw new ResponseException(BAD_REQUEST,
204                 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)");
205         }
206         val schemaNode = module.findInstanceDataChild(payload.name, module.namespace)
207         val value = normalizeNode(payload, schemaNode, null)
208         val iiWithData = addLastIdentifierFromData(null, value, schemaNode)
209         var RpcResult<TransactionStatus> status = null
210         if (iiWithData.mountPoint !== null) {
211             status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
212                 iiWithData.instanceIdentifier, value)?.get();
213         } else {
214             status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
215         }
216         if (status === null) {
217             return Response.status(ACCEPTED).build
218         }
219         switch status.result {
220             case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
221             default: Response.status(INTERNAL_SERVER_ERROR).build
222         }
223     }
224     
225     override deleteConfigurationData(String identifier) {
226         val iiWithData = identifier.toInstanceIdentifier
227         var RpcResult<TransactionStatus> status = null
228         if (iiWithData.mountPoint !== null) {
229             status = broker.commitConfigurationDataDeleteBehindMountPoint(iiWithData.mountPoint,
230                 iiWithData.getInstanceIdentifier).get;
231         } else {
232             status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
233         }
234         switch status.result {
235             case TransactionStatus.COMMITED: Response.status(OK).build
236             default: Response.status(INTERNAL_SERVER_ERROR).build
237         }
238     }
239     
240     private def dispatch URI namespace(CompositeNode data) {
241         return data.nodeType.namespace
242     }
243     
244     private def dispatch URI namespace(CompositeNodeWrapper data) {
245         return data.namespace
246     }
247     
248     private def dispatch String localName(CompositeNode data) {
249         return data.nodeType.localName
250     }
251     
252     private def dispatch String localName(CompositeNodeWrapper data) {
253         return data.localName
254     }
255
256     private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
257         if (mountPoint !== null) {
258             return mountPoint.findModuleByNamespace(data.nodeType.namespace)
259         } else {
260             return findModuleByNamespace(data.nodeType.namespace)
261         }
262     }
263
264     private def dispatch Module findModule(MountInstance mountPoint, CompositeNodeWrapper data) {
265         Preconditions.checkNotNull(data.namespace)
266         var Module module = null;
267         if (mountPoint !== null) {
268             module = mountPoint.findModuleByNamespace(data.namespace) // namespace from XML
269             if (module === null) {
270                 module = mountPoint.findModuleByName(data.namespace.toString) // namespace (module name) from JSON
271             }
272         } else {
273             module = data.namespace.findModuleByNamespace // namespace from XML
274             if (module === null) {
275                 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
276             }
277         }
278         return module
279     }
280     
281     private def dispatch getName(CompositeNode data) {
282         return data.nodeType.localName
283     }
284     
285     private def dispatch getName(CompositeNodeWrapper data) {
286         return data.localName
287     }
288     
289     private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode,
290         CompositeNode data, DataSchemaNode schemaOfData) {
291         val iiOriginal = identifierWithSchemaNode?.instanceIdentifier
292         var InstanceIdentifierBuilder iiBuilder = null
293         if (iiOriginal === null) {
294             iiBuilder = InstanceIdentifier.builder
295         } else {
296             iiBuilder = InstanceIdentifier.builder(iiOriginal)
297         }
298
299         if (schemaOfData instanceof ListSchemaNode) {
300             iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
301         } else {
302             iiBuilder.node(schemaOfData.QName)
303         }
304         return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
305     }
306
307     private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) {
308         val keyValues = new HashMap<QName, Object>();
309         for (key : listNode.keyDefinition) {
310             val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value
311             if (dataNodeKeyValueObject === null) {
312                 throw new ResponseException(BAD_REQUEST,
313                     "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" +
314                         key.localName + "\"")
315             }
316             keyValues.put(key, dataNodeKeyValueObject);
317         }
318         return keyValues
319     }
320     
321     private def endsWithMountPoint(String identifier) {
322         return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
323     }
324     
325     private def representsMountPointRootData(CompositeNode data) {
326         return ((data.namespace == SchemaContext.NAME.namespace || data.namespace == MOUNT_POINT_MODULE_NAME) &&
327             data.localName == SchemaContext.NAME.localName)
328     }
329     
330     private def addMountPointIdentifier(String identifier) {
331         if (identifier.endsWith("/")) {
332             return identifier + ControllerContext.MOUNT
333         }
334         return identifier + "/" + ControllerContext.MOUNT
335     }
336     
337     private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, MountInstance mountPoint) {
338         if (schema === null) {
339             throw new ResponseException(INTERNAL_SERVER_ERROR, "Data schema node was not found for " + node?.nodeType?.localName)
340         }
341         if (!(schema instanceof DataNodeContainer)) {
342             throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
343         }
344         if (node instanceof CompositeNodeWrapper) {
345             if ((node  as CompositeNodeWrapper).changeAllowed) {
346                 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
347             }
348             return (node as CompositeNodeWrapper).unwrap()
349         }
350         return node
351     }
352     
353     private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
354         MountInstance mountPoint) {
355         if (schema === null) {
356             throw new ResponseException(BAD_REQUEST,
357                 "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema.");
358         }
359         var validQName = schema.QName
360         var currentAugment = previousAugment;
361         if (schema.augmenting) {
362             currentAugment = schema.QName
363         } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) {
364             validQName = QName.create(currentAugment, schema.QName.localName);
365         }
366         var String moduleName = null;
367         if (mountPoint === null) {
368             moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
369         } else {
370             moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
371         }
372         if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
373             nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
374             nodeBuilder.qname = validQName
375         } else {
376             throw new ResponseException(BAD_REQUEST,
377                 "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName +
378                     "\" should be \"" + schema.QName.namespace + "\".\nIf data is in Json format then module name for \"" +
379                     nodeBuilder.localName + "\" should be \"" + moduleName + "\".");
380         }
381
382         if (nodeBuilder instanceof CompositeNodeWrapper) {
383             val List<NodeWrapper<?>> children = (nodeBuilder as CompositeNodeWrapper).getValues
384             for (child : children) {
385                 normalizeNode(child,
386                     findFirstSchemaByLocalName(child.localName, (schema as DataNodeContainer).childNodes),
387                     currentAugment, mountPoint)
388             }
389             if(schema instanceof ListSchemaNode) {
390                 val listKeys = (schema as ListSchemaNode).keyDefinition
391                 for (listKey : listKeys) {
392                     var foundKey = false
393                     for (child : children) {
394                         if (child.unwrap.nodeType.localName == listKey.localName) {
395                             foundKey = true;
396                         }
397                     }
398                     if (!foundKey) {
399                         throw new ResponseException(BAD_REQUEST,
400                             "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"")
401                     }
402                 }
403             }
404         } else if (nodeBuilder instanceof SimpleNodeWrapper) {
405             val simpleNode = (nodeBuilder as SimpleNodeWrapper)
406             val value = simpleNode.value
407             var inputValue = value;
408             
409             if (schema.typeDefinition instanceof IdentityrefTypeDefinition) {
410                 if (value instanceof String) {
411                     inputValue = new IdentityValuesDTO(validQName.namespace.toString, value as String, null)
412                 } // else value is instance of ValuesDTO
413             }
414             
415             val outputValue = RestCodec.from(schema.typeDefinition, mountPoint)?.deserialize(inputValue);
416             simpleNode.setValue(outputValue)
417         } else if (nodeBuilder instanceof EmptyNodeWrapper) {
418             val emptyNodeBuilder = nodeBuilder as EmptyNodeWrapper
419             if (schema instanceof LeafSchemaNode) {
420                 emptyNodeBuilder.setComposite(false);
421             } else if (schema instanceof ContainerSchemaNode) {
422
423                 // FIXME: Add presence check
424                 emptyNodeBuilder.setComposite(true);
425             }
426         }
427     }
428
429     private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
430         var baseType = node.type
431         while (baseType.baseType !== null) {
432             baseType = baseType.baseType;
433         }
434         baseType
435     }
436
437     private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
438         var TypeDefinition<?> baseType = node.type
439         while (baseType.baseType !== null) {
440             baseType = baseType.baseType;
441         }
442         baseType
443     }
444
445     private def DataSchemaNode findFirstSchemaByLocalName(String localName, Set<DataSchemaNode> schemas) {
446         for (schema : schemas) {
447             if (schema instanceof ChoiceNode) {
448                 for (caze : (schema as ChoiceNode).cases) {
449                     val result = findFirstSchemaByLocalName(localName, caze.childNodes)
450                     if (result !== null) {
451                         return result
452                     }
453                 }
454             } else {
455                 val result = schemas.findFirst[n|n.QName.localName.equals(localName)]
456                 if (result !== null) {
457                     return result;
458
459                 }
460             }
461         }
462         return null
463     }
464
465 }