2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sal.restconf.impl
10 import com.google.common.base.Preconditions
12 import java.util.ArrayList
13 import java.util.HashMap
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
38 import static javax.ws.rs.core.Response.Status.*
40 class RestconfImpl implements RestconfService {
42 val static RestconfImpl INSTANCE = new RestconfImpl
43 val static MOUNT_POINT_MODULE_NAME = "ietf-netconf"
49 extension ControllerContext controllerContext
52 if (INSTANCE !== null) {
53 throw new IllegalStateException("Already instantiated");
57 static def getInstance() {
61 override readAllData() {
63 // return broker.readOperationalData("".toInstanceIdentifier.getInstanceIdentifier);
64 throw new UnsupportedOperationException("Reading all data is currently not supported.")
67 override getModules() {
68 throw new UnsupportedOperationException("TODO: auto-generated method stub")
75 override invokeRpc(String identifier, CompositeNode payload) {
76 return callRpc(identifier.rpcDefinition, payload)
79 override invokeRpc(String identifier) {
80 return callRpc(identifier.rpcDefinition, null)
83 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
85 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
87 var CompositeNode rpcRequest;
88 if (payload === null) {
89 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
91 val value = normalizeNode(payload, rpc.input, null)
92 val List<Node<?>> input = new ArrayList
94 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
96 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
97 if (!rpcResult.successful) {
98 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
100 if (rpcResult.result === null) {
103 return new StructuredData(rpcResult.result, rpc.output, null)
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)
112 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
114 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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)
123 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
125 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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)
134 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
136 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
139 override updateConfigurationDataLegacy(String identifier, CompositeNode payload) {
140 updateConfigurationData(identifier, payload);
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()
151 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
153 switch status.result {
154 case TransactionStatus.COMMITED: Response.status(OK).build
155 default: Response.status(INTERNAL_SERVER_ERROR).build
159 override createConfigurationDataLegacy(String identifier, CompositeNode payload) {
160 createConfigurationData(identifier, payload);
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)");
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.");
175 val completIdentifier = identifier.addMountPointIdentifier
176 iiWithData = completIdentifier.toInstanceIdentifier
177 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
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 + "\"")
185 val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
186 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
187 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
189 var RpcResult<TransactionStatus> status = null
190 if (iiWithData.mountPoint !== null) {
191 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
192 iiWithData.instanceIdentifier, value)?.get();
194 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
196 if (status === null) {
197 return Response.status(ACCEPTED).build
199 switch status.result {
200 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
201 default: Response.status(INTERNAL_SERVER_ERROR).build
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)");
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)");
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();
223 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
225 if (status === null) {
226 return Response.status(ACCEPTED).build
228 switch status.result {
229 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
230 default: Response.status(INTERNAL_SERVER_ERROR).build
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;
241 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
243 switch status.result {
244 case TransactionStatus.COMMITED: Response.status(OK).build
245 default: Response.status(INTERNAL_SERVER_ERROR).build
249 private def dispatch URI namespace(CompositeNode data) {
250 return data.nodeType.namespace
253 private def dispatch URI namespace(CompositeNodeWrapper data) {
254 return data.namespace
257 private def dispatch String localName(CompositeNode data) {
258 return data.nodeType.localName
261 private def dispatch String localName(CompositeNodeWrapper data) {
262 return data.localName
265 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
266 if (mountPoint !== null) {
267 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
269 return findModuleByNamespace(data.nodeType.namespace)
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
282 module = data.namespace.findModuleByNamespace // namespace from XML
283 if (module === null) {
284 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
290 private def dispatch getName(CompositeNode data) {
291 return data.nodeType.localName
294 private def dispatch getName(CompositeNodeWrapper data) {
295 return data.localName
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
305 iiBuilder = InstanceIdentifier.builder(iiOriginal)
308 if (schemaOfData instanceof ListSchemaNode) {
309 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
311 iiBuilder.node(schemaOfData.QName)
313 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
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 + "\"")
325 keyValues.put(key, dataNodeKeyValueObject);
330 private def endsWithMountPoint(String identifier) {
331 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
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)
339 private def addMountPointIdentifier(String identifier) {
340 if (identifier.endsWith("/")) {
341 return identifier + ControllerContext.MOUNT
343 return identifier + "/" + ControllerContext.MOUNT
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)
351 if (!(schema instanceof DataNodeContainer)) {
352 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
354 if (node instanceof CompositeNodeWrapper) {
355 if ((node as CompositeNodeWrapper).changeAllowed) {
356 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
358 return (node as CompositeNodeWrapper).unwrap()
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.");
370 var QName currentAugment;
371 if (nodeBuilder.qname !== null) {
372 currentAugment = previousAugment
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 + "\".");
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")
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)
398 var rightNodeSchemaFound = false
399 for (potentialSchemaNode : potentialSchemaNodes) {
400 if (!rightNodeSchemaFound) {
401 val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment,
403 if (child.qname !== null) {
404 normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint)
405 rightNodeSchemaFound = true
409 if (!rightNodeSchemaFound) {
410 throw new ResponseException(BAD_REQUEST,
411 "Schema node \"" + child.localName + "\" was not found in module.")
414 if (schema instanceof ListSchemaNode) {
415 val listKeys = (schema as ListSchemaNode).keyDefinition
416 for (listKey : listKeys) {
418 for (child : children) {
419 if (child.unwrap.nodeType.localName == listKey.localName) {
424 throw new ResponseException(BAD_REQUEST,
425 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName +
430 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
431 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
432 val value = simpleNode.value
433 var inputValue = value;
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
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) {
449 // FIXME: Add presence check
450 emptyNodeBuilder.setComposite(true);
455 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
456 var baseType = node.type
457 while (baseType.baseType !== null) {
458 baseType = baseType.baseType;
463 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
464 var TypeDefinition<?> baseType = node.type
465 while (baseType.baseType !== null) {
466 baseType = baseType.baseType;
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);
480 var String moduleName = null;
481 if (mountPoint === null) {
482 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
484 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
486 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
487 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
488 nodeBuilder.qname = validQName
490 return currentAugment