1 package org.opendaylight.controller.sal.restconf.impl
3 import com.google.common.base.Preconditions
5 import java.util.ArrayList
6 import java.util.HashMap
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
31 import static javax.ws.rs.core.Response.Status.*
33 class RestconfImpl implements RestconfService {
35 val static RestconfImpl INSTANCE = new RestconfImpl
36 val static MOUNT_POINT_MODULE_NAME = "ietf-netconf"
42 extension ControllerContext controllerContext
45 if (INSTANCE !== null) {
46 throw new IllegalStateException("Already instantiated");
50 static def getInstance() {
54 override readAllData() {
56 // return broker.readOperationalData("".toInstanceIdentifier.getInstanceIdentifier);
57 throw new UnsupportedOperationException("Reading all data is currently not supported.")
60 override getModules() {
61 throw new UnsupportedOperationException("TODO: auto-generated method stub")
68 override invokeRpc(String identifier, CompositeNode payload) {
69 return callRpc(identifier.rpcDefinition, payload)
72 override invokeRpc(String identifier) {
73 return callRpc(identifier.rpcDefinition, null)
76 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
78 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
80 var CompositeNode rpcRequest;
81 if (payload === null) {
82 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
84 val value = normalizeNode(payload, rpc.input, null)
85 val List<Node<?>> input = new ArrayList
87 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
89 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
90 if (!rpcResult.successful) {
91 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
93 if (rpcResult.result === null) {
96 return new StructuredData(rpcResult.result, rpc.output, null)
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)
105 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
107 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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)
116 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
118 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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)
127 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
129 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
132 override updateConfigurationDataLegacy(String identifier, CompositeNode payload) {
133 updateConfigurationData(identifier, payload);
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()
144 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
146 switch status.result {
147 case TransactionStatus.COMMITED: Response.status(OK).build
148 default: Response.status(INTERNAL_SERVER_ERROR).build
152 override createConfigurationDataLegacy(String identifier, CompositeNode payload) {
153 createConfigurationData(identifier, payload);
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)");
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.");
168 val completIdentifier = identifier.addMountPointIdentifier
169 iiWithData = completIdentifier.toInstanceIdentifier
170 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
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 + "\"")
178 val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
179 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
180 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
182 var RpcResult<TransactionStatus> status = null
183 if (iiWithData.mountPoint !== null) {
184 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
185 iiWithData.instanceIdentifier, value)?.get();
187 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
189 if (status === null) {
190 return Response.status(ACCEPTED).build
192 switch status.result {
193 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
194 default: Response.status(INTERNAL_SERVER_ERROR).build
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)");
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)");
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();
216 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
218 if (status === null) {
219 return Response.status(ACCEPTED).build
221 switch status.result {
222 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
223 default: Response.status(INTERNAL_SERVER_ERROR).build
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;
234 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
236 switch status.result {
237 case TransactionStatus.COMMITED: Response.status(OK).build
238 default: Response.status(INTERNAL_SERVER_ERROR).build
242 private def dispatch URI namespace(CompositeNode data) {
243 return data.nodeType.namespace
246 private def dispatch URI namespace(CompositeNodeWrapper data) {
247 return data.namespace
250 private def dispatch String localName(CompositeNode data) {
251 return data.nodeType.localName
254 private def dispatch String localName(CompositeNodeWrapper data) {
255 return data.localName
258 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
259 if (mountPoint !== null) {
260 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
262 return findModuleByNamespace(data.nodeType.namespace)
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
275 module = data.namespace.findModuleByNamespace // namespace from XML
276 if (module === null) {
277 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
283 private def dispatch getName(CompositeNode data) {
284 return data.nodeType.localName
287 private def dispatch getName(CompositeNodeWrapper data) {
288 return data.localName
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
298 iiBuilder = InstanceIdentifier.builder(iiOriginal)
301 if (schemaOfData instanceof ListSchemaNode) {
302 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
304 iiBuilder.node(schemaOfData.QName)
306 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
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 + "\"")
318 keyValues.put(key, dataNodeKeyValueObject);
323 private def endsWithMountPoint(String identifier) {
324 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
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)
332 private def addMountPointIdentifier(String identifier) {
333 if (identifier.endsWith("/")) {
334 return identifier + ControllerContext.MOUNT
336 return identifier + "/" + ControllerContext.MOUNT
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)
344 if (!(schema instanceof DataNodeContainer)) {
345 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
347 if (node instanceof CompositeNodeWrapper) {
348 if ((node as CompositeNodeWrapper).changeAllowed) {
349 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
351 return (node as CompositeNodeWrapper).unwrap()
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.");
363 var QName currentAugment;
364 if (nodeBuilder.qname !== null) {
365 currentAugment = previousAugment
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 + "\".");
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")
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)
391 var rightNodeSchemaFound = false
392 for (potentialSchemaNode : potentialSchemaNodes) {
393 if (!rightNodeSchemaFound) {
394 val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment,
396 if (child.qname !== null) {
397 normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint)
398 rightNodeSchemaFound = true
402 if (!rightNodeSchemaFound) {
403 throw new ResponseException(BAD_REQUEST,
404 "Schema node \"" + child.localName + "\" was not found in module.")
407 if (schema instanceof ListSchemaNode) {
408 val listKeys = (schema as ListSchemaNode).keyDefinition
409 for (listKey : listKeys) {
411 for (child : children) {
412 if (child.unwrap.nodeType.localName == listKey.localName) {
417 throw new ResponseException(BAD_REQUEST,
418 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName +
423 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
424 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
425 val value = simpleNode.value
426 var inputValue = value;
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
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) {
442 // FIXME: Add presence check
443 emptyNodeBuilder.setComposite(true);
448 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
449 var baseType = node.type
450 while (baseType.baseType !== null) {
451 baseType = baseType.baseType;
456 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
457 var TypeDefinition<?> baseType = node.type
458 while (baseType.baseType !== null) {
459 baseType = baseType.baseType;
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);
473 var String moduleName = null;
474 if (mountPoint === null) {
475 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
477 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
479 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
480 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
481 nodeBuilder.qname = validQName
483 return currentAugment