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 getModules() {
62 throw new UnsupportedOperationException("TODO: auto-generated method stub")
69 override invokeRpc(String identifier, CompositeNode payload) {
70 return callRpc(identifier.rpcDefinition, payload)
73 override invokeRpc(String identifier, String noPayload) {
74 if (!noPayload.nullOrEmpty) {
75 throw new ResponseException(UNSUPPORTED_MEDIA_TYPE, "Content-Type contains unsupported Media Type.");
77 return callRpc(identifier.rpcDefinition, null)
80 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
82 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
84 var CompositeNode rpcRequest;
85 if (payload === null) {
86 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
88 val value = normalizeNode(payload, rpc.input, null)
89 val List<Node<?>> input = new ArrayList
91 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
93 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
94 if (!rpcResult.successful) {
95 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
97 if (rpcResult.result === null) {
100 return new StructuredData(rpcResult.result, rpc.output, null)
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)
109 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
111 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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)
120 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
122 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
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()
133 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
135 switch status.result {
136 case TransactionStatus.COMMITED: Response.status(OK).build
137 default: Response.status(INTERNAL_SERVER_ERROR).build
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)");
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.");
153 val completIdentifier = identifier.addMountPointIdentifier
154 iiWithData = completIdentifier.toInstanceIdentifier
155 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
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 + "\"")
163 val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
164 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
165 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
167 var RpcResult<TransactionStatus> status = null
168 if (iiWithData.mountPoint !== null) {
169 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
170 iiWithData.instanceIdentifier, value)?.get();
172 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
174 if (status === null) {
175 return Response.status(ACCEPTED).build
177 switch status.result {
178 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
179 default: Response.status(INTERNAL_SERVER_ERROR).build
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)");
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)");
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();
201 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
203 if (status === null) {
204 return Response.status(ACCEPTED).build
206 switch status.result {
207 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
208 default: Response.status(INTERNAL_SERVER_ERROR).build
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;
219 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
221 switch status.result {
222 case TransactionStatus.COMMITED: Response.status(OK).build
223 default: Response.status(INTERNAL_SERVER_ERROR).build
227 private def dispatch URI namespace(CompositeNode data) {
228 return data.nodeType.namespace
231 private def dispatch URI namespace(CompositeNodeWrapper data) {
232 return data.namespace
235 private def dispatch String localName(CompositeNode data) {
236 return data.nodeType.localName
239 private def dispatch String localName(CompositeNodeWrapper data) {
240 return data.localName
243 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
244 if (mountPoint !== null) {
245 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
247 return findModuleByNamespace(data.nodeType.namespace)
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
260 module = data.namespace.findModuleByNamespace // namespace from XML
261 if (module === null) {
262 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
268 private def dispatch getName(CompositeNode data) {
269 return data.nodeType.localName
272 private def dispatch getName(CompositeNodeWrapper data) {
273 return data.localName
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
283 iiBuilder = InstanceIdentifier.builder(iiOriginal)
286 if (schemaOfData instanceof ListSchemaNode) {
287 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
289 iiBuilder.node(schemaOfData.QName)
291 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
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 + "\"")
303 keyValues.put(key, dataNodeKeyValueObject);
308 private def endsWithMountPoint(String identifier) {
309 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
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)
317 private def addMountPointIdentifier(String identifier) {
318 if (identifier.endsWith("/")) {
319 return identifier + ControllerContext.MOUNT
321 return identifier + "/" + ControllerContext.MOUNT
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)
329 if (!(schema instanceof DataNodeContainer)) {
330 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
332 if (node instanceof CompositeNodeWrapper) {
333 if ((node as CompositeNodeWrapper).changeAllowed) {
334 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
336 return (node as CompositeNodeWrapper).unwrap()
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.");
348 var QName currentAugment;
349 if (nodeBuilder.qname !== null) {
350 currentAugment = previousAugment
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 + "\".");
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")
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)
376 var rightNodeSchemaFound = false
377 for (potentialSchemaNode : potentialSchemaNodes) {
378 if (!rightNodeSchemaFound) {
379 val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment,
381 if (child.qname !== null) {
382 normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint)
383 rightNodeSchemaFound = true
387 if (!rightNodeSchemaFound) {
388 throw new ResponseException(BAD_REQUEST,
389 "Schema node \"" + child.localName + "\" was not found in module.")
392 if (schema instanceof ListSchemaNode) {
393 val listKeys = (schema as ListSchemaNode).keyDefinition
394 for (listKey : listKeys) {
396 for (child : children) {
397 if (child.unwrap.nodeType.localName == listKey.localName) {
402 throw new ResponseException(BAD_REQUEST,
403 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName +
408 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
409 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
410 val value = simpleNode.value
411 var inputValue = value;
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
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) {
427 // FIXME: Add presence check
428 emptyNodeBuilder.setComposite(true);
433 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
434 var baseType = node.type
435 while (baseType.baseType !== null) {
436 baseType = baseType.baseType;
441 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
442 var TypeDefinition<?> baseType = node.type
443 while (baseType.baseType !== null) {
444 baseType = baseType.baseType;
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);
458 var String moduleName = null;
459 if (mountPoint === null) {
460 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
462 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
464 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
465 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
466 nodeBuilder.qname = validQName
468 return currentAugment