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