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