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
9 import javax.ws.rs.core.Response
10 import org.opendaylight.controller.md.sal.common.api.TransactionStatus
11 import org.opendaylight.controller.sal.core.api.mount.MountInstance
12 import org.opendaylight.controller.sal.rest.api.RestconfService
13 import org.opendaylight.yangtools.yang.common.QName
14 import org.opendaylight.yangtools.yang.common.RpcResult
15 import org.opendaylight.yangtools.yang.data.api.CompositeNode
16 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
17 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
18 import org.opendaylight.yangtools.yang.data.api.Node
19 import org.opendaylight.yangtools.yang.data.impl.NodeFactory
20 import org.opendaylight.yangtools.yang.model.api.ChoiceNode
21 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
22 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
23 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
24 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
25 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
26 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
27 import org.opendaylight.yangtools.yang.model.api.Module
28 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
29 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
30 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
32 import static javax.ws.rs.core.Response.Status.*
33 import org.opendaylight.yangtools.yang.model.api.SchemaContext
35 class RestconfImpl implements RestconfService {
37 val static RestconfImpl INSTANCE = new RestconfImpl
38 val static MOUNT_POINT_MODULE_NAME = "ietf-netconf"
44 extension ControllerContext controllerContext
47 if (INSTANCE !== null) {
48 throw new IllegalStateException("Already instantiated");
52 static def getInstance() {
56 override readAllData() {
57 // return broker.readOperationalData("".toInstanceIdentifier.getInstanceIdentifier);
58 throw new UnsupportedOperationException("Reading all data is currently not supported.")
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) {
74 return callRpc(identifier.rpcDefinition, null)
77 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
79 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
81 var CompositeNode rpcRequest;
82 if (payload === null) {
83 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
85 val value = normalizeNode(payload, rpc.input, null)
86 val List<Node<?>> input = new ArrayList
88 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
90 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
91 if (!rpcResult.successful) {
92 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
94 if (rpcResult.result === null) {
97 return new StructuredData(rpcResult.result, rpc.output, null)
100 override readData(String identifier) {
101 val iiWithData = identifier.toInstanceIdentifier
102 var CompositeNode data = null;
103 if (iiWithData.mountPoint !== null) {
104 data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.instanceIdentifier)
106 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
108 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
111 override readConfigurationData(String identifier) {
112 val iiWithData = identifier.toInstanceIdentifier
113 var CompositeNode data = null;
114 if (iiWithData.mountPoint !== null) {
115 data = broker.readConfigurationDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
117 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
119 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
122 override readOperationalData(String identifier) {
123 val iiWithData = identifier.toInstanceIdentifier
124 var CompositeNode data = null;
125 if (iiWithData.mountPoint !== null) {
126 data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
128 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
130 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
133 override updateConfigurationDataLegacy(String identifier, CompositeNode payload) {
134 updateConfigurationData(identifier, payload);
137 override updateConfigurationData(String identifier, CompositeNode payload) {
138 val iiWithData = identifier.toInstanceIdentifier
139 val value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
140 var RpcResult<TransactionStatus> status = null
141 if (iiWithData.mountPoint !== null) {
142 status = broker.commitConfigurationDataPutBehindMountPoint(iiWithData.mountPoint,
143 iiWithData.instanceIdentifier, value).get()
145 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
147 switch status.result {
148 case TransactionStatus.COMMITED: Response.status(OK).build
149 default: Response.status(INTERNAL_SERVER_ERROR).build
153 override createConfigurationDataLegacy(String identifier, CompositeNode payload) {
154 createConfigurationData(identifier, payload);
157 override createConfigurationData(String identifier, CompositeNode payload) {
158 if (payload.namespace === null) {
159 throw new ResponseException(BAD_REQUEST,
160 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
162 var InstanceIdWithSchemaNode iiWithData;
163 var CompositeNode value;
164 if (payload.representsMountPointRootData) { // payload represents mount point data and URI represents path to the mount point
165 if (identifier.endsWithMountPoint) {
166 throw new ResponseException(BAD_REQUEST,
167 "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + "\" for POST operation.");
169 val completIdentifier = identifier.addMountPointIdentifier
170 iiWithData = completIdentifier.toInstanceIdentifier
171 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
173 val uncompleteInstIdWithData = identifier.toInstanceIdentifier
174 val parentSchema = uncompleteInstIdWithData.schemaNode as DataNodeContainer
175 val namespace = uncompleteInstIdWithData.mountPoint.findModule(payload)?.namespace
176 val schemaNode = parentSchema.findInstanceDataChild(payload.name, namespace)
177 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
178 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
180 var RpcResult<TransactionStatus> status = null
181 if (iiWithData.mountPoint !== null) {
182 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
183 iiWithData.instanceIdentifier, value)?.get();
185 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
187 if (status === null) {
188 return Response.status(ACCEPTED).build
190 switch status.result {
191 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
192 default: Response.status(INTERNAL_SERVER_ERROR).build
196 override createConfigurationData(CompositeNode payload) {
197 if (payload.namespace === null) {
198 throw new ResponseException(BAD_REQUEST,
199 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
201 val module = findModule(null, payload)
202 if (module === null) {
203 throw new ResponseException(BAD_REQUEST,
204 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)");
206 val schemaNode = module.findInstanceDataChild(payload.name, module.namespace)
207 val value = normalizeNode(payload, schemaNode, null)
208 val iiWithData = addLastIdentifierFromData(null, value, schemaNode)
209 var RpcResult<TransactionStatus> status = null
210 if (iiWithData.mountPoint !== null) {
211 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
212 iiWithData.instanceIdentifier, value)?.get();
214 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
216 if (status === null) {
217 return Response.status(ACCEPTED).build
219 switch status.result {
220 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
221 default: Response.status(INTERNAL_SERVER_ERROR).build
225 override deleteConfigurationData(String identifier) {
226 val iiWithData = identifier.toInstanceIdentifier
227 var RpcResult<TransactionStatus> status = null
228 if (iiWithData.mountPoint !== null) {
229 status = broker.commitConfigurationDataDeleteBehindMountPoint(iiWithData.mountPoint,
230 iiWithData.getInstanceIdentifier).get;
232 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
234 switch status.result {
235 case TransactionStatus.COMMITED: Response.status(OK).build
236 default: Response.status(INTERNAL_SERVER_ERROR).build
240 private def dispatch URI namespace(CompositeNode data) {
241 return data.nodeType.namespace
244 private def dispatch URI namespace(CompositeNodeWrapper data) {
245 return data.namespace
248 private def dispatch String localName(CompositeNode data) {
249 return data.nodeType.localName
252 private def dispatch String localName(CompositeNodeWrapper data) {
253 return data.localName
256 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
257 if (mountPoint !== null) {
258 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
260 return findModuleByNamespace(data.nodeType.namespace)
264 private def dispatch Module findModule(MountInstance mountPoint, CompositeNodeWrapper data) {
265 Preconditions.checkNotNull(data.namespace)
266 var Module module = null;
267 if (mountPoint !== null) {
268 module = mountPoint.findModuleByNamespace(data.namespace) // namespace from XML
269 if (module === null) {
270 module = mountPoint.findModuleByName(data.namespace.toString) // namespace (module name) from JSON
273 module = data.namespace.findModuleByNamespace // namespace from XML
274 if (module === null) {
275 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
281 private def dispatch getName(CompositeNode data) {
282 return data.nodeType.localName
285 private def dispatch getName(CompositeNodeWrapper data) {
286 return data.localName
289 private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode,
290 CompositeNode data, DataSchemaNode schemaOfData) {
291 val iiOriginal = identifierWithSchemaNode?.instanceIdentifier
292 var InstanceIdentifierBuilder iiBuilder = null
293 if (iiOriginal === null) {
294 iiBuilder = InstanceIdentifier.builder
296 iiBuilder = InstanceIdentifier.builder(iiOriginal)
299 if (schemaOfData instanceof ListSchemaNode) {
300 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
302 iiBuilder.node(schemaOfData.QName)
304 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
307 private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) {
308 val keyValues = new HashMap<QName, Object>();
309 for (key : listNode.keyDefinition) {
310 val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value
311 if (dataNodeKeyValueObject === null) {
312 throw new ResponseException(BAD_REQUEST,
313 "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" +
314 key.localName + "\"")
316 keyValues.put(key, dataNodeKeyValueObject);
321 private def endsWithMountPoint(String identifier) {
322 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
325 private def representsMountPointRootData(CompositeNode data) {
326 return ((data.namespace == SchemaContext.NAME.namespace || data.namespace == MOUNT_POINT_MODULE_NAME) &&
327 data.localName == SchemaContext.NAME.localName)
330 private def addMountPointIdentifier(String identifier) {
331 if (identifier.endsWith("/")) {
332 return identifier + ControllerContext.MOUNT
334 return identifier + "/" + ControllerContext.MOUNT
337 private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, MountInstance mountPoint) {
338 if (schema === null) {
339 throw new ResponseException(INTERNAL_SERVER_ERROR, "Data schema node was not found for " + node?.nodeType?.localName)
341 if (!(schema instanceof DataNodeContainer)) {
342 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
344 if (node instanceof CompositeNodeWrapper) {
345 if ((node as CompositeNodeWrapper).changeAllowed) {
346 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
348 return (node as CompositeNodeWrapper).unwrap()
353 private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
354 MountInstance mountPoint) {
355 if (schema === null) {
356 throw new ResponseException(BAD_REQUEST,
357 "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema.");
359 var validQName = schema.QName
360 var currentAugment = previousAugment;
361 if (schema.augmenting) {
362 currentAugment = schema.QName
363 } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) {
364 validQName = QName.create(currentAugment, schema.QName.localName);
366 var String moduleName = null;
367 if (mountPoint === null) {
368 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
370 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
372 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
373 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
374 nodeBuilder.qname = validQName
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 + "\".\nIf data is in Json format then module name for \"" +
379 nodeBuilder.localName + "\" should be \"" + moduleName + "\".");
382 if (nodeBuilder instanceof CompositeNodeWrapper) {
383 val List<NodeWrapper<?>> children = (nodeBuilder as CompositeNodeWrapper).getValues
384 for (child : children) {
386 findFirstSchemaByLocalName(child.localName, (schema as DataNodeContainer).childNodes),
387 currentAugment, mountPoint)
389 if(schema instanceof ListSchemaNode) {
390 val listKeys = (schema as ListSchemaNode).keyDefinition
391 for (listKey : listKeys) {
393 for (child : children) {
394 if (child.unwrap.nodeType.localName == listKey.localName) {
399 throw new ResponseException(BAD_REQUEST,
400 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName + "\"")
404 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
405 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
406 val value = simpleNode.value
407 var inputValue = value;
409 if (schema.typeDefinition instanceof IdentityrefTypeDefinition) {
410 if (value instanceof String) {
411 inputValue = new IdentityValuesDTO(validQName.namespace.toString, value as String, null)
412 } // else value is instance of ValuesDTO
415 val outputValue = RestCodec.from(schema.typeDefinition, mountPoint)?.deserialize(inputValue);
416 simpleNode.setValue(outputValue)
417 } else if (nodeBuilder instanceof EmptyNodeWrapper) {
418 val emptyNodeBuilder = nodeBuilder as EmptyNodeWrapper
419 if (schema instanceof LeafSchemaNode) {
420 emptyNodeBuilder.setComposite(false);
421 } else if (schema instanceof ContainerSchemaNode) {
423 // FIXME: Add presence check
424 emptyNodeBuilder.setComposite(true);
429 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
430 var baseType = node.type
431 while (baseType.baseType !== null) {
432 baseType = baseType.baseType;
437 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
438 var TypeDefinition<?> baseType = node.type
439 while (baseType.baseType !== null) {
440 baseType = baseType.baseType;
445 private def DataSchemaNode findFirstSchemaByLocalName(String localName, Set<DataSchemaNode> schemas) {
446 for (schema : schemas) {
447 if (schema instanceof ChoiceNode) {
448 for (caze : (schema as ChoiceNode).cases) {
449 val result = findFirstSchemaByLocalName(localName, caze.childNodes)
450 if (result !== null) {
455 val result = schemas.findFirst[n|n.QName.localName.equals(localName)]
456 if (result !== null) {