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