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