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
11 import com.google.common.base.Splitter
12 import com.google.common.collect.Lists
14 import java.text.ParseException
15 import java.text.SimpleDateFormat
16 import java.util.ArrayList
17 import java.util.HashMap
20 import javax.ws.rs.core.Response
21 import javax.ws.rs.core.UriInfo
22 import org.opendaylight.controller.md.sal.common.api.TransactionStatus
23 import org.opendaylight.controller.sal.core.api.mount.MountInstance
24 import org.opendaylight.controller.sal.rest.api.RestconfService
25 import org.opendaylight.controller.sal.streams.listeners.Notificator
26 import org.opendaylight.controller.sal.streams.websockets.WebSocketServer
27 import org.opendaylight.yangtools.yang.common.QName
28 import org.opendaylight.yangtools.yang.common.RpcResult
29 import org.opendaylight.yangtools.yang.data.api.CompositeNode
30 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier
31 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder
32 import org.opendaylight.yangtools.yang.data.api.Node
33 import org.opendaylight.yangtools.yang.data.impl.NodeFactory
34 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
35 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
36 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
37 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode
38 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
40 import org.opendaylight.yangtools.yang.model.api.Module
41 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext
43 import org.opendaylight.yangtools.yang.model.api.TypeDefinition
44 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition
45 import org.opendaylight.yangtools.yang.model.util.EmptyType
46 import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder
47 import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder
49 import static javax.ws.rs.core.Response.Status.*
51 class RestconfImpl implements RestconfService {
53 val static RestconfImpl INSTANCE = new RestconfImpl
54 val static MOUNT_POINT_MODULE_NAME = "ietf-netconf"
55 val static REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd")
56 val static RESTCONF_MODULE_DRAFT02_REVISION = "2013-10-19"
57 val static RESTCONF_MODULE_DRAFT02_NAME = "ietf-restconf"
58 val static RESTCONF_MODULE_DRAFT02_NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf"
59 val static RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE = "restconf"
60 val static RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE = "restconf"
61 val static RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE = "modules"
62 val static RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE = "module"
63 val static RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations"
64 val static SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
65 val static SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription"
71 extension ControllerContext controllerContext
74 if (INSTANCE !== null) {
75 throw new IllegalStateException("Already instantiated");
79 static def getInstance() {
83 override getModules() {
84 val restconfModule = getRestconfModule()
85 val List<Node<?>> modulesAsData = new ArrayList
86 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
87 for (module : allModules) {
88 modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode))
90 val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)
91 val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData)
92 return new StructuredData(modulesNode, modulesSchemaNode, null)
95 override getModules(String identifier) {
96 var Set<Module> modules = null
97 var MountInstance mountPoint = null
98 if (identifier.contains(ControllerContext.MOUNT)) {
99 mountPoint = identifier.toMountPointIdentifier.mountPoint
100 modules = mountPoint.allModules
102 throw new ResponseException(BAD_REQUEST, "URI has bad format. If modules behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT)
104 val List<Node<?>> modulesAsData = new ArrayList
105 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
106 for (module : modules) {
107 modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode))
109 val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)
110 val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData)
111 return new StructuredData(modulesNode, modulesSchemaNode, mountPoint)
114 override getModule(String identifier) {
115 val moduleNameAndRevision = identifier.moduleNameAndRevision
116 var Module module = null
117 var MountInstance mountPoint = null
118 if (identifier.contains(ControllerContext.MOUNT)) {
119 mountPoint = identifier.toMountPointIdentifier.mountPoint
120 module = mountPoint.findModuleByNameAndRevision(moduleNameAndRevision)
122 module = findModuleByNameAndRevision(moduleNameAndRevision)
124 if (module === null) {
125 throw new ResponseException(BAD_REQUEST,
126 "Module with name '" + moduleNameAndRevision.localName + "' and revision '" +
127 moduleNameAndRevision.revision + "' was not found.")
129 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
130 val moduleNode = module.toModuleCompositeNode(moduleSchemaNode)
131 return new StructuredData(moduleNode, moduleSchemaNode, mountPoint)
134 override getOperations() {
135 return operationsFromModulesToStructuredData(allModules,null)
138 override getOperations(String identifier) {
139 var Set<Module> modules = null
140 var MountInstance mountPoint = null
141 if (identifier.contains(ControllerContext.MOUNT)) {
142 mountPoint = identifier.toMountPointIdentifier.mountPoint
143 modules = mountPoint.allModules
145 throw new ResponseException(BAD_REQUEST, "URI has bad format. If operations behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT)
147 return operationsFromModulesToStructuredData(modules,mountPoint)
150 private def StructuredData operationsFromModulesToStructuredData(Set<Module> modules,MountInstance mountPoint) {
151 val List<Node<?>> operationsAsData = new ArrayList
152 val operationsSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE)
153 val fakeOperationsSchemaNode = new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, operationsSchemaNode.QName, operationsSchemaNode.path)
154 for (module : modules) {
155 for (rpc : module.rpcs) {
156 operationsAsData.add(NodeFactory.createImmutableSimpleNode(rpc.QName, null, null))
157 val fakeRpcSchemaNode = new LeafSchemaNodeBuilder(module.name, 0, rpc.QName, null)
158 fakeRpcSchemaNode.setAugmenting(true)
159 fakeRpcSchemaNode.setType(EmptyType.instance)
160 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build)
163 val operationsNode = NodeFactory.createImmutableCompositeNode(operationsSchemaNode.QName, null, operationsAsData)
164 return new StructuredData(operationsNode, fakeOperationsSchemaNode.build, mountPoint)
167 private def Module getRestconfModule() {
168 val restconfModule = findModuleByNameAndRevision(
169 QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION,
170 RESTCONF_MODULE_DRAFT02_NAME))
171 if (restconfModule === null) {
172 throw new ResponseException(INTERNAL_SERVER_ERROR, "Restconf module was not found.")
174 return restconfModule
177 private def QName getModuleNameAndRevision(String identifier) {
178 val indexOfMountPointFirstLetter = identifier.indexOf(ControllerContext.MOUNT)
179 var moduleNameAndRevision = "";
180 if (indexOfMountPointFirstLetter !== -1) { // module and revision is behind mount point string
181 moduleNameAndRevision = identifier.substring(indexOfMountPointFirstLetter + ControllerContext.MOUNT.length)
183 moduleNameAndRevision = identifier
185 val pathArgs = Lists.newArrayList(Splitter.on("/").omitEmptyStrings.split(moduleNameAndRevision))
186 if (pathArgs.length < 2) {
187 throw new ResponseException(BAD_REQUEST,
188 "URI has bad format. End of URI should be in format 'moduleName/yyyy-MM-dd'")
191 val moduleName = pathArgs.head
192 val moduleRevision = REVISION_FORMAT.parse(pathArgs.get(1))
193 return QName.create(null, moduleRevision, moduleName)
194 } catch(ParseException e) {
195 throw new ResponseException(BAD_REQUEST, "URI has bad format. It should be 'moduleName/yyyy-MM-dd'")
199 private def CompositeNode toModuleCompositeNode(Module module, DataSchemaNode moduleSchemaNode) {
200 val List<Node<?>> moduleNodeValues = new ArrayList
201 val nameSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("name").head
202 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.QName, null, module.name))
203 val revisionSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("revision").head
204 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(revisionSchemaNode.QName, null, REVISION_FORMAT.format(module.revision)))
205 val namespaceSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("namespace").head
206 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(namespaceSchemaNode.QName, null, module.namespace.toString))
207 val featureSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("feature").head
208 for (feature : module.features) {
209 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(featureSchemaNode.QName, null, feature.QName.localName))
211 return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.QName, null, moduleNodeValues)
214 private def DataSchemaNode getSchemaNode(Module restconfModule, String schemaNodeName) {
215 val restconfGrouping = restconfModule.groupings.filter[g|g.QName.localName == RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE].head
216 val restconfContainer = restconfGrouping.findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE).head
217 if (schemaNodeName == RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE) {
218 return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE).head
219 } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE) {
220 return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head
221 } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) {
222 val modules = (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head
223 return (modules as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE).head
232 override invokeRpc(String identifier, CompositeNode payload) {
233 val rpc = resolveIdentifierInInvokeRpc(identifier)
234 if (rpc.QName.namespace.toString == SAL_REMOTE_NAMESPACE && rpc.QName.localName == SAL_REMOTE_RPC_SUBSRCIBE) {
235 val value = normalizeNode(payload, rpc.input, null)
236 val pathNode = value?.getFirstSimpleByName(QName.create(rpc.QName, "path"))
237 val pathValue = pathNode?.value
238 if (pathValue === null && !(pathValue instanceof InstanceIdentifier)) {
239 throw new ResponseException(INTERNAL_SERVER_ERROR, "Instance identifier was not normalized correctly.");
241 val pathIdentifier = (pathValue as InstanceIdentifier)
242 var String streamName = null
243 if (!pathIdentifier.path.nullOrEmpty) {
244 streamName = Notificator.createStreamNameFromUri(pathIdentifier.toFullRestconfIdentifier)
246 if (streamName.nullOrEmpty) {
247 throw new ResponseException(BAD_REQUEST, "Path is empty or contains data node which is not Container or List build-in type.");
249 val streamNameNode = NodeFactory.createImmutableSimpleNode(QName.create(rpc.output.QName, "stream-name"), null, streamName)
250 val List<Node<?>> output = new ArrayList
251 output.add(streamNameNode)
252 val responseData = NodeFactory.createMutableCompositeNode(rpc.output.QName, null, output, null, null)
254 if (!Notificator.existListenerFor(pathIdentifier)) {
255 Notificator.createListener(pathIdentifier, streamName)
258 return new StructuredData(responseData, rpc.output, null)
260 return callRpc(identifier.rpcDefinition, payload)
263 override invokeRpc(String identifier, String noPayload) {
264 if (!noPayload.nullOrEmpty) {
265 throw new ResponseException(UNSUPPORTED_MEDIA_TYPE, "Content-Type contains unsupported Media Type.");
267 val rpc = resolveIdentifierInInvokeRpc(identifier)
268 return callRpc(rpc, null)
271 def resolveIdentifierInInvokeRpc(String identifier) {
272 if (identifier.indexOf("/") === -1) {
273 val identifierDecoded = identifier.urlPathArgDecode
274 val rpc = identifierDecoded.rpcDefinition
278 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
280 val slashErrorMsg = String.format("Identifier %n%s%ncan't contain slash character (/). +
281 If slash is part of identifier name then use %2F placeholder.",identifier)
282 throw new ResponseException(NOT_FOUND, slashErrorMsg);
285 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
287 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
289 var CompositeNode rpcRequest;
290 if (payload === null) {
291 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
293 val value = normalizeNode(payload, rpc.input, null)
294 val List<Node<?>> input = new ArrayList
296 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
298 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
299 if (!rpcResult.successful) {
300 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
302 if (rpcResult.result === null) {
305 return new StructuredData(rpcResult.result, rpc.output, null)
308 override readConfigurationData(String identifier) {
309 val iiWithData = identifier.toInstanceIdentifier
310 var CompositeNode data = null;
311 if (iiWithData.mountPoint !== null) {
312 data = broker.readConfigurationDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
314 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
316 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
319 override readOperationalData(String identifier) {
320 val iiWithData = identifier.toInstanceIdentifier
321 var CompositeNode data = null;
322 if (iiWithData.mountPoint !== null) {
323 data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
325 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
327 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
330 override updateConfigurationData(String identifier, CompositeNode payload) {
331 val iiWithData = identifier.toInstanceIdentifier
332 val value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
333 var RpcResult<TransactionStatus> status = null
334 if (iiWithData.mountPoint !== null) {
335 status = broker.commitConfigurationDataPutBehindMountPoint(iiWithData.mountPoint,
336 iiWithData.instanceIdentifier, value).get()
338 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
340 switch status.result {
341 case TransactionStatus.COMMITED: Response.status(OK).build
342 default: Response.status(INTERNAL_SERVER_ERROR).build
346 override createConfigurationData(String identifier, CompositeNode payload) {
347 if (payload.namespace === null) {
348 throw new ResponseException(BAD_REQUEST,
349 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
351 var InstanceIdWithSchemaNode iiWithData;
352 var CompositeNode value;
353 if (payload.representsMountPointRootData) { // payload represents mount point data and URI represents path to the mount point
354 if (identifier.endsWithMountPoint) {
355 throw new ResponseException(BAD_REQUEST,
356 "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + "\" for POST operation.");
358 val completIdentifier = identifier.addMountPointIdentifier
359 iiWithData = completIdentifier.toInstanceIdentifier
360 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
362 val uncompleteInstIdWithData = identifier.toInstanceIdentifier
363 val parentSchema = uncompleteInstIdWithData.schemaNode as DataNodeContainer
364 val module = uncompleteInstIdWithData.mountPoint.findModule(payload)
365 if (module === null) {
366 throw new ResponseException(BAD_REQUEST, "Module was not found for \"" + payload.namespace + "\"")
368 val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
369 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
370 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
372 var RpcResult<TransactionStatus> status = null
373 if (iiWithData.mountPoint !== null) {
374 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
375 iiWithData.instanceIdentifier, value)?.get();
377 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
379 if (status === null) {
380 return Response.status(ACCEPTED).build
382 switch status.result {
383 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
384 default: Response.status(INTERNAL_SERVER_ERROR).build
388 override createConfigurationData(CompositeNode payload) {
389 if (payload.namespace === null) {
390 throw new ResponseException(BAD_REQUEST,
391 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
393 val module = findModule(null, payload)
394 if (module === null) {
395 throw new ResponseException(BAD_REQUEST,
396 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)");
398 val schemaNode = module.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
399 val value = normalizeNode(payload, schemaNode, null)
400 val iiWithData = addLastIdentifierFromData(null, value, schemaNode)
401 var RpcResult<TransactionStatus> status = null
402 if (iiWithData.mountPoint !== null) {
403 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
404 iiWithData.instanceIdentifier, value)?.get();
406 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
408 if (status === null) {
409 return Response.status(ACCEPTED).build
411 switch status.result {
412 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
413 default: Response.status(INTERNAL_SERVER_ERROR).build
417 override deleteConfigurationData(String identifier) {
418 val iiWithData = identifier.toInstanceIdentifier
419 var RpcResult<TransactionStatus> status = null
420 if (iiWithData.mountPoint !== null) {
421 status = broker.commitConfigurationDataDeleteBehindMountPoint(iiWithData.mountPoint,
422 iiWithData.getInstanceIdentifier).get;
424 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
426 switch status.result {
427 case TransactionStatus.COMMITED: Response.status(OK).build
428 default: Response.status(INTERNAL_SERVER_ERROR).build
432 override subscribeToStream(String identifier, UriInfo uriInfo) {
433 val streamName = Notificator.createStreamNameFromUri(identifier)
434 if (streamName.nullOrEmpty) {
435 throw new ResponseException(BAD_REQUEST, "Stream name is empty.")
437 val listener = Notificator.getListenerFor(streamName);
438 if (listener === null) {
439 throw new ResponseException(BAD_REQUEST, "Stream was not found.")
441 broker.registerToListenDataChanges(listener)
442 val uriBuilder = uriInfo.getAbsolutePathBuilder()
443 val uriToWebsocketServer = uriBuilder.port(WebSocketServer.PORT).replacePath(streamName).build()
444 return Response.status(OK).location(uriToWebsocketServer).build
447 private def dispatch URI namespace(CompositeNode data) {
448 return data.nodeType.namespace
451 private def dispatch URI namespace(CompositeNodeWrapper data) {
452 return data.namespace
455 private def dispatch String localName(CompositeNode data) {
456 return data.nodeType.localName
459 private def dispatch String localName(CompositeNodeWrapper data) {
460 return data.localName
463 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
464 if (mountPoint !== null) {
465 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
467 return findModuleByNamespace(data.nodeType.namespace)
471 private def dispatch Module findModule(MountInstance mountPoint, CompositeNodeWrapper data) {
472 Preconditions.checkNotNull(data.namespace)
473 var Module module = null;
474 if (mountPoint !== null) {
475 module = mountPoint.findModuleByNamespace(data.namespace) // namespace from XML
476 if (module === null) {
477 module = mountPoint.findModuleByName(data.namespace.toString) // namespace (module name) from JSON
480 module = data.namespace.findModuleByNamespace // namespace from XML
481 if (module === null) {
482 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
488 private def dispatch getName(CompositeNode data) {
489 return data.nodeType.localName
492 private def dispatch getName(CompositeNodeWrapper data) {
493 return data.localName
496 private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode,
497 CompositeNode data, DataSchemaNode schemaOfData) {
498 val iiOriginal = identifierWithSchemaNode?.instanceIdentifier
499 var InstanceIdentifierBuilder iiBuilder = null
500 if (iiOriginal === null) {
501 iiBuilder = InstanceIdentifier.builder
503 iiBuilder = InstanceIdentifier.builder(iiOriginal)
506 if (schemaOfData instanceof ListSchemaNode) {
507 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
509 iiBuilder.node(schemaOfData.QName)
511 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
514 private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) {
515 val keyValues = new HashMap<QName, Object>();
516 for (key : listNode.keyDefinition) {
517 val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value
518 if (dataNodeKeyValueObject === null) {
519 throw new ResponseException(BAD_REQUEST,
520 "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" +
521 key.localName + "\"")
523 keyValues.put(key, dataNodeKeyValueObject);
528 private def endsWithMountPoint(String identifier) {
529 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
532 private def representsMountPointRootData(CompositeNode data) {
533 return ((data.namespace == SchemaContext.NAME.namespace || data.namespace == MOUNT_POINT_MODULE_NAME) &&
534 data.localName == SchemaContext.NAME.localName)
537 private def addMountPointIdentifier(String identifier) {
538 if (identifier.endsWith("/")) {
539 return identifier + ControllerContext.MOUNT
541 return identifier + "/" + ControllerContext.MOUNT
544 private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, MountInstance mountPoint) {
545 if (schema === null) {
546 throw new ResponseException(INTERNAL_SERVER_ERROR,
547 "Data schema node was not found for " + node?.nodeType?.localName)
549 if (!(schema instanceof DataNodeContainer)) {
550 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
552 if (node instanceof CompositeNodeWrapper) {
553 if ((node as CompositeNodeWrapper).changeAllowed) {
554 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
556 return (node as CompositeNodeWrapper).unwrap()
561 private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
562 MountInstance mountPoint) {
563 if (schema === null) {
564 throw new ResponseException(BAD_REQUEST,
565 "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema.");
568 var QName currentAugment;
569 if (nodeBuilder.qname !== null) {
570 currentAugment = previousAugment
572 currentAugment = normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint)
573 if (nodeBuilder.qname === null) {
574 throw new ResponseException(BAD_REQUEST,
575 "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName +
576 "\" should be \"" + schema.QName.namespace + "\".\n" +
577 "If data is in JSON format then module name for \"" + nodeBuilder.localName +
578 "\" should be corresponding to namespace \"" + schema.QName.namespace + "\".");
582 if (nodeBuilder instanceof CompositeNodeWrapper) {
583 val List<NodeWrapper<?>> children = (nodeBuilder as CompositeNodeWrapper).getValues
584 for (child : children) {
585 val potentialSchemaNodes = (schema as DataNodeContainer).findInstanceDataChildrenByName(child.localName)
586 if (potentialSchemaNodes.size > 1 && child.namespace === null) {
587 val StringBuilder namespacesOfPotentialModules = new StringBuilder;
588 for (potentialSchemaNode : potentialSchemaNodes) {
589 namespacesOfPotentialModules.append(" ").append(potentialSchemaNode.QName.namespace.toString).append("\n")
591 throw new ResponseException(BAD_REQUEST,
592 "Node \"" + child.localName + "\" is added as augment from more than one module. "
593 + "Therefore node must have namespace (XML format) or module name (JSON format)."
594 + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules)
596 var rightNodeSchemaFound = false
597 for (potentialSchemaNode : potentialSchemaNodes) {
598 if (!rightNodeSchemaFound) {
599 val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment,
601 if (child.qname !== null) {
602 normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint)
603 rightNodeSchemaFound = true
607 if (!rightNodeSchemaFound) {
608 throw new ResponseException(BAD_REQUEST,
609 "Schema node \"" + child.localName + "\" was not found in module.")
612 if (schema instanceof ListSchemaNode) {
613 val listKeys = (schema as ListSchemaNode).keyDefinition
614 for (listKey : listKeys) {
616 for (child : children) {
617 if (child.unwrap.nodeType.localName == listKey.localName) {
622 throw new ResponseException(BAD_REQUEST,
623 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName +
628 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
629 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
630 val value = simpleNode.value
631 var inputValue = value;
633 if (schema.typeDefinition instanceof IdentityrefTypeDefinition) {
634 if (value instanceof String) {
635 inputValue = new IdentityValuesDTO(nodeBuilder.namespace.toString, value as String, null)
636 } // else value is already instance of IdentityValuesDTO
639 val outputValue = RestCodec.from(schema.typeDefinition, mountPoint)?.deserialize(inputValue);
640 simpleNode.setValue(outputValue)
641 } else if (nodeBuilder instanceof EmptyNodeWrapper) {
642 val emptyNodeBuilder = nodeBuilder as EmptyNodeWrapper
643 if (schema instanceof LeafSchemaNode) {
644 emptyNodeBuilder.setComposite(false);
645 } else if (schema instanceof ContainerSchemaNode) {
647 // FIXME: Add presence check
648 emptyNodeBuilder.setComposite(true);
653 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
654 var baseType = node.type
655 while (baseType.baseType !== null) {
656 baseType = baseType.baseType;
661 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
662 var TypeDefinition<?> baseType = node.type
663 while (baseType.baseType !== null) {
664 baseType = baseType.baseType;
669 private def QName normalizeNodeName(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
670 MountInstance mountPoint) {
671 var validQName = schema.QName
672 var currentAugment = previousAugment;
673 if (schema.augmenting) {
674 currentAugment = schema.QName
675 } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) {
676 validQName = QName.create(currentAugment, schema.QName.localName);
678 var String moduleName = null;
679 if (mountPoint === null) {
680 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
682 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
684 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
685 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
686 nodeBuilder.qname = validQName
688 return currentAugment